<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Graham Trott</title>
    <description>The latest articles on DEV Community by Graham Trott (@gtanyware).</description>
    <link>https://dev.to/gtanyware</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F124120%2F816fc938-f821-4f06-ba89-2313994a95dc.jpeg</url>
      <title>DEV Community: Graham Trott</title>
      <link>https://dev.to/gtanyware</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gtanyware"/>
    <language>en</language>
    <item>
      <title>The Catch-22 of programming</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Sat, 29 Nov 2025 21:06:33 +0000</pubDate>
      <link>https://dev.to/gtanyware/the-catch-22-of-programming-2e1p</link>
      <guid>https://dev.to/gtanyware/the-catch-22-of-programming-2e1p</guid>
      <description>&lt;p&gt;The Catch-22:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Only skilled programmers can guide AI to create good software.&lt;/li&gt;
&lt;li&gt;It takes a long time to gain the skills to become a skilled programmer, starting with a long period of simple coding at the entry level.&lt;/li&gt;
&lt;li&gt;AI is taking away the jobs of entry-level programmers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Becoming a programmer
&lt;/h3&gt;

&lt;p&gt;In the early days of computing, the route to becoming a programmer was straightforward. You just did it. Training courses were initially nonexistent, so we had to learn from textbooks and data sheets. The ones who had the motivation to stick with it were the ones who became professional programmers.&lt;/p&gt;

&lt;p&gt;After that, in some ways it got a lot easier. Information became more plentiful, and with the arrival of first email then the World Wide Web, there were people you could ask and articles you could read without having to order a book and wait for it to arrive. Courses became available for pretty much anything.&lt;/p&gt;

&lt;p&gt;In other ways, though, it didn’t get easier. Software became more complex, and a myriad of tools and libraries became regarded as essential things to know. Job listings started to fill up with acronyms and left many applicants feeling their efforts had all been for nothing, as the prize continued to remain just out of reach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Programming with an LLM
&lt;/h3&gt;

&lt;p&gt;Then along came AI, and suddenly it seemed a programmer's life became simpler again. No more need to remember every function in a library; just ask AI to find the one you need. But just as we started to get used to this new way of working, these upstart AI systems started to go for our jobs. And now we’re told that pretty much all programming will soon be done entirely by AI engines.&lt;/p&gt;

&lt;p&gt;Or so it seems. However, if you actually ask AI to write software, you’ll soon discover that what comes back might be classifiable as a prototype but is likely to be totally unfit for delivery as a finished product. The only way to ensure a good product is to create a bullet-proof prompt to describe what you want, then closely monitor the entire process from start to finish, enforce good working practices and test everything as it gets written. The only person who can do that is a competent programmer.&lt;/p&gt;

&lt;p&gt;So we have a Catch-22 situation. To get a good product you need to be an expert to drive AI in the right way. But because AI is taking over all the mundane programming work, few will ever spend the time and effort to become expert. Who in their right mind spends hours churning out boilerplate code when AI will spit it out in seconds? But if you don’t do it yourself you’ll never really learn how to, and you won’t be able to spot the mistakes made by AI. Likewise, you’ll never really know what any given programming language is capable of, and whether AI is making best use of it.&lt;/p&gt;

&lt;p&gt;Or to put it more concisely: To get a job you need skills, but to get skills you need a job.&lt;/p&gt;

&lt;h3&gt;
  
  
  The crux of the matter
&lt;/h3&gt;

&lt;p&gt;This is a situation that is only just developing but is set to get pretty serious quite quickly as the supply of top-level programmers dries up. The risk is that AI-generated products will frequently never develop past the prototype stage, while many will remain incomplete and generally unfit for purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are our options?
&lt;/h2&gt;

&lt;p&gt;At the heart of the problem is the way we communicate with computers. There are basically 2 ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Using traditional high-level languages. This means code written in C++, Java, Python or any other mainstream programming language. As explained above, fewer people will become skilled at this work as AI takes over the lower levels of coding.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Using natural language, e.g. English. This is the new way that has come into its own with the arrival of AI. However, program code must be unambiguous and human language is usually anything but. This is the enemy of AI, as it will usually work with what it thinks you mean rather than question a possible ambiguity, so the result may be subtly or even grossly different to that which you wanted.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Software can be categorised in several ways, but one that I find useful is to distinguish between tools and products. Tools include operating systems and code libraries, and products are the things that are built with the tools. The thing that for me stands out like a lighthouse beacon is that coders use the same language for both, when in fact they often require very different forms of language. A computer algorithm expressed in English is clumsy and hard to follow, and a set of business rules that makes perfect sense in English becomes impenetrable when converted to Python or Java. Real-world requirements are often long chains of ‘if-then-else’ logic, with a frequent need for ‘goto’ to jump from one place to another, which is of course a complete no-no in the world of structured programming. Maybe what we need is a different form of language.&lt;/p&gt;

&lt;p&gt;However much automation is provided, there will always be skilled toolmakers. Although it has limited storage capacity, the human brain is capable of insights no machine can match. So why waste such talent by asking such people to simply mind AI while it does all the hard work? Right now, the answer is that there is no separate class of coder with the ability to express clearly what is needed and guide the AI to create it, without having taken the long route to becoming a professional user of conventional languages and tools.&lt;/p&gt;

&lt;p&gt;Some people are better than others at expressing clear logic. Many become lawyers, and they use a highly constrained form of English that most of us find difficult to understand, but which has that vital property of being unambiguous. Maybe we need something akin to this when we want to guide AI?&lt;/p&gt;

&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;To help clarify where I'm heading, here's an example. Let's suppose the task is to create a new app with a windowing user interface. The first thing is to decide where it will run, so I'll say on any desktop OS. This probably suggests Python should be the language to code for, and I'll constrain it a bit further by specifying PySide to handle the graphics.&lt;/p&gt;

&lt;p&gt;The app will mimic a traditional web page, with a central data area, left and right side bars, a title bar and a footer. There's a menu bar with a single menu having 2 items, About and Exit, each with simple behaviours. It looks like this:&lt;/p&gt;

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

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

&lt;p&gt;Here's an AI prompt to create the app as shown above:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Using PySide6, create a main window with the following attributes:&lt;br&gt;
Location: 100,100&lt;br&gt;
Size: 640,480&lt;br&gt;
Title: My New App&lt;br&gt;
The window contains 3 rows.&lt;br&gt;
The first row is a panel taking 20% of the window height, containing a label with the text ‘Header’.&lt;br&gt;
The second row has a height that is what is left after the other 2 rows are created. It has 3 elements: 1 - a panel taking 20% of the window width, with a label having the text ‘Left’. 2 - a panel taking whatever width is left after the other 2 items are created, with a label having the text ‘Center’. 3 - a panel taking 15% of the window width with a label having the text ‘Right’.&lt;br&gt;
The third row is a panel taking 15% of the window height, containing a label with the text ‘Footer’.&lt;br&gt;
The window has a menu bar with a single ‘File’menu having 2 items:&lt;br&gt;
An ‘About’ item that when clicked shows a simple dialog.&lt;br&gt;
An ‘Exit’ item that terminates the app when clicked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is a fairly clumsy way to program. It's largely unstructured, and when changes are needed whole sections may have to be rewritten. Most importantly, to avoid ambiguity it requires discipline in the choice of wording; the kind of discipline that only comes with experience. Since there’s no “compiler” to parse the text and spot problems with language or syntax, errors can easily slip through unnoticed.&lt;/p&gt;

&lt;p&gt;So let’s see if we can use a simplified form of English to describe this window. The vocabulary I’ve chosen is arbitrary and many syntax variations can be accommodated to provde alternative ways of expressing the same thing, as in English. To be clear, when I refer to this as English I mean it's text that is mostly if not completely confined to English words, not mathematical symbols. And it’s terse; this is a recipe, not a poem. Let’s call this the pseudocode version.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;! Variables used in the script
window Window
layout Content
panel Panel
label Header
panel Panel2
label Left
label Center
label Right
label Footer
menu Menu
menuitem Item

! Create a window
create Window
   at 100 100
   size 640 480
   title `My New App`

! Add the main content; a series of nested panels and labels
create in Window Content type rows

! The first row
create in Content Panel height 20%
create in Panel Header text `Header`

! The second row
create in Content Panel type columns
create in Panel Panel2
create in Panel2 Left width 20% text `Left`
create in Panel Panel2
create in Panel2 Center text `Center`
create in Panel Panel2
create in Panel2 Right width 15% text `Right`

! The third row
create in Content Panel height 15%
create in Panel Footer text `Footer`

! The menu
create in Window Menu text `File`
add to Menu Item text `About`
on click Item show dialog type info text `A sample application`
add to Menu Item text `Exit`
on click Item exit

show Window
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is 47 lines, compared to roughly 100 for the Python code that resulted from the AI query.&lt;/p&gt;

&lt;p&gt;This version has a big advantage over the natural language version. It’s compilable to a form that will run directly from the command line. This means it can be used as a prototype product, rather than being just a specification of how to build the product. That’s demonstrated by the last line, &lt;code&gt;show Window&lt;/code&gt;, which will display the window on a suitably-equipped system. Compilers and runtimes to handle this kind of script are not hard to write; in fact, something very close to this already exists in the Python world. See &lt;a href="https://github.com/easycoder/easycoder-py" rel="noopener noreferrer"&gt;EasyCoder&lt;/a&gt;. (Disclaimer: This is my own product.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How does this work with AI?
&lt;/h2&gt;

&lt;p&gt;Can AI use pseudocode instead of natural language to generate a fully native application in whatever underlying language you prefer? To answer this question, I did the following:&lt;/p&gt;

&lt;p&gt;First, I submitted the original natural-language request to DeepSeek, which generated Python code. The image above is a screenshot of the running code.&lt;/p&gt;

&lt;p&gt;I then asked DeepSeek if it could recognise the pseudocode version. It came back with the confirmation that the two versions were in fact identical, and that if a suitable dictionary were built it could easily handle pseudocode instead of natural language. It described the approach as&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;a declarative UI specification system where:&lt;br&gt;
Pseudocode = Human-readable UI description&lt;br&gt;
Keyword dictionary = Mapping to actual UI components and properties&lt;br&gt;
Generator = Interprets pseudocode → creates actual UI code&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;then went on to comment that&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is similar to how many modern UI frameworks work (React, Vue components) but with an even more abstracted, human-readable syntax.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and offered the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Benefits of This Approach:&lt;br&gt;
Rapid Prototyping - Write UI structure in simple English-like syntax&lt;br&gt;
Consistency - Enforced patterns through keyword definitions&lt;br&gt;
Platform Independence - Same pseudocode could generate PySide6, Tkinter, web UI, etc.&lt;br&gt;
Non-technical Friendly - Designers can specify layouts without coding&lt;br&gt;
Version Control Friendly - Pseudocode is clean and diff-able&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Compilable pseudocode reduces complexity
&lt;/h2&gt;

&lt;p&gt;In the example above, many assumptions have been made. The way items are positioned and sized in their parent containers, the foreground and background colors, the fonts used for text, border widths and so on. In the browser world these are all handled by stylesheets, and in fact PySide handles most common stylesheet definitions. In any practical application, some form of stylesheet will have to be defined, holding global values and values that are specific to a class of on-screen widget. Doing all of this using natural language is painful to say the least, but a variant of the same pseudocode can be used to create JSON stylesheet files that can be read by the compiler. The language will need a vocabulary that allows the user to define which styles belong where, but this is not a hard task. DeepSeek offered to design such a system for me, but I’m not yet ready quite yet to dive further into this.&lt;/p&gt;

&lt;p&gt;By scripting with compilable pseudocode, products can be developed quickly with or without AI, or by combining both. If people start to use pseudocode, AI will gradually be able to provide more assistance in creating applications by examining examples of those already created.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does this change the way we work?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Unlike natural language, pseudocode scripts can be compiled on any computer and then run; hence the last line in the script shown above - &lt;code&gt;show Window&lt;/code&gt;. The entire script can be run from the command line, directly from the source text. The performance may not be sparkling, but as a means of catching errors it's unbeatable as corrections can be made instantly and the run started again. Typical compilation times are in the tens of milliseconds, even for quite large scripts. The runtime even includes a visual debugger that allows breakpoints to be added and variables examined with the application halted.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The script uses a lot of variables to represent screen elements. Some of these are reused but others hold values that are of interest for the lifetime of the application. The natural language version might name such elements, but the syntax to refer to them will be clumsy and impede understanding. Having them available as code elements allows them to be accessed at any time simply by name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since performance is rarely an issue with prototypes, the compiler and runtime for these scripts can check very aggressively for potential coding errors during the development phase. Typical examples are the wrong type of variable being used, or an attempt to use a variable that has no value assigned to it. Once the prototype is seen to run without errors the script can be submitted to AI for recoding in Python, JavaScript, C++ or whatever is best for the target environment. This maximises performance.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  New opportunities
&lt;/h2&gt;

&lt;p&gt;This takes us back to the title of this article - the catch-22. Pseudocode allows very large chunks of functionality to be represented by a fairly concise English syntax. New functionality, created by experts in their fields, gets added as extra pseudocode vocabulary, which is easily understood by AI and by people familiar with the domain in which they are working. It is fairly easy to take a given piece of functionality and create a new language feature for it, and it does not usually require any great knowledge of how that functionality is coded. Take Google Maps, for example, which has a remarkably simple API for such a complex and comprehensive product. We will still need highly skilled programmers to create new core functionality, but pseudocode unlocks the door to making it accessible to those with more limited skills, to build the products that depend on this functionality. This could well be where most human programmers will be employed; managing AI efficiently to create the applications needed by the world.&lt;/p&gt;

&lt;p&gt;And let’s not forget the problem of maintenance. Having advanced applications coded in pseudocode that can be understood both by AI and by humans makes future maintenance far simpler than requiring every maintenance engineer to know the details of every programming language and its libraries.&lt;/p&gt;

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

&lt;p&gt;As AI takes over the jobs of those doing simpler forms of coding, fewer people will come up through the system and progress to the higher skill levels. The adoption of a suitable pseudocode for describing a wide range of applications will reduce the need for highly skilled coders and create a demand instead for logical thinkers who understand problem domains and can translate requirements into a form that can run as a prototype. Once this has been tested thoroughly it can then be handed to AI to generate the final code for the target environment.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>pseudocode</category>
    </item>
    <item>
      <title>OTA Python updates with ESP-Now</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Sat, 27 Sep 2025 14:33:42 +0000</pubDate>
      <link>https://dev.to/gtanyware/ota-python-updates-with-esp-now-4iai</link>
      <guid>https://dev.to/gtanyware/ota-python-updates-with-esp-now-4iai</guid>
      <description>&lt;p&gt;This is the third article in a series about using ESP-Now to implement an ESP32-based master-slave network. &lt;a href="https://dev.to/gtanyware/esp-now-master-slave-in-micropython-16e2"&gt;Part one&lt;/a&gt; describes the main network comms class and &lt;a href="https://dev.to/gtanyware/channel-hopping-on-the-esp32-c3-with-esp-now-2led"&gt;part two&lt;/a&gt; describes how to follow the system router when it channel-hops. In this final article I'll describe an implementation of over-the-air (OTA) updates to the Python and other files held on the devices.&lt;/p&gt;

&lt;p&gt;In part one of this series, when a message is received it is handed to a message handler, which carries out the needed action(s) and returns a result. The following is that message handler. Most of it is directly applicable to any similar application, since there are only so many things a network slave is able to do.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unhexlify&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;renameFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;deleteFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;createDirectory&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRelay&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saveError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="c1"&gt;#        print('Handler:',msg)
&lt;/span&gt;        &lt;span class="n"&gt;bleValues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBLEValues&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getUptime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; :&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bleValues&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;uptime&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ON&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No relay&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;off&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; OFF&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;off&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No relay&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;relay&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relay&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No relay&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reset&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ipaddr&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIPAddr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;environ&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEnviron&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTemperature&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pause&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pause&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK paused&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;resume&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK resumed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;delete&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;deleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;part&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Format is part:{n},text:{text}
&lt;/span&gt;            &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;part&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;unhexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saveError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saveError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Save error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;
                        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;saveError&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Sequence error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;save&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
                &lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Save &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                    &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="c1"&gt;# Check the file against the buffer
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;File saved&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Bad save&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tname&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Update &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;deleteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="nf"&gt;renameFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mkdir&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mkdir &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;createDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Unknown message: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;#        print('Handler response:',response)
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checkFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# index in lst
&lt;/span&gt;                &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# position in current list item
&lt;/span&gt;                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="c1"&gt;# End of file: check if we've also finished the list
&lt;/span&gt;                        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
                            &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                            &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
                    &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
                        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                        &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;__init__()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The system makes extensive use of a &lt;code&gt;Config&lt;/code&gt; class, which holds all the system-specific configuration data such as which I/O pins do what. It also acts as a central message interchange, with functions that either return an object that has the required functionality or call the objects themselves, avoiding the need for every class to know about all the others. So in this &lt;code&gt;Handler&lt;/code&gt; class, the relay object supplied by &lt;code&gt;Config&lt;/code&gt; is a class that has the ability to turn the relay on and off and to return the current relay state.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;handleMessage()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is where most of the work is done. The function handles a set of commands, most of which should be fairly obvious, though some will be more relevant than others. For example, elsewhere in the code there's a Bluetooth (BLE) module that collects occasional transmissions from specific devices and for some message types appends these values to the string that is returned to the caller. In my system these are temperature values, but not everyone will want this feature.&lt;/p&gt;

&lt;p&gt;The most interesting part of this is the OTA file updater, which works like this. The maximum size of an ESP-Now data packet is something over 200 bytes, so few Python scripts can be transferred in a single message. I chose to send 100 bytes at a time, with the text data encoded as hex values. The file is sent as a series of messages, each containing 100 bytes, and the messages are prefixed by the command &lt;code&gt;part&lt;/code&gt; and a part number starting with zero. The function splits up the packet to extract the part number and the data itself. If the part number matches what was expected, the text is added to a list, otherwise the function returns an error. For a successfully received part, the function returns the part number and the size of the data, which the sender can use to verify that it has been received successfully.&lt;/p&gt;

&lt;p&gt;When all the parts have been sent, the list can be saved to a temporary file. This is done by the &lt;code&gt;save&lt;/code&gt; command.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;checkFile(()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Now we need to check the data has not been corrupted. This function reads the file back, one character at a time, and compares it with the contents of the list. If no errors are found, the new file can be assumed to be good.&lt;/p&gt;

&lt;p&gt;However, a problem comes when several Python files all need to be updated together. If only some of the files are updated and then an error occurs or for some other reason the update stops, there could be a mismatch between the various parts and the program will not run after the next reboot. So rather than update files one by one they should instead be done as a batch. Only when all of the files have been saved and checked is it then safe to rename the entire batch. This is done by the &lt;code&gt;update&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;In a practical implementation, when an error is discovered and reported back to the sender, the entire save can be retried. Although for the most part OTA updates proceed without a hitch, in some cases, particularly where the signal strength isn't great or suffers from interference, it can take several attempts to transfer a large file. I allow up to 10 retries before abandoning the save.&lt;/p&gt;

</description>
      <category>networking</category>
      <category>python</category>
      <category>esp32</category>
      <category>iot</category>
    </item>
    <item>
      <title>Channel-hopping on the ESP32-C3 with ESP-Now</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Sat, 27 Sep 2025 14:30:21 +0000</pubDate>
      <link>https://dev.to/gtanyware/channel-hopping-on-the-esp32-c3-with-esp-now-2led</link>
      <guid>https://dev.to/gtanyware/channel-hopping-on-the-esp32-c3-with-esp-now-2led</guid>
      <description>&lt;p&gt;This is the second article in a series about using ESP-Now in Micropython to implement an ESP32-based master-slave network. &lt;a href="https://dev.to/gtanyware/esp-now-master-slave-in-micropython-16e2"&gt;Part one&lt;/a&gt; describes the main network comms class and &lt;a href="https://dev.to/gtanyware/ota-python-updates-with-esp-now-4iai"&gt;part three&lt;/a&gt; describes how to do reliable OTA file updates. &lt;/p&gt;

&lt;p&gt;The 2.4GHz wifi band is getting crowded. Much of the heaviest traffic now goes on 5GHz, but that leaves a lot of devices competing for bandwidth; in particular those in the world of IOT. Modern domestic routers deal with this by channel-hopping; they periodically monitor the network to see which channel is the least busy and switch to that channel. This can happen as frequently as every hour or two, but I would hope that routers are intelligent enough to realise that constantly ping-ponging back and forth would be mostly ineffective as well as annoying. For although computer operating systems can deal pretty seamlessly with channel-hopping, simpler devices don't have the firmware to do this, leaving you and me to do it in our applications.&lt;/p&gt;

&lt;p&gt;The simplest and most recommended approach is of course to restrict the router to a single channel, but this is only feasible if you have control of the router. For my master-slave system I made no such assumption; I decided to grab the bull by the horns and work out a solution.&lt;/p&gt;

&lt;p&gt;Before continuing, I should mention that this code has been developed for the ESP32-C3, which is a cut-down device with only a single radio subsystem. This means it can only handle a single wifi channel. Other variants have two subsystems and can operate two channels independently. The code here and in the rest of the series is built to handle the more constrained environment. During development I found that many published articles about ESP-Now on the ESP32 don't work on the C3 variant as they rely on having the dual-radio.&lt;/p&gt;

&lt;p&gt;In fact, with dual radio devices there may well be no need to channel-hop at all. The entire ESP-Now subsystem can operate independently of the AP and STA interfaces. However, the C3 variant occupies a niche that was previously filled by the ESP8266; low cost and very small size, as exhibited by devices such as the ESP32-C3 Super Mini and the ESP01-C3, allowing a full implementation of Micropython to run many cost and space-critical applications. Here's a ESP32-C3 Super Mini (with antenna) and an ESP01-C3.&lt;/p&gt;

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

&lt;p&gt;So if you're still with me, here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Channels&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Starting Channels&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myMaster&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMyMaster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMaster&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSSID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPassword&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkRouterChannel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setupSlaveTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Set up slave tasks&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMyMaster&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;countMissingMessages&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resetCounter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idleCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;findMyMaster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hopToNextChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myMaster&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;espSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Ping response from&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;myMaster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Found master on channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;countMissingMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Count missing messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;
        &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;
        &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idleCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idleCount&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idleCount&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;No messages for 30 seconds&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c1"&gt;# Retry the current channel
&lt;/span&gt;                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ping&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idleCount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Master not found on channel &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hopToNextChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hopToNextChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
                &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;==-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checkRouterChannel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Check the router channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;
            &lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Reconnecting...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isconnected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; router changed channel from&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_event_loop&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; no channel change&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;espComms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restartESPNow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this system, the &lt;code&gt;ESPComms&lt;/code&gt; class described previously manages all network functions except for the channel hopping, which is done by this &lt;code&gt;Channels&lt;/code&gt; class. The system also makes heavy use of a &lt;code&gt;Config&lt;/code&gt; class, which manages system data such as SSIDs and passwords, I/O pin usage and so on. It also acts as a central routing point for most communications between other modules, having getter and setter functions that just pass on calls to the appropriate class. &lt;code&gt;Channels&lt;/code&gt; is something of an exception as it's tied quite closely to &lt;code&gt;ESPComms&lt;/code&gt;, so most of the function calls in this class go there directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;__init__()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This defines the channels to be used. Although there are about 14 distinct channels in the 2.4GHz band, they are close enough together that adjacent ones overlap quite seriously. In fact, there are only three completely non-overlapping channels: 1, 6 and 11, so it will come as no surprise that most routers hop between these three. The function defines these channels. Its other job is to start up an asynchronous job (&lt;code&gt;checkRouterChannel()&lt;/code&gt;, see below) to monitor the system and detect when the channel has changed. This is needed when the system acts as the master device, as otherwise the change of channel might go undetected.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;setupSlaveTasks()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is typically called later during initialization, after other things have settled down.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;resetCounter()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The class constantly increments a counter, and when it reaches a predefined limit, action must be taken. This function is called from &lt;code&gt;ESPComms&lt;/code&gt; whenever a message is received, to prevent the action from being triggered.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;findMyMaster()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When a slave starts up it has no way of knowing which channel its master device is using. So it calls this function, which issues a 'ping' message on the channel that was saved during the last run. If it gets a reply it knows it's picked the right channel. Otherwise, it hops to the next channel, thereby saving the new channel number, and resets itself. Only once it gets a reply to the ping can it break out of this endless cycle and start normal operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;countMissingMessages()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This function runs continuously, incrementing the counter and checking if it has exceeded its set limit. If so, it first pings the master to see if this is still awake and on the same channel. There may be a perfectly valid reason for missing messages, such as the system being in maintenance mode where normal operations are suspended. If the ping fails, the device will reboot and re-enter the &lt;code&gt;findMyMaster()&lt;/code&gt; cycle above.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ping()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This sends a 'ping' message to the master device and waits for a reply, returning &lt;code&gt;True&lt;/code&gt; if it gets one within a second. To save you having to look it up, the function &lt;code&gt;espSend()&lt;/code&gt; in &lt;code&gt;ESPComms&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;espSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;espSend:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;countMissingMessages()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This continually increments &lt;code&gt;idleCount&lt;/code&gt; and checks if it has reached its predefined limit. If so, messages have gone missing, so it hops to the next channel and resets.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;checkRouterChannel()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is called periodically - say every 5 minutes - by the system master to ensure it is still on the same channel as the router. To do this, the STA interface must be deactivated and reconnected. If the channel has changed, a reboot is needed, otherwise ESP-Now can be restarted.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>networking</category>
      <category>python</category>
      <category>esp32</category>
    </item>
    <item>
      <title>ESP-Now master-slave in Micropython</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Sat, 27 Sep 2025 14:25:23 +0000</pubDate>
      <link>https://dev.to/gtanyware/esp-now-master-slave-in-micropython-16e2</link>
      <guid>https://dev.to/gtanyware/esp-now-master-slave-in-micropython-16e2</guid>
      <description>&lt;p&gt;This article describes how to build a master-slave network of ESP32 devices using Micropython and ESP-Now.&lt;/p&gt;

&lt;p&gt;ESP-Now is a simple low-level protocol for communicating between ESP32-based devices independently of HTTP. Many articles have been written on the subject, but the great majority cover simple scenarios, and once things get more complex these simple techniques start to reveal their limitations. This article is the result of several weeks of trial and error, with extensive help from DeepSeek, which patiently sought out the answers to a seemingly endless series of baffling questions.&lt;/p&gt;

&lt;p&gt;[Note: When I asked DeepSeek how best to make code available to a wider audience, it encouraged me to write this article. Forgive me for anthropomorphising slightly, but AI engines appear to enoy reading articles that describe working code. They also examine code repositories, but it seems they give greater weight to those that are supported by articles aimed at a technical readership.]&lt;/p&gt;

&lt;p&gt;The system in question comprises a single master (hub) device that acts as a gateway to an indefinite number of slaves arranged as a star network. In a &lt;a href="https://dev.to/gtanyware/using-subnets-to-extend-esp-now-mpb"&gt;previous article&lt;/a&gt;, I described how to extend such a network by arranging for slaves to run their own local networks, which overcomes the problem of wifi range and also of the number of peers that any one node can support. The code I presented was somewhat simplistic and proved to have a number of limitations, so here I describe a revised class that deals with the problems unearthed during the development process.&lt;/p&gt;

&lt;p&gt;I'll start by presenting the code, then I'll describe its most important features:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;espnow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ESPNow&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ESPComms&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ESPNow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMaster&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Starting as master&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IF_STA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSSID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPassword&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Connecting...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isconnected&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Timeout&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clearAndReset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ifconfig&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setIPAddr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ipaddr&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Starting as slave on channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AP_IF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mac&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMAC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;RBR-Now-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ssid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;00000000&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ifconfig&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.9.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;255.255.255.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;192.168.9.1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8.8.8.8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getName&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMaster&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WLAN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IF_STA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ESP-Now initialised&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestToSend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;closeAP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;randrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;999999&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Password:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;essid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;peers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_peer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;OSError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Failed to add peer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to ESP-NOW: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Added&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;to peers on channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;espSend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;espSend:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="c1"&gt;#        print(f'Send {msg[0:20]}... to {mac} on channel {self.channel}')
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestToSend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestToSend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;
                    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
                    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                            &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;irecv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                                &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="c1"&gt;#                                print(f"Received reply: {reply}")
&lt;/span&gt;                                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;
                                &lt;span class="k"&gt;break&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
                        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail (no reply)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail (no result)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail (adding peer)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Starting ESPNow receiver&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Received&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ping&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;pong&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ping:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="c1"&gt;# It's a message to be relayed
&lt;/span&gt;                            &lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                            &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
&lt;span class="c1"&gt;#                            print(f'Slave: {slave}, msg: {msg}')
&lt;/span&gt;                            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="c1"&gt;# It's a message for me
&lt;/span&gt;                            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHandler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRSS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resetCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getMyMaster&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMaster&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setMyMaster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Can&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s"&gt;t respond&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;requestToSend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sending&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;
                    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Not active&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickWatchdog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getRSS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;restartESPNow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;ESPNow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job of this class is to deliver small packets of data to specified MAC addresses and to return results back to the caller. As ESP-Now is a low-level protocol it does not deal with IP addresses, access point names or any of the other higher layers of the OSI model.&lt;/p&gt;

&lt;p&gt;ESP-Now requires the device to be already set up in either Access Point or Station mode. Its communications go unnoticed by higher-level functions such as HTTP, so it can run concurrently with them.&lt;/p&gt;

&lt;p&gt;In this class, the master (hub) device connects to the house router with its station (STA) interface but the slaves do not (they still however require STA to be active for ESP-Now to work). Both master and slaves use the same code modules but in different ways. In both cases, an access point (AP) is set up for the first 2 minutes following a reboot, so that an administrator can talk directly to the device over-the-air from a local browser. This feature is used for configuration, mostly avoiding the need for ESP32 modules to be physically extracted for code alterations.&lt;/p&gt;

&lt;p&gt;In this implementation, all messages, commands and requests are initiated outside of the ESP-Now network and handed to an HTTP server maintained by the master for onward transmission. Slave devices do not initiate messages other than the occasional 'ping' (see below). Adding full two-way messaging would result in considerable extra complexity, and all the necessary features can usually be provided without it.&lt;/p&gt;

&lt;p&gt;The code here was developed with a lot of trial-and-error and the assistance of AI to find relevant examples. There turned out to be none that directly considered the complexities of the scenario; most are far simpler. If anyone else has solved the same problem, they hadn't told the world about it at the time this code development was underway.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;__init__()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The class makes use of a second module, &lt;code&gt;Config&lt;/code&gt;, which handles system parameters such as router SSID/password and identifies which I/O pins are used by the device. This data is typically held in a JSON file that can be read and written as necessary. Wherever you see &lt;code&gt;config.xxxxx()&lt;/code&gt;, a function in the config module is being called.&lt;/p&gt;

&lt;p&gt;ESP-Now is extremely fussy about configuration. There may be differences between the variants of ESP32; this code is known to work on ESP32-C3, which is a fairly basic model, so it should be fine on most other variants. There is a strict sequence to follow, which differs between master and slave.&lt;/p&gt;

&lt;p&gt;The master first requires STA to be active. Here it is connected to the house router, which governs the wifi channel the system will use. Then the AP is set up, and finally ESP-Now is activated. This ordering guarantees that ESP-Now will use the STA wifi channel.&lt;/p&gt;

&lt;p&gt;The slave does things the other way round, first setting up AP and following that with a stub STA. This ensures that the AP channel will be used for ESP-Now communications. If you don't get this order right, some things will probably not work and strange errors will arise.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;closeAP()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;During the first 2 minutes following boot, the AP can be connected from a browser and will accept requests and commands. These include rewriting the JSON config file and rebooting the device. After this period, the SSID of the AP is changed to &lt;code&gt;-&lt;/code&gt; and a random password is applied, making it almost impossible to discover and connect to. This is a security feature that prevents malicious interference unless the attacker can physically repower the device.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;addPeer()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;It's very easy to get ESP-Now peer management wrong. The published documentation gives few hints as to how fussy the protocol is about handling peers. For example, it fails to mention that a channel change not only invalidates the peers table but makes it impossible to recreate. Only a reset will do that.&lt;/p&gt;

&lt;p&gt;Although ESP-Now will happily receive messages from anywhere, sending can only be done to a peer whose MAC address has been registered with &lt;code&gt;e.add_peer()&lt;/code&gt;. This must be done just once for any given peer or an error will be thrown. So this function manages its own peers list to track what has already been added. Note that the device is likely to have 2 MAC addresses; one for each of its 2 interfaces. When you initiate a send it will usually be to one of these, but when replying to a message it will be the other. Both must be added to the peers table.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;send()&lt;/code&gt; and &lt;code&gt;receive()&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;receive()&lt;/code&gt; function runs asynchronously in a loop, waiting for messages and replying to them. The original version of this class allowed messages to be sent without any consideration of what might happen if both activities try to take place at the same time. In a simple situation this will often work, but it's extremely vulnerable. One solution that appeared in the AI searches was to use a lock, so that send and receive can be exclusive, but this gives rise to some horrible issues that are hard to track down. The strategy here is one that avoids such issues and is much easier to debug.&lt;/p&gt;

&lt;p&gt;In the receive loop, when a message arrives it is passed to the &lt;code&gt;handleMessage()&lt;/code&gt; function in a companion &lt;code&gt;Handler&lt;/code&gt; class, which returns a reply to send back to the caller. While this is happening, a &lt;code&gt;sending&lt;/code&gt; flag is set &lt;code&gt;False&lt;/code&gt;. Any request to send will cause the sender to raise &lt;code&gt;requestToSend&lt;/code&gt; and block until &lt;code&gt;sending&lt;/code&gt; goes &lt;code&gt;True&lt;/code&gt;, which is done at the bottom of the receive loop. The receive function can tell from &lt;code&gt;requestToSend&lt;/code&gt; that the sender is waiting. The function can now release &lt;code&gt;sending&lt;/code&gt; to allow the message to be sent, and it then waits for that process to finish.&lt;/p&gt;

&lt;p&gt;However, a received message might not have come from the device we just sent to. This system is designed to handle router channel changes, and when this happens the master quickly changes to the new channel, but all the slaves know is that messages have stopped arriving (because they're all on the wrong channel). So after a while they issue a 'ping' message to the master. If it replies they're on the right channel, otherwise they change channels and try the ping again.&lt;/p&gt;

&lt;p&gt;The result is that one or more 'ping' messages can arrive while the sender is waiting for a reply to its own message, so the code watches out for them and discards them. Eventually it sees its own reply message and returns it to the caller, but before doing so it clears &lt;code&gt;sending&lt;/code&gt;, which releases the receiver to look out for more incoming messages.&lt;/p&gt;

&lt;p&gt;Note that this code cannot deal with anything but 'ping' messages from its slaves. Any scenario requiring a more general 2-way send and receive will probably need to use some form of addressing to identify message senders.&lt;/p&gt;

&lt;p&gt;The code in &lt;code&gt;receive()&lt;/code&gt; has one more feature. If the received message starts with &lt;code&gt;!&lt;/code&gt; it's not for this device, but instead is a request to forward a message to another device. The MAC address of the intended recipient follows the &lt;code&gt;!&lt;/code&gt; and is terminated by a comma. The code strips these away and is left with a MAC address and a (shorter) message, which it forwards, returning whatever returns back up to its own caller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Channel changes
&lt;/h2&gt;

&lt;p&gt;Because the 2.4Ghz wifi band is often quite crowded, most modern wifi routers run a background task that looks out for the least busy channel. Every so often, typically at intervals of several hours, the router will then change to this quieter channel. PC operating systems are aware of this behaviour and engage in a dialog with the router to get advance notice of an upcoming change, so the effect is barely noticeable by the users of these machines. However, simple IOT devices are not generally equipped to deal with channel changes; all they know is that messages stop arriving. To avoid the need to restrict a router to a single channel, the &lt;code&gt;Channels&lt;/code&gt; class works with the code above to deal with this and other related issues, and this is described in &lt;a href="https://dev.to/gtanyware/channel-hopping-on-the-esp32-c3-with-esp-now-2led"&gt;part two&lt;/a&gt; of this series. &lt;a href="https://dev.to/gtanyware/ota-python-updates-with-esp-now-4iai"&gt;Part three&lt;/a&gt; describes the message handler, which includes a strategy for reliably updating Python modules over the air.&lt;/p&gt;

&lt;p&gt;Full code and documentation for the project can be found at &lt;a href="https://github.com/easycoder/rbr" rel="noopener noreferrer"&gt;https://github.com/easycoder/rbr&lt;/a&gt;. The Micropython code is in the &lt;code&gt;RBRNow&lt;/code&gt; folder. I may be able to keep the code above in step with changes as they happen, but it would be better to use the repository version.&lt;/p&gt;

</description>
      <category>iot</category>
      <category>networking</category>
      <category>python</category>
      <category>esp32</category>
    </item>
    <item>
      <title>An AI virtual keyboard</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Mon, 12 May 2025 15:30:49 +0000</pubDate>
      <link>https://dev.to/gtanyware/an-ai-virtual-keyboard-d0f</link>
      <guid>https://dev.to/gtanyware/an-ai-virtual-keyboard-d0f</guid>
      <description>&lt;p&gt;I only recently overcame my usual inertia and responded to the invitations by VSCode to start using Copilot. Since then I've spent quite some time refining a fairly complex prompt, so I've decided to share my experience with the community.&lt;/p&gt;

&lt;p&gt;I operate in the world of Python. In this world there really is no excuse for not using a graphical interface wherever one is appropriate. It's really easy to get Copilot to create a UI to accept input and present output, though a little harder to produce something that's also aesthetically pleasing. But there's a point at which a prompt takes longer to write than does coding the entire job manually. &lt;/p&gt;

&lt;p&gt;The project I'm working on is a home heating control system, the control code and the user interface for which are both written in Python, the latter with some help from Copilot. The controller hardware is a 7-inch industrial computer from Ali Express, which arrived with a dreadful Android implementation but which I successfully reflashed to Debian Linux. Now it's a proper computer, but it lacks an essential feature; there's no virtual keyboard. (There seem to be a couple of truly appalling ones out there, but I wouldn't inflict them on a customer.)&lt;/p&gt;

&lt;p&gt;So the need was for a Python virtual keyboard. Quite a tedious manual programming task, but I figured it would be ideal for Copilot to get its teeth into. I decided to emulate an Android keyboard, which has 4 views that switch when shift or mode keys are tapped, and above the keys there's a text field for the characters being typed. Here's the first view of the keyboard:&lt;/p&gt;

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

&lt;p&gt;'Prompt engineering' is the term that refers to the skill needed to get effective results from an AI tool. As with most things in life, the GIGO principle applies; Garbage In, Garbage Out. I found it necessary to apply a good deal of guidance or the results had severe limitations and were a poor starting point for further manual embellishment. But if you're careful with your prompts you can get quite decent Python code from an AI engine. &lt;/p&gt;

&lt;p&gt;I say "can" because there's a massive caveat coming. A keyboard is not an exceptionally complex item, but without a lot of guidance all the AI engines I tried produced poorly structured code that didn't behave properly. So I tried again, this time leveraging my existing knowledge of Python and PySide (the Qt graphics library module) to structure my prompt. I requested a series of classes - a keyboard button, a row of buttons, a keyboard view comprising several rows, and finally the keyboard itself - giving for each class a set of function definitions and letting Copilot fill in the details. It turned out to be quite a substantial task. In fact, the prompt - even though it's incomplete - is far longer than the final working Python code - somewhere around 500 lines versus 320 lines of Python.&lt;/p&gt;

&lt;p&gt;One problem is that human-computer interfaces are a lot more complex that might be imagined, and a lot of details have to be spelled out in, well, excruciating detail. That's a job for a computer program, of course, which begs the question of what kind of language we should be using to write AI prompts. It's early days for this technology, but I feel there's a Eureka! moment waiting to be unearthed.&lt;/p&gt;

&lt;p&gt;Sadly, the rather long prompt below will not create a complete keyboard. For example, there's no text box to receive the typed characters. And if your experience is anything like mine, you won't get a complete set of keyboard views; Copilot just generates a comment that views 1, 2 and 3 are similar to view 0 and invites you to do the rest yourself. It seems to be influenced by previous versions of your prompt and decides to ignore bits of the new one. However, what you do get is probably enough to make completing the job by hand relatively easy.&lt;/p&gt;

&lt;p&gt;And that's my main take-away from this exercise. There are a lot of details that are simply too tedious to cover in an all-encompassing prompt script. You're far better off generating the main outline then using smaller prompts to fill in the nit-picky little details.&lt;/p&gt;

&lt;p&gt;The bottom line is, AI isn't going to take away your job and do it better. Not yet, anyway. But it can be a most valuable assistant, so please start using it. And your comments are welcome - I'm still at the start of the learning curve.&lt;/p&gt;

&lt;p&gt;(Cover image by Gemini)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This prompt describes a virtual keyboard,
to be written in Python using Pyside6 graphics.
I provide Python-style class definitions,
with constructor and function prototypes.
Python comments are general notes and instructions
to help build the classes.

#---------------------------------------------------------------

class KeyboardButton(QPushButton):
    def __init__(self, width, height, callback, text, icon):

# The text to be shown on the button can be any string or None
# The icon to be shown on the button can be any graphic, or None
# The button has rounded corners of radius 20% of its height
# The font size is half the button height
# The background of the button is white
# Apply a light shadow to the right and bottom of the button
# When the button is clicked it moves 2 pixels down and right,
# waits 200ms then moves back to its original position.
# On click, call function 'callback' with the value of 'text'.

#---------------------------------------------------------------

class KeyboardRow(QHBoxLayout):
    def __init__(self, list):  # add the items in 'list'

#---------------------------------------------------------------

class KeyboardView(QVBoxLayout):
    def __init__(self, list):  # add the items in 'list'

#---------------------------------------------------------------
This class is a complete keyboard with 4 views,
which are added one by one and only one of which can be visible.

class VirtualKeyboard(QStackedWidget):
    def __init__(self, buttonHeight):
        super().__init__()
        self.buttonHeight = buttonHeight
    def addView(self, view): # Add 'view' to the VirtualKeyboard
    def getCurrentView(self): # return the current index
    def onClickChar(self, keycode): # Print the key code
    def onClickShift(self, keycode): # Print 'Shift'.
        # if currentView is 0 call AbstractKeyboard.setCurrentIndex(1)
        # elif currentView is 1 call AbstractKeyboard.setCurrentIndex(0)
    def onClickNumbers(self, keycode):  # Print `Numbers`
        # call AbstractKeyboard.setCurrentIndex(2)
    def onClickLetters(self, keycode):  # Print `Letters`
        # call AbstractKeyboard.setCurrentIndex(3)
    def onClickBack(self, keycode):  # Print 'Back'
    def onClickSpace(self, keycode):  # Print `Space`
    def onClickEnter(self, keycode):  # Print 'Enter'

# This class sets up a keyboard

# __init__() calls each of the addKeyboardLayout() functions

The 4 keyboard views are as follows.
All buttons have the height given by buttonHeight.
All buttons have the same width as their height
unless specified otherwise.

#---------------------------------------------------------------
# View 0

Create an empty row list

Create a list of KeyboardButton with the characters in 'qwertyuiop'
The 'callback' function is onClickChar
Create a KeyboardRow with this list
Add the KeyboardRow to the row list

Create an empty list of widgets
Add a horizontal stretch to the widget list
Create a list of KeyboardButton with the characters into 'asdfghjkl'
The 'callback' function is onClickChar
Append this list to the widget list
Add a horizontal stretch to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'up.png'
    the 'callback' function is onClickShift
Append the KeyboardButton to the widget list
Append a spacer whose width is 5% of the standard height
Create a list of KeyboardButton with the characters in 'zxcvbnm'
The 'callback' function is onClickChar
Append this list to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'back.png'
    the 'callback' function is onClickBack
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'numbers.png'
    the 'callback' function is onClickNumbers
Append the KeyboardButton to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with the character ','
The 'callback' function is onClickChar
Append this button to the widget list
Create a KeyboardButton with
    a width 500% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'space.png'
    the 'callback' function is onClickSpace
Create a KeyboardButton with the character '.'
The 'callback' function is onClickChar
Append this button to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'enter.png'
    the 'callback' function is onClickEnter
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create a KeyboardView with the row list
Add the KeyboardView to this VirtualKeyboard

#---------------------------------------------------------------
# View 1

Create an empty row list

Create a list of KeyboardButton  with the characters in 'QWERTYUIOP'
The 'callback' function is onClickChar
Create a KeyboardRow with this list
Add the KeyboardRow to the row list

Create an empty list of widgets
Add a horizontal stretch to the widget list
Create a list of KeyboardButton  with the characters in 'ASDFGHJKL'
The 'callback' function is onClickChar
Append this list to the widget list
Add a horizontal stretch to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'up.png'
    the 'callback' function is onClickShift
Append the KeyboardButton to the widgt list
Append a spacer whose width is 5% of the standard height
Create a list of KeyboardButton with the characters in 'ZXCVBNM'
The 'callback' function is onClickChar
Append this list to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'back.png'
    the 'callback' function is onClickBack
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'numbers.png'
    the 'callback' function is onClickNumbers
Append the KeyboardButton to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with the character ','
The 'callback' function is onClickChar
Append this button to the widget list
Create a KeyboardButton with
    a width 500% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'space.png'
    the 'callback' function is onClickSpace
Create a KeyboardButton with the character '.'
The 'callback' function is onClickChar
Append this button to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'enter.png'
    the 'callback' function is onClickEnter
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create a KeyboardView with the row list
Add the KeyboardView to this VirtualKeyboard

#---------------------------------------------------------------
# View 2

Create an empty row list

Create a list of KeyboardButton  with the characters in '1234567890'
The 'callback' function is onClickChar
Create a KeyboardRow with this list
Add the KeyboardRow to the row list

Create a list of KeyboardButton  with the characters in '@#£&amp;amp;_-()=%'
The 'callback' function is onClickChar
Create a KeyboardRow with this list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    the standard height
    a text value None
    an icon taken from the graphic 'symbols.png'
    the 'callback' function is onClickSymbols
Append the KeyboardButton to the widgt list
Append a spacer whose width is 5% of the standard height
Create a list of KeyboardButton with the characters in '"*'
Add "'" to the list
Add the characters in ':/!?+' to the list
The 'callback' function for everything in this list is onClickChar
Append this list to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    the standard height
    a text value None
    an icon taken from the graphic 'back.png'
    the 'callback' function is onClickBack
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'letters.png'
    the 'callback' function is onClickLetters
Append the KeyboardButton to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    a width 500% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'space.png'
    the 'callback' function is onClickSpace
Create a KeyboardButton with the character '.'
The 'callback' function is onClickChar
Append this button to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'enter.png'
    the 'callback' function is onClickEnter
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create a KeyboardView with the row list
Add the KeyboardView to this VirtualKeyboard

#---------------------------------------------------------------
# View 3

Create an empty row list

Create a list of KeyboardButton  with the characters in '$€¥¢©®µ~¿¡'
The 'callback' function is onClickChar
Create a KeyboardRow with this list
Add the KeyboardRow to the row list

Create a list of KeyboardButton  with the characters in '¼½¾[]{}&amp;lt;&amp;gt;^'
The 'callback' function is onClickChar
Create a KeyboardRow with this list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    the standard height
    a text value None
    an icon taken from the graphic 'numbers.png'
    the 'callback' function is onClickNumbers
Append the KeyboardButton to the widget list
Append a spacer whose width is 5% of the standard height
Create a list of KeyboardButton with the characters in '`;÷\∣|¬±'
The 'callback' function is onClickChar
Append this list to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    the standard height
    a text value None
    an icon taken from the graphic 'back.png'
    the 'callback' function is onClickBack
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    the standard height
    a text value None
    an icon taken from the graphic 'symbols.png'
    the 'callback' function is onClickSymbols
Append the KeyboardButton to the widgt list
Append a spacer whose width is 5% of the standard height
Create a list of KeyboardButton with the characters in '"*'
Add "'" to the list
Add the characters in ':/!?+' to the list
The 'callback' function for everything in this list is onClickChar
Append this list to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with
    the standard height
    a text value None
    an icon taken from the graphic 'back.png'
    the 'callback' function is onClickBack
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create an empty list of widgets
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'letters.png'
    the 'callback' function is onClickLetters
Append the KeyboardButton to the widget list
Append a spacer whose width is 5% of the standard height
Create a KeyboardButton with the character ','
The 'callback' function is onClickChar
Append this button to the widget list
Create a KeyboardButton with
    a width 300% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'space.png'
    the 'callback' function is onClickSpace
Create a KeyboardButton with the character '.'
The 'callback' function is onClickChar
Append this button to the widget list
Append a spacer whose width is 5% of the standard height
Create a list of KeyboardButton with the characters in '✕§¶°'
The 'callback' function is onClickChar
Append this list to the widget list
Create a KeyboardButton with
    a width 150% of the standard height
    the standard height
    a text value None
    an icon taken from the graphic 'enter.png'
    the 'callback' function is onClickEnter
Append the KeyboardButton to the widget list
Create a KeyboardRow with the widget list
Add the KeyboardRow to the row list

Create a KeyboardView with the row list
Add the KeyboardView to this VirtualKeyboard

#---------------------------------------------------------------

This is a test program:

Create a window 600 pixels wide and 350 high with background #ccc.
Compute a standard button height value, as 15% of the window height. 
Create a VirtualKeyboard, giving it the computed height
Add the VirtualKeyboard to the window
Show the window

Note: QGraphicsDropShadowEffect is part of QtWidgets, not QtCore
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here are the control key graphics:&lt;/p&gt;

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

</description>
    </item>
    <item>
      <title>Using subnets to extend ESP-Now</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Sat, 19 Apr 2025 16:03:45 +0000</pubDate>
      <link>https://dev.to/gtanyware/using-subnets-to-extend-esp-now-mpb</link>
      <guid>https://dev.to/gtanyware/using-subnets-to-extend-esp-now-mpb</guid>
      <description>&lt;p&gt;ESP-Now is a lightweight networking protocol that uses only two layers (Physical and Data Link) of the OSI model. By doing so it offers high performance and simple programming.&lt;/p&gt;

&lt;p&gt;In a typical configuration, a single hub device maintains an HTTP connection to the system controller, either as a client or a server, and uses ESP-Now to communicate directly with up to 20 other devices. Commands and queries arrive at the hub on the HTTP interface and are routed to the appropriate device. These messages are sent using a call to a library function that takes 2 arguments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The MAC address of the intended recipient&lt;/li&gt;
&lt;li&gt; The payload, which can be up to 200 bytes. (A new version increases this limit but I'll ignore that for the purposes of this article.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a small network, the system offers great performance, impressive reliability and easy programming, but it has two fundamental limitations, being the 20 devices as stated above, and the distance from the hub to the furthest device. As with all things wifi, range is an issue. It can be very hard to find a location for a wifi hub where all of the devices to be controlled can see it reliably. Here's view of a hub with 4 connected devices:&lt;/p&gt;

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

&lt;p&gt;The red hatched area is outside the wifi range of the hub, so if new devices are added here they will suffer from poor connections and will consequently be unreliable:&lt;/p&gt;

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

&lt;p&gt;One well-known solution to this is to implement a mesh, but this functionality is not offered as part of the ESP-Now spec. In any case, a mesh is often overkill, since what is usually needed is for the hub to drive more devices over a greater range, not for all the devices to talk to each other in arbitrary ways.&lt;/p&gt;

&lt;p&gt;Fortunately, there's a quite simple solution to this problem, which is to allow any device to handle devices on a subnet of its own. The device count and the range of the network both immediately increase dramatically, albeit with a performance hit, but one that is likely to be insignificant for most small control systems:&lt;/p&gt;

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

&lt;p&gt;The way it works is like this. Every device in the system is described by a packet of information, typically a JSON structure, with fields such as the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"name" - the unique name of the device in the system
&lt;/li&gt;
&lt;li&gt;"mac" - the MAC address of the device
&lt;/li&gt;
&lt;li&gt;"channel" - the wifi channel used
&lt;/li&gt;
&lt;li&gt;"led" - the pin that drives the device LED
&lt;/li&gt;
&lt;li&gt;"led-invert" - a Boolean that governs the ON and OFF polarity
&lt;/li&gt;
&lt;li&gt;"relay" - the pin that drives the device's primary relay
&lt;/li&gt;
&lt;li&gt;"relay-invert" - a Boolean that governs the ON and OFF polarity
&lt;/li&gt;
&lt;li&gt;... (etc for other hardware and software features of this device)
&lt;/li&gt;
&lt;li&gt;"path" - the path to this device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each device has a JSON file containing its own information, and the central controller holds a file containing all the device structures, indexed by the device names.&lt;/p&gt;

&lt;p&gt;The last item in the list is the key to the extended networking. For devices close to the hub, nothing is needed here as messages are sent using the MAC address of the device and the payload. But by placing the MAC address of an intermediate device into the field, that device then becomes the parent of the target device (the one the message is intended for). The message is now sent to the parent (the "path" as above), to be forwarded to our required target device. We prefix the payload with the MAC address of the actual target device and structure it so as to make it easy to decode.&lt;/p&gt;

&lt;p&gt;As an example, let's suppose we want to send a command to turn on the device relay. Let's suppose the MAC address of the device is &lt;code&gt;aa:aa:aa:aa:aa:aa&lt;/code&gt; and the payload to turn on the relay is the single word &lt;code&gt;ON&lt;/code&gt;. If the network is small and the device is close to the hub we can just send it as is, using a function call such as&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but if the network is full or the device is physically remote we need to relay the message through another device already in the system, one that is closer to our new device. Let's say the MAC address of this parent device is &lt;code&gt;bb:bb:bb:bb:bb:bb&lt;/code&gt;. So we send a message to that device, and we adjust the payload to be&lt;/p&gt;

&lt;p&gt;&lt;code&gt;!aa:aa:aa:aa:aa:aa,ON&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The function call then becomes (micropython version)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exclamation mark is there to signal that the payload is to be routed to the device whose MAC address is given, and the comma is just a separator. Needless to say, this strategy does not allow payloads to begin with an exclamation mark, so you might wish to use a different marker.&lt;/p&gt;

&lt;p&gt;The code to handle this new message type is remarkably simple. You have to check for the marker, then extract the embedded MAC address and use it to construct a new message to send to the target device. The payload is everything that follows the comma - in this case the original &lt;code&gt;ON&lt;/code&gt; - and the reply from the target is simply passed back to your caller. You can see this done in the &lt;code&gt;receive&lt;/code&gt; function below.&lt;/p&gt;

&lt;p&gt;This technique adds no extra devices to the list of "peers" that are held by any given device, and it can be extended almost indefinitely, by concatenating MAC addresses in the "path" field, such as&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bb:bb:bb:bb:bb:bb,cc:cc:cc:cc:cc:cc&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;which causes the message to be routed through 2 intermediate devices.&lt;/p&gt;

&lt;p&gt;I reproduce below the ESP comms class from a Micropython implementation of the above. It relies on a &lt;code&gt;config&lt;/code&gt; parameter being supplied during initialization, to provide access to other functions in the system, but mostly it just deals with sending and receiving ESP-Now messages. You will notice that the system holds MAC addresses as human-readable strings, whereas ESP-Now requires formatted binaries, so there's a degree of back-and-forth between the two formats.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;binascii&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;hexlify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;unhexlify&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;espnow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ESPNow&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;E&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ESPComms&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

    &lt;span class="c1"&gt;# Default initializer
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;
        &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setESPComms&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;active&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ESP-Now initialised&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Check if a peer is already known
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;checkPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;peer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;peers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;add_peer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Send a message to a given MAC address
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;espmsg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;unhexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Send &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;espmsg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;... to &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;espmsg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Result: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
                &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                        &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;irecv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Received response: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                            &lt;span class="k"&gt;break&lt;/span&gt;
                    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Response timeout&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Fail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle received messages as they arrive
&lt;/span&gt;    &lt;span class="c1"&gt;# Either deal with them or pass them on
&lt;/span&gt;    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Starting ESPNow receiver on channel&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChannel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;hexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Message from &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;comma&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
&lt;span class="c1"&gt;#                    print('Forward to',slave,', msg:',msg)
&lt;/span&gt;                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getHandler&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getRSS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="c1"&gt;#                print('Response',response)
&lt;/span&gt;                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkPeer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickWatchdog&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Return the RSS (signal strength) of the received message
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getRSS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;unhexlify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;E&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;peers_table&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;peer&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Chained wi-fi networking for control systems</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Sat, 24 Aug 2024 20:59:52 +0000</pubDate>
      <link>https://dev.to/gtanyware/chained-wi-fi-networking-for-control-systems-5hf2</link>
      <guid>https://dev.to/gtanyware/chained-wi-fi-networking-for-control-systems-5hf2</guid>
      <description>&lt;p&gt;This describes a networking strategy for controlling simple wifi devices distributed over a large physical area, without the need for repeaters. The code is Micropython and the system was designed for a network of 4MB ESP8266 devices. It was originally developed for a home heating control system but is equally well suited to handling the needs of industrial heating, irrigation or similar systems, where several devices need to be controlled centrally.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Most small-scale WiFi networking systems adopt a “star” topology:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidi7nrks4fkcv677i3fy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidi7nrks4fkcv677i3fy.png" alt="A star topology" width="795" height="742"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;with all the control and sensor nodes connected to a single hub; often a standard internet router. This will be located close to where the service provider's cable enters the property, often in a remote corner. Devices such as the ESP8266 have small antennae and operate poorly over anything more than quite a short distance. This shows up as delays in responding, frequent resets and even crashes, but these may not become noticeable until after the installation has been completed and signed off. The system would then require a time-consuming rework.&lt;/p&gt;

&lt;p&gt;This problem can be overcome by the use of extra routers or network extenders, to form what is known as a 'mesh' system, but these may be visually intrusive and they add extra cost and complexity to the system.&lt;/p&gt;

&lt;p&gt;In the case of the home heating system, there was an additional problem, that if the internet connection went down the local NAT network would also fail and the system controller could not access the relays and other devices. To avoid this, the controller was fitted with a second wifi interface to provide a private LAN, and the entire control network was placed on that. In the event of a WAN failure the system carries on happily by itself.&lt;/p&gt;

&lt;p&gt;This works fine up to a point, but small computers like Raspberry/Orange pi can only support a limited number of wifi devices, and it didn't deal with devices being too far away to work reliably unless mesh routers were added.&lt;/p&gt;

&lt;p&gt;A secondary issue concerned updates to the firmware inside the devices. When updates were required it was necessary to manually visit each device in turn and perform an update. &lt;/p&gt;

&lt;p&gt;This article describes a simple, low-cost approach where the controlled devices themselves take care of all the extended networking. It can handle a large number of devices and can be deployed using very modest hardware such as an ESP8266. The system connects to its controller through a single wifi address and can be completely configured and tested before delivery for installation. Software updates propagate silently and automatically through the system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Push or pull
&lt;/h2&gt;

&lt;p&gt;In a "push" scenario, a central controller determines the states of each of the controlled devices and "pushes" messages to each of them via REST, MQTT or some other comms mechanism. This is fine while it's working but can cause major timing issues when devices go offline and the controller doesn't get the responses it expected.&lt;/p&gt;

&lt;p&gt;This system adopts a "pull" strategy. As before, the controller decides the states of the devices, but it's then up to each of them to poll the controller whenever it wishes and to act on the response it receives back. After several months' testing there have been no signs of timing issues. Occasionally a device will lose contact, suffer a memory overflow or some other problem that causes the local watchdog to force a reset, but this has little to no effect on the behaviour of the overall system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outline of the strategy
&lt;/h2&gt;

&lt;p&gt;This strategy employs some of the networked devices as message relays handing data to and from other nearby devices. The network then looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszeml2olyxzlz2hixdki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fszeml2olyxzlz2hixdki.png" alt="A chain topology" width="796" height="811"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;The strategy centres on a packet of information constructed by the system controller. Encoded as JSON, this contains a section for each of the networked devices, keyed by the name of the device. It also contains the current timestamp and the version number of the system firmware. Let's call this packet the “map”. Here’s an example of a simple system with two relay nodes:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{“Room1“: {“relay“: “off“}, “Room2“: {“relay“: “on“}, “ts“: “1719094849“, “v“: “90“}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The content of each device section in the map depends on the device. A relay has its state, on or off. A thermometer usually requires no information since its job is to return data, not consume it.&lt;/p&gt;

&lt;p&gt;One device is designated as the "entry point" to the network. It connects to the access point (wifi hotspot) published by the hub, which either is the system controller itself or an interface device that connects to it. The entry device knows nothing of its special role; it behaves just like every other node. The reason for having a single entry node is so the hub doesn’t have to support more than one wifi client or combine messages coming from multiple sources. Once connected, the entry device polls the hub at regular intervals such as every 2 seconds, and the hub responds by sending it the map. The device looks in the map for its own name and extracts the attached data to perform whatever tasks are needed. For a relay this will be to turn on or off. For a thermometer, no actions are needed at this stage.&lt;/p&gt;

&lt;p&gt;When a device starts up it constructs an empty 'return packet', which it maintains for the lifetime of the running program. The device adds to this packet the timestamp contained in the last outgoing map. For a relay, that's all there is; a thermometer also adds the current temperature. The return packet looks like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{“Room1“: {“ts”: “1719094849”}}&lt;/code&gt;&lt;br&gt;
or&lt;br&gt;
&lt;code&gt;{“Thermo1“: {“temp”: “25.3”, “ts”: “1719094849”}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When a device polls its parent it supplies its current return packet, which includes those of its own child devices as explained below. If polling stops or for any other reason the timestamps don't get updated, the controller can tell there's something wrong in the system by checking the ages of the returning timestamps.&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding more devices
&lt;/h2&gt;

&lt;p&gt;Devices like the ESP8266 and ESP32 can operate simultaneously in Station and in Access Point mode. That is, they can connect to a wifi hotspot while at the same time publishing one of their own. These local hotspots have limited functionality but can usually support up to 4 connections, which allows us to expand the system by connecting devices to each other in a chain rather than all to a central hub. In this system, each device sets up a hotspot with an SSID based on its own MAC address and a network IP address that's different to the one it’s connected to.&lt;/p&gt;

&lt;p&gt;When a device receives a polling request from a child device it extracts the contained return packet and adds it to its own return packet, for onward transmission to its own parent on the next poll. For example, if Room2 is a child of Room1, then following a poll of Room1 by Room2 the return packet from Room1 will expand to look like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{“Room1“: {“ts”: “1719094849”}, “Room2“: {“ts”: “1719093995”}}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As we move back along the chain towards the entry point device, the return packet grows to include all the nodes before finally being delivered to the hub on the next poll.&lt;/p&gt;

&lt;p&gt;After dealing with the return packet, the device responds to the poll request by returning the current outgoing map (that came from the controller) to the child device that polled it. In this way, the outgoing map propagates to all devices. &lt;/p&gt;

&lt;p&gt;When devices are added to the system they do not connect directly to the system hub as they would in a star topology. Instead, they connect to the nearest already configured device that has remaining capacity for another connection. Since each device publishes a hotspot, it's easy to check on a smartphone which one has the strongest signal, to guarantee reliable system performance.&lt;/p&gt;

&lt;p&gt;The behaviour of every device is identical so it’s very easy to configure the system; the device name and its parent SSID are the only items that differ from one device to another. And so the system grows, with devices being connected to any other device that can offer a good wifi signal.&lt;/p&gt;

&lt;p&gt;The system also has the welcome ability to operate without the need to manually track IP addresses. Devices are identified by unique names and SSIDs, where the latter are created automatically from the device MAC address. Each device chooses for its own hotspot an IP address range that's different to the one used by its parent. In the prototype systems, these ranges alternate between &lt;code&gt;172.24.100.x&lt;/code&gt; and &lt;code&gt;172.24.101.x&lt;/code&gt;. Each device uses one or the other.&lt;/p&gt;

&lt;p&gt;It will often be useful to create a simple spreadsheet to record the essential details of the system, as a guide while installing and when doing maintenance. Here's the table for a home heating system:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwr1cc5izzsc1qyy73k8p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwr1cc5izzsc1qyy73k8p.png" alt="System config" width="544" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, &lt;code&gt;Request&lt;/code&gt; is the entry device. (Here it's the relay that controls whether the boiler - or in this case heat pump - should run. It's ON when any radiator is ON.)&lt;/p&gt;

&lt;p&gt;It's quite simple to check signal strengths at a given location using any computer or smartphone, to decide which devices can connect to which parents. When your computer connects to a device hotspot, the gateway address (whose 4th octet is &lt;code&gt;1&lt;/code&gt;, e.g. &lt;code&gt;172.24.100.1&lt;/code&gt;) runs a small HTTP server that returns basic information about the device; its name, SSID, parent and how long it's been up and running.&lt;/p&gt;
&lt;h2&gt;
  
  
  Over The Air (OTA) updates
&lt;/h2&gt;

&lt;p&gt;The version number handed out by the hub is used to keep the system firmware up to date. Each device keeps a note of its current version. When a message packet (map) arrives with a higher version number, the device requests a list of files from its parent, and then requests each of the files listed, one by one. Once it has finished updating it saves the new version number.&lt;/p&gt;

&lt;p&gt;While updating is taking place, normal operation is suspended. Clients of a device will not get responses to their polling and will eventually time out and reset themselves. Once updating has finished, a device will restart normal operation and when it receives the map from its parent will pass it on. Since this now contains an updated version number, each of its clients will start its own update. So the latest version ripples through the system and everyone ends up with the same code.&lt;/p&gt;

&lt;p&gt;If the system is a mix of different device types, the code for all of them must be contained in each. So a relay contains thermometer code and vice versa.&lt;/p&gt;
&lt;h2&gt;
  
  
  Upsides
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Ability to handle a large number of devices spread over a large physical area&lt;/li&gt;
&lt;li&gt;No wifi black spots&lt;/li&gt;
&lt;li&gt;All IP addresses are inferred by the devices themselves&lt;/li&gt;
&lt;li&gt;Very low cost&lt;/li&gt;
&lt;li&gt;The messaging protocol is flexible. It can be extended to add more complex information exchanges, without any compatibility issues.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Downsides
&lt;/h2&gt;

&lt;p&gt;The main downside of this system is the time it takes for messages to propagate through the network. It's not suitable for use where a rapid response is needed, as response times are measured in seconds or even tens of seconds. There are however many applications for which this is not a problem, such as home or commercial heating systems and large-scale irrigation.&lt;/p&gt;
&lt;h1&gt;
  
  
  The code
&lt;/h1&gt;

&lt;p&gt;The device on which this code runs, typically an ESP8266, must have at least 4M bytes of flash memory in order to support the full set of Micropython modules required - most importantly, asynchronous functions. If you are using the ESP-01 be sure to get the 4MB version, not the older 1MB.&lt;/p&gt;

&lt;p&gt;Since this is an ongoing project, any code I present here will be out of date very rapidly. So instead, here is a link to the GitHub repository, which includes all the source files referred to in the following paragraphs:&lt;br&gt;
(&lt;a href="https://github.com/easycoder/rbr/blob/main/roombyroom/Controller/home/orangepi/firmware/XR" rel="noopener noreferrer"&gt;https://github.com/easycoder/rbr/blob/main/roombyroom/Controller/home/orangepi/firmware/XR&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;As with all Micropython projects there is a boot file and a main program. First, &lt;code&gt;boot.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from machine import Pin

led = Pin(2, Pin.OUT)
led.on()

import esp
esp.osdebug(None)

import gc
gc.collect()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty standard. Next, &lt;code&gt;main.py&lt;/code&gt;, which will always run automatically following &lt;code&gt;boot.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

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

def fileExists(filename):
    try:
        os.stat(filename)
        return True
    except OSError:
        return False

if fileExists('config.json'):
    if fileExists('update'):
        import updater
        f = open('update','r')
        value=f.read()
        f.close()
        updater.run(value)
    else:
        import configured
        configured.run()
else:
    import unconfigured
    unconfigured.run()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The operating mode is determined by a couple of files kept in the device flash. The file &lt;code&gt;config.json&lt;/code&gt; contains information specific to this device. If this file does not exist the device is unconfigured; if it exists then the device is considered to be configured and ready to run. In the latter case, the file &lt;code&gt;update&lt;/code&gt; is checked; if this one also exists the device enters update mode, which I’ll cover last.&lt;/p&gt;

&lt;p&gt;Note that the main function modules are only called in if they are needed for the chosen mode. This avoids having to load everything up front when much of it will remain unused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unconfigured mode
&lt;/h2&gt;

&lt;p&gt;This mode is handled by &lt;code&gt;unconfigured.py&lt;/code&gt;. The mode is optional; if you provide a &lt;code&gt;config.json&lt;/code&gt; file manually the device will never run unconfigured. It exists so that a batch of devices can be flashed with identical firmware and then each one configured individually using a browser.&lt;/p&gt;

&lt;p&gt;The job of the module is to run a web server that offers a configuration form for the user to supply configuration data. At the top are a couple of HTML pages, then some utility functions and finally the main code.&lt;/p&gt;

&lt;p&gt;The key feature is the use of asynchronous functions to permit multitasking. The program runs 2 jobs; one waits for a client to connect and the other flashes the LED. Since neither of these can be allowed to block, asynchronous code is needed. The first thing is to create the two tasks, using asyncio, then enter an endless loop while they perform their duties.&lt;/p&gt;

&lt;p&gt;The code sets up a webserver whose SSID is of the form &lt;code&gt;RBR-xr-xxxxxx&lt;/code&gt;, where &lt;code&gt;RBR-xr-&lt;/code&gt; is a product descriptor and &lt;code&gt;xxxxxx&lt;/code&gt; are the last 6 digits of the device MAC address. The IP address of the server is &lt;code&gt;192.168.66.1&lt;/code&gt; (or anything else you prefer). Connecting to it at &lt;code&gt;http://192.168.66.1/config&lt;/code&gt; brings up the following screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wu45y1l8ttka1qniug8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7wu45y1l8ttka1qniug8.png" alt="Config screen" width="655" height="497"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The fields are as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relay Name&lt;/strong&gt;: Any name that uniquely identifies this relay in the network. Spaces are not allowed.&lt;br&gt;
&lt;strong&gt;Host SSID&lt;/strong&gt;: The SSID of the device this one should connect to.&lt;br&gt;
&lt;strong&gt;Host Password&lt;/strong&gt;: The Wifi password of the host.&lt;br&gt;
&lt;strong&gt;My Password&lt;/strong&gt;: The Wifi password that will allow access to the server on this device once configured.&lt;/p&gt;

&lt;p&gt;When &lt;strong&gt;Setup&lt;/strong&gt; is clicked the fields are combined into a JSON structure and saved into the device flash area as &lt;code&gt;config.json&lt;/code&gt;. Then the device resets itself. If all is well it should restart in Configured mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configured mode
&lt;/h2&gt;

&lt;p&gt;This mode is handled by &lt;code&gt;configured.py&lt;/code&gt;. As with unconfigured mode, the code runs concurrent tasks using the asyncio library. One of these tasks is the main program; the other is a watchdog that monitors activity to ensure polling runs as expected. If for any reason it stops (such as the device’s host going offline), the watchdog forces a reset after a decent interval. This avoids un-needed resets when momentary failures occur.&lt;/p&gt;

&lt;p&gt;Most of the functions required by the code - other than standard libraries - are kept in two local modules; &lt;code&gt;functions.py&lt;/code&gt; and &lt;code&gt;hardware.py&lt;/code&gt;. These will be described later.&lt;/p&gt;

&lt;p&gt;The main progam starts by doing some initialization. Two special dictionary objects - &lt;code&gt;outgoingMap&lt;/code&gt; and &lt;code&gt;incomingMap&lt;/code&gt; - hold the data passing around the network. Both of these, once created, last for as long as the program is running.&lt;/p&gt;

&lt;p&gt;The code also sets up a local access point (hotspot) with an SSID that is similar but not identical to the one used in configured mode. In this example it will be &lt;code&gt;RBR-XR-xxxxxx&lt;/code&gt;, where &lt;code&gt;xxxxxx&lt;/code&gt; is as before the last 6 digits of the MAC address. Since two of the characters in the product descriptor are now uppercase, the device is easy to find in a list of local access points.&lt;/p&gt;

&lt;p&gt;In the main loop, the code polls its parent device at regular intervals, sending it the &lt;code&gt;incomingMap&lt;/code&gt;. The reply from the parent - the &lt;code&gt;outgoingMap&lt;/code&gt; - is parsed to find if the local relay should be on or off. The version number in &lt;code&gt;outgoingMap&lt;/code&gt; is compared with that currently saved, and if there’s been an advance the device creates an update file and forces a reset.&lt;/p&gt;

&lt;p&gt;When a client device polls this one, the incoming request is parsed to find the command and its data (if any). The format of the request is one of&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://(my ip address)/command&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://(my ip address)/command?data&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The commands recognised are as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;reboot&lt;/code&gt;&lt;/strong&gt; forces a restart of the code.&lt;br&gt;
&lt;strong&gt;&lt;code&gt;reset&lt;/code&gt;&lt;/strong&gt; erases the configuration file then forces a reboot, causing the device to restart in unconfigured mode.&lt;br&gt;
&lt;strong&gt;&lt;code&gt;getFile?{filename}&lt;/code&gt;&lt;/strong&gt; is a request for the contents of the named file to be returned. This is used by the updater.&lt;br&gt;
&lt;strong&gt;&lt;code&gt;poll?data={incomingMap}&lt;/code&gt;&lt;/strong&gt; Poll the parent of this device, supplying the incoming map (or the part of it known to this device). The outgoing map is returned to the caller.&lt;/p&gt;

&lt;p&gt;Any other command will return a line of general information about the device and its status.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 'functions' modules
&lt;/h2&gt;

&lt;p&gt;A set of helper functions is kept in &lt;code&gt;functions.py&lt;/code&gt; and &lt;code&gt;hardware.py&lt;/code&gt;. These are provided as two files to help avoid running out of memory during updates.&lt;/p&gt;

&lt;p&gt;A single third-party library, &lt;code&gt;uaiohttpclient&lt;/code&gt;, which handles asynchronous HTTP requests, is placed in the lib directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update mode
&lt;/h2&gt;

&lt;p&gt;This mode is handled by &lt;code&gt;updater.py&lt;/code&gt;. The updater runs its main task and a watchdog. The main task starts by requesting from its parent device a list of all the files that comprise the code set. This list is saved as &lt;code&gt;files.txt&lt;/code&gt;, then each of the files named is requested. When all have successfully been dealt with, the new version number is saved.&lt;/p&gt;

&lt;p&gt;The mechanism for processing each file has to be robust and able to recover from errors. The procedure is as follows:&lt;/p&gt;

&lt;p&gt;1 Download the requested file and save it as a temporary file.&lt;br&gt;
 1 Read the saved file and check it’s the same as the data downloaded. If not, force a reset.&lt;br&gt;
 1 If there is no existing file with the name requested, rename the temporary file and return.&lt;br&gt;
 1 If there is an existing file, read it and compare it with the downloaded file. If they are the same, no update is needed so delete the temporary file and return.&lt;br&gt;
 1 If they are different, delete the current (old) version, rename the temporary file and return.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future work
&lt;/h2&gt;

&lt;p&gt;The system is agnostic about the environment in which it operates. In the current prototypes the hub is a small computer such as a Raspberry or Orange pi, where control is done by a PHP module on an Apache web server. A useful next step would be to create an integration for Home Assistant. A hub module - probably itself an ESP8266 - would handle a complete chain of devices and present them to HA as a single item via MQTT. A suitable API would allow any named device in the chain to be addressed and controlled independently via the hub. &lt;/p&gt;

</description>
      <category>esp8266</category>
      <category>control</category>
      <category>networking</category>
      <category>micropython</category>
    </item>
    <item>
      <title>Dynamic arrays in C++</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Thu, 15 Feb 2024 21:43:58 +0000</pubDate>
      <link>https://dev.to/gtanyware/dynamic-arrays-in-c-24oj</link>
      <guid>https://dev.to/gtanyware/dynamic-arrays-in-c-24oj</guid>
      <description>&lt;p&gt;I am an engineer turned self-taught programmer, with no formal computer science training. (That's by way of an excuse for what follows.) For about 10 years up to about 1995 I used C but never really got fully to grips with it. Then I moved on, first to Java then later JavaScript and Python. Recently however I started some ESP8266 projects. I tried Micropython but found it lacking in “bare metal” capabilities and it takes up too much space in a 512KB device.&lt;/p&gt;

&lt;p&gt;So it was time to return to C - or more specifically C++. It came as something of a shock to rediscover how complex it is, how easy it is to make simple programming errors that cause programs to crash, and how lacking C++ is in features we take for granted in other languages. One of these is dynamic arrays, which allow elements to be freely added without the need to consider memory management. &lt;/p&gt;

&lt;p&gt;There are of course libraries out there that could serve my needs, but I usually find it's a better use of my time (and more enjoyable) to spend a couple of days writing code rather than hunting down potential candidate libraries, getting to understand them and often as not rejecting them as unsuitable. So if the code that follows is similar to something already available then it's a complete coincidence. &lt;/p&gt;

&lt;p&gt;My dynamic arrays start with a linked list class. This has a no-argument constructor and 3 main methods:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void add(element)&lt;/code&gt; adds the supplied element to the list. It throws an exception if the request fails&lt;/p&gt;

&lt;p&gt;&lt;code&gt;int getSize()&lt;/code&gt; returns the number of elements in the list&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void* get(index)&lt;/code&gt; returns the element at the specified position&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void clear()&lt;/code&gt; clears the list&lt;/p&gt;

&lt;p&gt;The list is doubly-linked (forwards and backwards) and needs no malloc() commands as the elements added are all pointers to the original items. All elements are stored as &lt;code&gt;void*&lt;/code&gt; pointers so the list can handle any kind of data. An inner &lt;code&gt;LinkedListElement&lt;/code&gt; class handles each element.&lt;/p&gt;

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#ifndef LINKED_LIST
#define LINKED_LIST

#include &amp;lt;stdio.h&amp;gt;

/*
    This is a doubly linked list element able to store any kind of data.
    The content is immutable within this class.
*/
class LinkedListElement {

    private:

       const void* content;
       LinkedListElement* previous = nullptr;
       LinkedListElement* next = nullptr;

    public:

       // Get the content of this element
       void* get() {
           return (void*)content;
       }

       // Get the previous element pointer
       LinkedListElement* getPrevious() {
           return previous;
       }


       // Set the previous element pointer
       void setPrevious(LinkedListElement* data) {
           previous = data;
       }


       // Get the next element pointer
       LinkedListElement* getNext() {
           return next;
       }


       // Set the next element pointer
       void setNext(LinkedListElement* data) {
           next = data;
       }


        // Constructor
        LinkedListElement(const void* data) {
            content = data;
        }

        // Destructor
        ~LinkedListElement() {}
};

class LinkedList {

    private:
        int size = 0;                          // the number of items
        LinkedListElement* head = nullptr;
        LinkedListElement* tail = nullptr;

    public:

        ///////////////////////////////////////////////////////////////////////
        // Get the size (the number of elements in the list)
        int getSize() {
            return size;
        };

        ///////////////////////////////////////////////////////////////////////
        void add(const void* data) {
            LinkedListElement* element = new LinkedListElement(data);
            if (size == 0) {
                head = element;
                tail = head;
            } else {
                tail-&amp;gt;setNext(element);
                element-&amp;gt;setPrevious(tail);
                tail = element;
            }
            ++size;
        }

        ///////////////////////////////////////////////////////////////////////
        void* get(int index) {
            if (index &amp;lt; size) {
                LinkedListElement* element = head;
                while (index &amp;gt; 0) {
                    element = element-&amp;gt;getNext();
                    --index;
                }
                return element-&amp;gt;get();
            }
            return nullptr;
        }

        ///////////////////////////////////////////////////////////////////////
        // Clear the list. Remove everything except the element data.
        void clear() {
            LinkedListElement* walker = head;
            if (walker != nullptr) {
                while (walker-&amp;gt;getNext() != nullptr) {
                    LinkedListElement* next = walker-&amp;gt;getNext();
                    delete walker;
                    walker = next;
                }
                delete walker;
            }
            size = 0;
        }

        // Default constructor
        LinkedList() {}

        // Destructor
        ~LinkedList() {
            clear();
         }
};

#endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linked lists are simple and effective, but to retrieve an element means “walking” the list, which has a performance overhead that gets significant for large lists. By contrast, an array provides instant access to its elements but requires knowledge of its size to allocate memory for its list of pointers before any elements can be added. To get around both these limitations we need to combine an array with a linked list. &lt;/p&gt;

&lt;p&gt;The next class is &lt;code&gt;StringArray&lt;/code&gt;, which is an array of character strings referenced by &lt;code&gt;char*&lt;/code&gt; pointers. As well as this array it also contains a &lt;code&gt;LinkedList&lt;/code&gt;. The significant methods of the class are&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void add(element)&lt;/code&gt; adds the supplied element to the &lt;code&gt;StringArray&lt;/code&gt;. It will be added to the internal linked list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;int getSize()&lt;/code&gt; returns the number of elements held by this class instance. This is the sum of those in the array and those in the linked list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;char* get(index)&lt;/code&gt; returns the element at the specified position. The element might be taken from either the array or the linked list, depending on the index and how many elements there are in each part.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;void flatten()&lt;/code&gt; “flattens” the array. It creates a new array big enough to hold the contents of the existing array plus those in the list, copies all of their pointers to the new array then clears the list and frees the old array. Note that the data itself is untouched and unaffected. This method is “idempotent”; that is, it can be called as often as desired. If the list is empty it does nothing.&lt;/p&gt;

&lt;p&gt;Here's the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#ifndef STRINGARRAY
#define STRINGARRAY

/*
    StringArray is a memory-efficient class for managing arrays of strings.
    A typical usage is to break a piece of text into lines and make them
    available by line number.
*/
class StringArray {

    private:
        int size = 0;                       // the number of items
        char** array = (char**)malloc(1);   // the array of items
        LinkedList* list;                   // a list to hold new data items

    public:

        ///////////////////////////////////////////////////////////////////////
        // Get the size (the number of elements in the array)
        int getSize() {
            return this-&amp;gt;size + list-&amp;gt;getSize();
        };

        ///////////////////////////////////////////////////////////////////////
        // Get a specified item.
        // If the index is greater than the array size, return the item from the list.
        char* get(int n) {
            if (n &amp;lt; size) {
                return array[n];
            }
            else if (n &amp;lt; size + list-&amp;gt;getSize()) {
                return (char*)list-&amp;gt;get(n - size);
            }
            return nullptr;
        };

        ///////////////////////////////////////////////////////////////////////
        // Add an item. This goes into the linked list.
        void add(const char* item) {
            list-&amp;gt;add(item);
        }

        ///////////////////////////////////////////////////////////////////////
        // Flatten this item by creating a single array to hold all the data.
        void flatten() {
            char** oldArray = array;
            int oldSize = size;
            // Create a new array big enough for the old array and the list
            int total = oldSize + list-&amp;gt;getSize();
            if (total &amp;gt; 0) {
                array = (char**)malloc(sizeof(StringArray*) * (total));
                // Copy the old array to the new
                size = 0;
                while (size &amp;lt; oldSize) {
                    array[size] = oldArray[size];
                    size++;
                }
                free(oldArray);
                // Copy the list to the new array
                int n = 0;
                while (n &amp;lt; list-&amp;gt;getSize()) {
                    array[size++] = (char*)list-&amp;gt;get(n++);
                }
                list-&amp;gt;clear();
            }
        }

        ///////////////////////////////////////////////////////////////////////
        // Default constructor
        StringArray() {
            list = new LinkedList();
        }

        ///////////////////////////////////////////////////////////////////////
        // Destructor
        ~StringArray() {
            free(array);
            delete list;
         }
};

#endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not shown here are useful methods such as &lt;code&gt;indexOf(char* item)&lt;/code&gt;, which searches for and returns the index of an item in the array. My own implementation also has a &lt;code&gt;parse(char* data)&lt;/code&gt; method that takes a single string containing a number of items separated by a common delimiter (such as lines of a text document or a CSV file), breaks them up into individual strings and adds them all to the array.&lt;/p&gt;

&lt;p&gt;The final class is &lt;code&gt;ObjectArray&lt;/code&gt;, which is an array of arbitrary objects referenced by &lt;code&gt;void*&lt;/code&gt; pointers. This class is useful for holding data of varying types. It differs only very slightly from the previous &lt;code&gt;StringArray&lt;/code&gt;. Here’s the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#ifndef OBJECTARRAY
#define OBJECTARRAY

/*
    ObjectArray is a memory-efficient class for managing arrays of arbitrary objects.
*/
class ObjectArray {

    private:
        int size = 0;                         // the number of items
        void** array = (void**)malloc(1);     // the array of items
        LinkedList* list;                     // a list to hold new data items

    public:

        ///////////////////////////////////////////////////////////////////////
        // Get the size (the number of elements in the array)
        int getSize() {
            return this-&amp;gt;size + list-&amp;gt;getSize();
        };

        ///////////////////////////////////////////////////////////////////////
        // Get a specified item.
        // If the index is greater than the array size, return the item from the list.
        void* get(int n) {
            if (n &amp;lt; size) {
                return array[n];
            }
            else if (n &amp;lt; size + list-&amp;gt;getSize()) {
                return (void*)list-&amp;gt;get(n - size);
            }
            return nullptr;
        };

        ///////////////////////////////////////////////////////////////////////
        // Add an item. This goes into the linked list.
        void add(const void* item) {
            list-&amp;gt;add(item);
        }

        ///////////////////////////////////////////////////////////////////////
        // Flatten this item by creating a single array to hold all the data.
        void flatten() {
            void** oldArray = array;
            int oldSize = size;
            // Create a new array big enough for the old array and the list
            int total = oldSize + list-&amp;gt;getSize();
            if (total &amp;gt; 0) {
                array = (void**)malloc(sizeof(ObjectArray*) * (total));
                // Copy the old array to the new
                size = 0;
                while (size &amp;lt; oldSize) {
                    array[size] = oldArray[size];
                    size++;
                }
                free(oldArray);
                // Copy the list to the new array
                int n = 0;
                while (n &amp;lt; list-&amp;gt;getSize()) {
                    array[size++] = list-&amp;gt;get(n++);
                }
                list-&amp;gt;clear();
            }
        }

        ///////////////////////////////////////////////////////////////////////
        // Default constructor
        ObjectArray() {
            list = new LinkedList();
        }

        ///////////////////////////////////////////////////////////////////////
        // Destructor
        ~ObjectArray() {
            free(array);
            delete list;
         }
};

#endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a simple test program to illustrate its usage. (Yes, I know the classes defined here are all animals and could share a common superclass, but that’s not the point of the exercise.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;string.h&amp;gt;
#include "linkedlist.cpp"
#include "objectarray.cpp"

class Cat {
  private:
    const char* name;
  public:
    char* getName() { return (char*)this-&amp;gt;name; }
    Cat(const char* name) { this-&amp;gt;name = name; }
};

class Dog {
  private:
    const char* name;
  public:
    char* getName() { return (char*)this-&amp;gt;name; }
    Dog(const char* name) { this-&amp;gt;name = name; }
};

class Rabbit {
  private:
    const char* name;
  public:
    char* getName() { return (char*)this-&amp;gt;name; }
    Rabbit(const char* name) { this-&amp;gt;name = name; }
};

class Canary {
  private:
    const char* name;
  public:
    char* getName() { return (char*)this-&amp;gt;name; }
    Canary(const char* name) { this-&amp;gt;name = name; }
};

// Main program
int main(void) {

  ObjectArray* pets = new ObjectArray();
  pets-&amp;gt;add(new Cat("Garfield"));
  pets-&amp;gt;add(new Dog("Scooby"));
  pets-&amp;gt;add(new Rabbit("Bugs"));
  pets-&amp;gt;add(new Canary("Tweety Pie"));

  pets-&amp;gt;flatten();    // optional

  printf("I have a cat called %s,\n", ((Cat*)pets-&amp;gt;get(0))-&amp;gt;getName());
  printf("a dog called %s,\n", ((Dog*)pets-&amp;gt;get(1))-&amp;gt;getName());
  printf("a rabbit called %s,\n", ((Rabbit*)pets-&amp;gt;get(2))-&amp;gt;getName());
  printf("and a canary called %s.\n", ((Canary*)pets-&amp;gt;get(3))-&amp;gt;getName());
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output when run is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I have a cat called Garfield,
a dog called Scooby,
a rabbit called Bugs,
and a canary called Tweety Pie.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Having written these simple classes I am amazed by how much the rest of my code now depends on them. I hope others may find them just as useful. &lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@bamin?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Pierre Bamin&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/color-pencil-lot-dnGgAIRNnsE?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>It's in there somewhere</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Tue, 26 Dec 2023 15:12:47 +0000</pubDate>
      <link>https://dev.to/gtanyware/its-in-there-somewhere-3gm2</link>
      <guid>https://dev.to/gtanyware/its-in-there-somewhere-3gm2</guid>
      <description>&lt;p&gt;My partner takes a lot of photos. Since smartphones replaced compact cameras for taking photos, everything has been shovelled into Google Photos, but previously she would take a pocket camera everywhere with her. This left her with thousands of images distributed in a messy filing system comprising hundreds of folders and no real organization. She owned several computers over that period, and each time one packed up, most of its hard drive got copied to the (much larger one) on the replacement computer. So duplication is rife. Everything is in there somewhere, but it's all hard to find and it's taking up way too much space. So what should we do about it?&lt;/p&gt;

&lt;p&gt;There are two ways to approach the problem. One is to find a tool that will do the job; the other is to write one. The problem with the first of these is that it will take time to find such a tool, largely because I can't figure out a suitable question to ask Google. Having found a tool I then have to learn how to use it. Unless it's top-drawer commercial software its documentation - if there is any - will likely be cryptic if not incomprehensible, so this part could take hours. Then there's finally the good chance that it won't actually do the job after all, so the whole exercise is wasted. And if the original search came up with several products, everything has to be repeated for each one.&lt;/p&gt;

&lt;p&gt;So that leaves writing my own. At the time of writing it's the Christmas break, so what better time to do some easy coding with none of the usual work pressures? I decided to write the thing in Python as a command-line tool; you'll find the source in the EasyCoder repository at &lt;a href="https://github.com/easycoder/unpick" rel="noopener noreferrer"&gt;https://github.com/easycoder/unpick&lt;/a&gt;. By the time you read this, the repo version may well have moved on, so the documentation below could be out of date.&lt;/p&gt;

&lt;p&gt;(Note: Writing a tool is one thing. Finding a good name for it - that hasn't already been used - is quite another.)&lt;/p&gt;

&lt;p&gt;Running the tool with no arguments outputs some basic help, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A recursive tool to list extensions, copy/move all files
of a given type, remove duplicates etc.
Syntax:
unpick [options] [path]
Options (note that many of these are mutually exclusive):
   -types, -extensions, -ext -- list all file extensions
   -list  -- list all files with the given extension
   -duplicates, -dup  -- list all duplicates with the given extension
   -delete, -del -- delete all files with the given extension
   -remove, -rem -- delete all duplicates with the given extension
   -copy -- copy files to a target directory
   -move -- move files to a target directory
   -type, -extension, -ext -- specify the extension
   -target [path] -- copy/move to this target directory
   -flatten -- don't maintain directory structure in copy/move
   -ignore -- ignore duplicates in copy/move
   -help -- this help message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool walks any given directory recursively, taking appropriate actions as it goes. Here are some examples of how to use each option (at the time of writing). In each case, &lt;code&gt;&amp;lt;path&amp;gt;&lt;/code&gt; is the path (absolute or relative) to the directory being scanned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unpick -types &amp;lt;path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output a list of all the file types (extensions) found in the directory and its subdirectories, with the number of files of that type, sorted most to least.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unpick -copy -type doc -target &amp;lt;target folder&amp;gt; &amp;lt;path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy all files with the given extension, putting them into &lt;code&gt;target folder&lt;/code&gt;. The same folder hierarchy will be maintained, or you can add the &lt;code&gt;-flatten&lt;/code&gt; option to force everything to go into a single directory. You can also add the &lt;code&gt;-ignore&lt;/code&gt; option to ignore duplicates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unpick -move -type pdf -target &amp;lt;target folder&amp;gt; &amp;lt;path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move all files with the given extension, putting them into &lt;code&gt;target folder&lt;/code&gt;. This is the same as &lt;code&gt;copy&lt;/code&gt; but the copied files will be deleted as they are moved.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unpick -duplicates -type jpg &amp;lt;path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;List all duplicate file names. The first one encountered is taken to be the original and all others duplicates. The fact that the names are the same doesn't necessarily mean they are the same file, of course, so if there's any doubt you'll need to examine the output carefully. Maybe a later version of this tool will examine the content as well as the name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unpick -remove -type png &amp;lt;path&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete all duplicate files (files with the same name but in different directories). Use &lt;code&gt;duplicates&lt;/code&gt; first to check these really are the same file. Also be aware that the first instance of any file discovered by unpick is assumed to be the master copy, which may cause the wrong copy to be deleted.&lt;/p&gt;

&lt;p&gt;So did it work? Well, after I ran &lt;code&gt;copy&lt;/code&gt; once for each wanted file type, the extracted files took up just over 100GB, compared to more than 500GB on the original backup disk. Most of the rest is old emails, ancient Windows executables and so on, none of which need to be kept, and it should now be a lot easier for my partner to find photos, videos or documents should the need arise. The results are far from perfect, as it can still take a while to burrow through the folder hierarchy or to pick the right file from a huge number in one directory, but at least it's an improvement and it does save a lot of space.&lt;/p&gt;

&lt;p&gt;I hope others here may also find it useful in pruning the immense piles of clutter we fill our computers with. I could do with spending some time adding extra goodies such as a check that a duplicate file name really means an identical file, but if someone else gets there before me I'm happy to use their code.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Setting up a low-cost server</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Mon, 12 Sep 2022 20:19:25 +0000</pubDate>
      <link>https://dev.to/gtanyware/setting-up-a-low-cost-server-4ol1</link>
      <guid>https://dev.to/gtanyware/setting-up-a-low-cost-server-4ol1</guid>
      <description>&lt;p&gt;This article is really about choosing an alternative to a Raspberry Pi and using Network Manager to set up wifi on a headless install, but I couldn't get all that into the title. In fact, networking causes more headaches than pretty well anything else so I think the title is actually quite useful, as once that part is done the rest is plain sailing.&lt;/p&gt;

&lt;p&gt;For several years the Raspberry Pi has been the preferred choice for small servers, as well as for a host of hobbyist projects. It has decent performance, small size, an attractive price and is well supported by both official and community help channels.&lt;/p&gt;

&lt;p&gt;However, the recent chip shortages have hit availability and as a result the price has multiplied several times. At $25 it's excellent value; at $150, meh. So many people have been looking for decent alternatives.&lt;/p&gt;

&lt;p&gt;There are many rivals to the RPi, but none enjoy anything like the same breadth of support. There is a distinct lack of good articles out there on how to set up one of these alternatives, so here goes.&lt;/p&gt;

&lt;p&gt;The Orange Pi Zero 2 is equivalent in performance to a Pi Model 3, if not slightly better. It's considerably smaller and has everything needed for duty as a small webserver. It's a 4-core, 64-bit model with 1GB of RAM, an RJ-45 Ethernet port, wifi, an SD socket, a single USB port, a micro-HDMI port and a USB-C power socket. And it's available at a very attractive price from Ali Express. I chose a package that bundles the computer with a rather neat transparent case, fired off an order for two and they arrived in the UK in a couple of weeks.&lt;/p&gt;

&lt;p&gt;So, now being the proud owner of a pair of rather dinky little computers, the big challenge is what software to run on them. There are a selection of alternatives, notably Armbian, Debian and Ubuntu. I've tried all three and my preference is for Debian.&lt;/p&gt;

&lt;p&gt;Debian is available via the &lt;a href="http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/service-and-support/Orange-Pi-Zero-2.html" rel="noopener noreferrer"&gt;Orange Pi website&lt;/a&gt;. I choose the bullseye server version since I have little need for a desktop; my application is a local controller for smart radiator valves. I flash a 16GB SD card using Balena Etcher, which takes a couple minutes, then put the card into the OPi, connect an Ethernet cable to my router and power up. A red light comes on right away and few seconds later it goes out and a green one appears. After a minute or two the device is visible on my router's connected devices screen, and a short while after that the SSH server is ready to go. So I log in as &lt;code&gt;root&lt;/code&gt; with the password &lt;code&gt;orangepi&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The big job is to set up wifi because I don't want to be tethered to the router all the time. A Google search for help on setting up wifi on Linux turns up a good number of pages, but... nearly all of them are out of date. Since they were written there have been some big happenings in the way networking is managed. You can wave goodbye to &lt;code&gt;dhcpcd&lt;/code&gt;, &lt;code&gt;networks/interfaces&lt;/code&gt;, &lt;code&gt;ifconfig&lt;/code&gt;, &lt;code&gt;ifup&lt;/code&gt; and &lt;code&gt;ifdown&lt;/code&gt;, because they're all history!&lt;/p&gt;

&lt;p&gt;The new kid on the block is Network Manager, a superbly-crafted toolkit that sweeps away all the clutter and makes setting up networking a breeze, just as it ought to be. It's included in new OS builds and specifically in Debian bullseye for the Orange Pi Zero 2.&lt;/p&gt;

&lt;p&gt;I'm going to add wifi to my OPi and give it a static IP address. To do this I run the strangely-named &lt;code&gt;nmtui&lt;/code&gt; (could that be Network Manager Text UI?) and the user interface pops up:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-1.png" alt="Screen 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I move the highlight to &lt;code&gt;Activate a connection&lt;/code&gt;, find my own router and click &lt;code&gt;Activate&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-2.png" alt="Screen 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have to give the network password:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-3.png" alt="Screen 3"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;which shows the connection as &lt;code&gt;Activated&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-4.png" alt="Screen 4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I click &lt;code&gt;Back&lt;/code&gt; to return to the home screen, then &lt;code&gt;Edit a connection&lt;/code&gt; and select the connection I just activated:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-5.png" alt="Screen 5"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I click &lt;code&gt;Edit&lt;/code&gt; and the main editing screen appears:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-6.png" alt="Screen 6"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I move down to &lt;code&gt;IPv4 CONFIGURATION&lt;/code&gt; and click &lt;code&gt;Automatic&lt;/code&gt; to change it to &lt;code&gt;Manual&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-7.png" alt="Screen 7"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;then I fill in all the relevant fields. The result looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Frbrheating.com%2Fresources%2Fpub%2Fnmtui-8.png" alt="Screen 8"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I go to the bottom of the form, click &lt;code&gt;OK&lt;/code&gt; to return to the previous screen, &lt;code&gt;Back&lt;/code&gt; to return to the home screen and &lt;code&gt;Activate a connection&lt;/code&gt;. I move to my chosen connection and click &lt;code&gt;Deactivate&lt;/code&gt;. Then I select it again and click &lt;code&gt;Activate&lt;/code&gt;. Finally, I back out of &lt;code&gt;nmtui&lt;/code&gt; and close my ssh session by typing &lt;code&gt;exit&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now I can ssh back in again, either at the previous address using Ethernet or at the new one (&lt;code&gt;192.168.0.95&lt;/code&gt; in my example) using wifi. As well as &lt;code&gt;root&lt;/code&gt;, Debian also sets up an &lt;code&gt;orangepi&lt;/code&gt; user account, also with the same &lt;code&gt;orangepi&lt;/code&gt; password. Now I can disconnect the Ethernet cable and use just wifi. From here on, things depend on the application. The hardest part is in getting networking to work, and the creators of Network Manager have done a superb job that takes away all the frustration of setting up wifi on a Linux headless system.&lt;/p&gt;

</description>
      <category>orangepi</category>
      <category>linux</category>
      <category>server</category>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>A Smart Heating System</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Fri, 28 Jan 2022 16:26:19 +0000</pubDate>
      <link>https://dev.to/gtanyware/a-smart-heating-system-4d5d</link>
      <guid>https://dev.to/gtanyware/a-smart-heating-system-4d5d</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@lemonzandtea?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Aleksandar Cvetanovic&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/warm?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;  &lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;The cost of domestic heating is currently going through the roof (so to speak). Here in the UK in winter/spring of 2022 we're facing a 50% or more increase in the cost of town gas and electricity, so energy-saving measures are a hot topic (ha!). This piece is about a private project: a DIY hardware/software system for home heating control.&lt;/p&gt;

&lt;p&gt;I'd been thinking for a while about building a system to control radiators or other heating types, with electric actuators to replace the thermostatic ones I had at present. Something using Arduinos or Pi Picos, perhaps. Then, a couple of months ago, by lucky chance I discovered the Bulgarian company &lt;a href="https://shelly.cloud/" rel="noopener noreferrer"&gt;Shelly™&lt;/a&gt;, who sell a range of smart, low-cost relays and sensors, each one having a wifi interface. Since my main interest was in building software, not hardware, this was too good to pass up, so I bought a small kit of parts and set to work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The aims of the project
&lt;/h2&gt;

&lt;p&gt;The primary aim was to build an energy-saving system that would regulate the temperature in each room independently and turn off the heating in rooms that are not being used, either with timing rules, movement detectors or just manually. Everything would be controlled from a smartphone; not just from inside the house but from anywhere with an Internet connection. And the project would respect &lt;a href="https://www.britannica.com/topic/Occams-razor" rel="noopener noreferrer"&gt;Occam's Razor&lt;/a&gt;, avoiding complexity wherever possible.&lt;/p&gt;

&lt;p&gt;Some requirements and constraints soon became apparent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user interface is a browser-based mobile webapp. I don't have the time, experience or inclination to program native apps for Android and iPhone.&lt;/li&gt;
&lt;li&gt;The system is set up as a DIY project requiring little more expertise than the ability to wire up a plug. Full documentation is (or will be) provided as part of the UI. It should offer commercial opportunities for contractors to deploy systems to their own customers.&lt;/li&gt;
&lt;li&gt;For keen coders, all the software is Open Source except for the Shelly items, which for the more dedicated can be replaced by home-builds if so desired.&lt;/li&gt;
&lt;li&gt;The software should be accessible to the widest possible range of interested parties, which means no complex build or deploy toolchains and the absolute minimum of dependencies. This should avoid being held hostage to the future, but also make the software accessible to amateur and part-time programmers as well as full-time professionals.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  System description
&lt;/h2&gt;

&lt;p&gt;So with the above aims in mind, this is how I structured the system:&lt;/p&gt;

&lt;p&gt;The control hardware is based on Shelly 1 relays (see their on-line store) that control standard 30mm electric radiator valve actuators. They can also be used with direct resistance heating as they are able to switch up to 16A. A Shelly H&amp;amp;T (Humidity and Temperature) unit is provided in each room to report the temperature at regular intervals.&lt;/p&gt;

&lt;p&gt;Here's a photo of a "room kit" - all the parts needed to equip a single room with a single radiator, using a standard UK pattress box. The relay is the small blue item inside the box, which also has a neon indicator to show when power is applied to the actuator (right of photo). It plugs into a standard wall outlet and communicates by wifi with the system controller. On the upper left is a Shelly H&amp;amp;T thermometer unit.&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fluru3m5k39fu346z5lji.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fluru3m5k39fu346z5lji.jpg" alt="Image description" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The relays and sensors are controlled by a small computer such as a Raspberry Pi. It doesn't have to be the latest Model 4; I've successfully used a Model 1B, which although very slow is able to do the job and is available at a low cost. It runs a local HTTP server into which the thermometer modules post messages, and a control program that runs through a set of rules for the system to decide when and where heating is needed and turn relays on and off as appropriate. All the Pi software is written in Python (indirectly, as I'll explain later).&lt;/p&gt;

&lt;p&gt;The controller reports changes of temperature to an external web server using REST requests. I use standard public shared hosting and that pretty well mandates PHP, but (for those who hate the language) the REST server is only a few hundred lines of PHP and once built can mostly be forgotten.&lt;/p&gt;

&lt;p&gt;The user interface is a browser-based webapp written in JavaScript (indirectly - see later) and built as a single-page application (SPA). It gives the user a view of the current state of the system and the ability to add and remove rooms, change the target temperature and set up timing schedules of any complexity. It includes a full help stack (or will eventually) and a demo module running behind the scenes that gives a full simulation of a system without the need to buy any hardware. &lt;/p&gt;
&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The user's interaction with the webapp creates a &lt;em&gt;system map&lt;/em&gt; - a JSON structure with details of the rooms, the sensors and the relays. When any changes occur the map is posted to the server with a flag set to request confirmation. The system controller picks up the map every few seconds and confirms, passing back the current states of the sensors. These are then retrieved by the webapp and displayed to the user. &lt;/p&gt;

&lt;p&gt;All the software is held in a GitHub repository. Anyone wishing to contribute to the project will be welcomed, whether it's to work on the software, the user interface or the documentation. The project is currently in a fairly early state of development, with a growing list of things to fix and things to add. &lt;/p&gt;

&lt;p&gt;Here are some notes on the component parts of the software:&lt;/p&gt;
&lt;h2&gt;
  
  
  The system controller
&lt;/h2&gt;

&lt;p&gt;This is Python code running on a headless Raspberry Pi. Any other computer would do just as well but few can match it on price or compactness. The local HTTP server uses Bottle, which can be regarded as a leaner version of Flask, and it has very little to do. Each thermometer unit regularly reports its temperature to the local HTTP server, which writes it to a file whose name is the IP address of the thermometer.&lt;/p&gt;

&lt;p&gt;The control program is run as a &lt;code&gt;cron&lt;/code&gt; task, once every minute. It downloads the system map from the web server, collects the current temperature values from the appropriate local thermometer files and posts them back to the server. It also uses this information to decide if heating should be turned on or off in any room, and sends commands to the relays, each of which also has a local IP address and is listening on port 80.&lt;/p&gt;

&lt;p&gt;As hinted earlier, the controller code is not written directly in Python but in an English-like scripting language that is itself written in Python. The compiler for this language runs on the Pi itself and is supplied with the project as Python source files. High-level source scripts compile on the fly and the compiler output is passed directly to a built-in runtime engine. Here's a snippet to give the general flavour; here, all capitalized words are script variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
open File Path cat `/sensors/` cat Sensor cat `.txt` for reading
read SensorValues from File
close File
put json SensorValues into SensorValues
put float property `temperature` of SensorValues into Temperature
put property `relays` of Room into Relays
put property `events` of Room into Events
take 1 from the length of Events giving L

! Deal with mode requests
put property `mode` of Room into Mode
if Mode is `off` go to TurnOff

if Mode is `on`
begin
    put float property `target` of Room into Target
    if Temperature is less than Target go to TurnOn
    go to TurnOff
end
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reason for doing this is for accessibility and maintainability. Changes are simply a matter of editing a text file and writing it back to the Pi. Using English-like script means the code - to a considerable extent - resembles the way you would describe the operation of the system in words. Software professionals commonly assume that everyone shares their ability to read code and understand complex frameworks and build tools, but that's far from the case. The majority - including some quite competent programmers - find it very hard to read conventional code. Our brains are wired for words, not mathematical expressions.&lt;/p&gt;

&lt;p&gt;There's a performance price to pay, of course. The control program is little more than 200 lines of script but takes nearly half a second to compile on a Pi Model 1B, though later models slash this down to well under 20ms. However, even on the old model this is quite acceptable; a couple of seconds to get the program up and running is of little importance in a one minute &lt;code&gt;cron&lt;/code&gt; cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The user interface
&lt;/h2&gt;

&lt;p&gt;The UI home page is intended for mobile use and currently looks like this:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39xwyyq5hm5jzwgcoulm.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39xwyyq5hm5jzwgcoulm.jpg" alt="Image description" width="800" height="800"&gt;&lt;/a&gt;&lt;br&gt;
As with the system controller, an unusual approach is used here too, in the form of another high-level script compiler and runtime, this one written in JavaScript. It's syntactically very similar to the Python version and designed to facilitate the creation of complex interactive user interfaces without the need to learn any complex programming techniques. In terms of accessability and long-term maintainability there is nothing to beat English. For a native English speaker the learning curve is very short as scripts tend to follow the natural way of describing what's happening. Which is that a DOM is created and the user interacts with it. The compiler/runtime is loaded directly from the repository; all relevant URLs can be found at the end of this article.&lt;/p&gt;

&lt;p&gt;Some might argue that all this represents an unnecessary additional layer of complexity. I would counter that most large coding projects today use a complex framework and require an additional build/deploy stage with its own learning requirements. The compiler used here - EasyCoder - is far simpler than products such as React or Angular and is extensively documented in its own website and repository. The ability to run directly from a high-level script greatly speeds development and maintenance, and runtime performance is scarcely an issue; on a modern smartphone Chrome will compile a thousand-line script in well under 50ms.&lt;/p&gt;

&lt;p&gt;Additionally, a high-level scripting language makes the underlying JavaScript invisible, except perhaps when tracking down the more obscure bugs. This is great for people who don't live and breathe JavaScript but who would like to know what's happening.&lt;/p&gt;
&lt;h3&gt;
  
  
  DOM construction
&lt;/h3&gt;

&lt;p&gt;This project also uses an unconventional approach to DOM construction; one that I described in &lt;a href="https://dev.to/gtanyware/webson-a-new-dom-markup-5cn6"&gt;a recent Dev.to article&lt;/a&gt;. Webson is a JSON format; a language for representing DOM trees. In this project, no HTML is present. Instead, all of the screen elements are defined in a series of Webson files, which combine layout and style in a format not dissimilar to CSS. Webson permits - but neither encourages nor discourages - the use of external CSS classes but none are used so far in this project. With screens of this kind the layout is quite closely associated with its appearance so I personally prefer to keep the styles in with the layout elements they affect.&lt;/p&gt;

&lt;p&gt;Here's the code for the icon and text at the left-hand end of each room in the UI photo above when the operating mode is set to "on". As you can see, much of it is CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "#doc": "The icon and text for 'on' mode",
    "#element": "button",
    "@id" : "room-/ROOM/-mode-icon",
    "display": "flex",
    "flex-direction": "row",
    "width": "10em",
    "#": ["$Button", "$Padding", "$Text"],

    "$Button": {
        "#element": "div",
        "margin-right": "0.5em",
        "pointer-events": "none",
        "#": "$Icon",

        "$Icon": {
            "#element": "img",
            "@src": "resources/icon/temperature.png",
            "height": "2em",
            "padding": "0.5em"
        }
    },

    "$Padding": {
        "#element": "div",
        "width": "0.2em",
        "pointer-events": "none"
    },

    "$Text": {
        "#element": "div",
        "display": "flex",
        "flex-direction": "column",
        "flex": 1,
        "height": "100%",
        "text-align": "left",
        "pointer-events": "none",
        "#": "$Inner",

        "$Inner": {
            "#element": "div",
            "display": "inline-block",
            "flex": 1,
            "text-align": "center",
            "padding": "0.2em",
            "#": ["$Content1", "$Content2"],

            "$Content1": {
                "#element": "div",
                "#content": "On",
                "font-size": "1.4em",
                "font-weight": "bold"
            },

            "$Content2": {
                "#element": "div",
                "@id" : "room-/ROOM/-target",
                "font-size": "0.9em",
                "font-weight": "bold"
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using Webson in an EasyCoder script is simple. It's generally 2 lines of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rest get MainMenuScript from `/resources/webson/mainmenu.json`
render MainMenuScript in Container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;Container&lt;/code&gt; is a script variable that acts for the specific element - usually a &lt;code&gt;div&lt;/code&gt; - that's already in the DOM and is due to receive the new content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo mode
&lt;/h2&gt;

&lt;p&gt;The hardware needed for a typical installation costs a couple of hundred pounds/dollars/euros, so I added a "demo mode" for system evaluation. This runs a background script that mimics the effects of heating being turned on and off, and runs in accelerated real time. Users can set up entire systems and watch them perform according to the built-in rules. &lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Every software product needs good documentation; for system installers, users and maintenance people. There's no better place to put it than in the user interface, where it can't get lost. To make the writing job as simple as possible I chose Markdown, enhanced to support some extra tags and to fit in with the single-page design. This is a modified version of another past project from the same overall repository and is delivered as a single high-level script.&lt;/p&gt;

&lt;p&gt;Every screen and every popup dialog has a Help button that leads to a page of help in a cross-referenced stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;The mobile webapp is at &lt;a href="https://rbrheating.com/home" rel="noopener noreferrer"&gt;https://rbrheating.com/home&lt;/a&gt;. Its documentation can be viewed by navigating from the screen that appears the first time the site is visited, or from the Help button that's present on every screen.&lt;/p&gt;

&lt;p&gt;The project repository is at &lt;a href="https://github.com/easycoder/rbr" rel="noopener noreferrer"&gt;https://github.com/easycoder/rbr&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;EasyCoder is at &lt;a href="https://easycoder.github.io" rel="noopener noreferrer"&gt;https://easycoder.github.io&lt;/a&gt; with its repository at &lt;a href="https://github.com/easycoder/easycoder.github.io" rel="noopener noreferrer"&gt;https://github.com/easycoder/easycoder.github.io&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Webson repository is at &lt;a href="https://github.com/easycoder/webson" rel="noopener noreferrer"&gt;https://github.com/easycoder/webson&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Storyteller (enhanced Markdown) repository is at &lt;a href="https://github.com/easycoder/storyteller" rel="noopener noreferrer"&gt;https://github.com/easycoder/storyteller&lt;/a&gt;. This project uses a modified version whose source script is included in the main repository.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Webson: a new DOM markup</title>
      <dc:creator>Graham Trott</dc:creator>
      <pubDate>Mon, 25 Oct 2021 17:50:27 +0000</pubDate>
      <link>https://dev.to/gtanyware/webson-a-new-dom-markup-5cn6</link>
      <guid>https://dev.to/gtanyware/webson-a-new-dom-markup-5cn6</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@halacious?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Hal Gatewood&lt;/a&gt; on &lt;a href="https://unsplash.com/s/photos/website?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Synopsis
&lt;/h2&gt;

&lt;p&gt;This article introduces &lt;em&gt;Webson&lt;/em&gt;, an easy-to-use syntax with its own run-time rendering engine, that turns JSON into DOM markup and adds features way beyond those of HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;A web page is the visual representation of a Document Object Model, or DOM, the data structure maintained internally by all browsers. Traditionally, the DOM is constructed by the browser from HTML scripts, but as pages get bigger and more complex HTML becomes ever more cumbersome. In recent years it has become increasingly common to create the DOM using JavaScript, with no HTML ever being seen, but while this suits programmers well it requires a different skill set from that needed to build pages the traditional way.&lt;/p&gt;

&lt;p&gt;Today's Web pages may have hundreds or thousands of elements, all carefully positioned to create the desired result. There's no way to hide this complexity, whether it's done with HTML/CSS, JavaScript or some kind of no-code visual builder. In the end it's a human brain that's doing the real work of translating the customer's requirements - a mental picture - into something the browser can use to create the DOM.&lt;/p&gt;

&lt;p&gt;HTML is not program code; it's a form of "markup", the ultimate expression of which came in the form of XML, able to represent not only visual structures but a wide range of other data too. Unfortunately, XML is wordy and hard to read and is not greatly loved. In 2001, Douglas Crockford invented (he would say "discovered") a simpler syntax for representing data structures, as a means of transferring data in and out of JavaScript programs in the form of plain text. The syntax is JavaScript Object Notation, or JSON, and in the past 2 decades it has widely supplanted XML. Virtually every programming language has the ability to read and write JSON and it's now the most common way to transfer data across the Web.&lt;/p&gt;

&lt;p&gt;Since HTML shares many of the disadvantages of XML, the question might be asked, &lt;em&gt;Can JSON also replace HTML?&lt;/em&gt;. If the answer is "yes", a couple of supplementary questions might be&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Can we have user-defined variables and reusable blocks?&lt;/em&gt; &lt;br&gt;
 &lt;em&gt;How about conditional structures?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;which would greatly reduce the amount of markup needed to describe a complex web page, where items are commonly repeated with only minor differences.&lt;/p&gt;
&lt;h2&gt;
  
  
  Webson
&lt;/h2&gt;

&lt;p&gt;Webson is a markup syntax that allows JSON to be used to describe a DOM, together with a JavaScript rendering engine that can be embedded in any web page to process scripts at runtime. The system is immediately usable by HTML/CSS coders and no JavaScript experience is required. It's aimed at simplifying the design and implementation of highly complex layouts, where precise positioning of large numbers of elements is hard to achieve manually, and it achieves this with JSON markup rather than with code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Let's start with a simple example; a layout commonly found in online magazines and social media. At the top there's a full-width header; under this a central panel with 2 sidebars and at the bottom a footer. As this is only an example I've given each of the component &lt;code&gt;div&lt;/code&gt;s its own background color so it stands out clearly. It looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpj2kpnatrqxeavwk0tn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpj2kpnatrqxeavwk0tn.png" alt="Alt Text" width="716" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the HTML that will create this screen. It uses inline styles to avoid the need to present a separate CSS file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width:50%;height:50%;display:flex;flex-direction:column"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"top"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height:20%;background:cyan"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width:100%;flex:1;display:flex;flex-direction:row"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"left"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:inline-block;width:25%;height:100%;background:green"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"center"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:inline-block;height:100%;background:yellow;flex:1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"right"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"display:inline-block;width:15%;height:100%;background:blue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"bottom"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height:10%;background:magenta"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a total of 655 characters. The corresponding Webson script to create the same screen is 1172 characters, nearly twice as many, and occupies 61 lines rather than 14, but before you dismiss Webson as being too wordy I must say in its defence that this is a very basic example which doesn't make use of any of the more advanced features of the system. More complex scripts tend to be far smaller than their HTML equivalents, as we'll see later.&lt;/p&gt;

&lt;p&gt;The reason for the extra size in this example is partly that every item is named and partly because JSON itself is fairly bulky (lots of double-quotes), while the increase in lines is mainly because it's a lot more spaced out. This helps readability; high information density makes code hard to read at a glance as the eye has to pick out specific details from a dense surrounding mass. With Webson, the CSS properties are separated out, one per line, rather than all being crammed onto a single line. This can of course be done with HTML too, but because there's no agreed way to present it the result is usually an unstructured mess, so most coders just put everything on the same line.&lt;/p&gt;

&lt;p&gt;Here's the script. It just uses a basic feature set; I'll get on to some of the advanced features later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"50%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"50%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"flex-direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"column"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"$Top"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$Middle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$Bottom"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Top"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cyan"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Middle"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"flex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"flex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"flex-direction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"row"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"$Left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$Center"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$Right"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Bottom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"magenta"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Left"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inline-block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"25%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Center"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inline-block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"flex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yellow"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inline-block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Running through the script, you will see that every DOM element has its own named block of JSON data. User-defined names all start with &lt;code&gt;$&lt;/code&gt;. There are also directives and other system items; the names of these start with &lt;code&gt;#&lt;/code&gt;. Everything else in the script above is a CSS style to be applied to the current element.&lt;/p&gt;

&lt;p&gt;In the above, most of the blocks include a &lt;code&gt;#element&lt;/code&gt; directive, which names the DOM element type. If this is missing, everything in the block applies to the current element (the one defined in the block that calls this one). Here the only block that lacks an &lt;code&gt;#element&lt;/code&gt; is the very first one, so its styles all apply to the parent container that was created outside Webson and passed to its renderer as a parameter.&lt;/p&gt;

&lt;p&gt;The symbol &lt;code&gt;#&lt;/code&gt; by itself signals that child elements are to be added. This directive takes either a single name or an array of names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attributes
&lt;/h2&gt;

&lt;p&gt;The structure we've built here isn't much use unless we can add further items to the various &lt;code&gt;div&lt;/code&gt;s. Some of this can be done with further Webson code but ultimately you'll either use an &lt;code&gt;onClick="&amp;lt;something&amp;gt;"&lt;/code&gt; callout or a JavaScript function that populates or interacts with the DOM. For the latter to work, elements must have unique ids to allow JavaScript to find them. Here's the &lt;code&gt;$Left&lt;/code&gt; block again, with an id and a couple of other additions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"$Left"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#doc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The left-hand sidebar"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inline-block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"25%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have another new symbol, &lt;code&gt;@&lt;/code&gt;, which (appropriately) signifies an &lt;em&gt;attribute&lt;/em&gt;. Various HTML elements require special attributes such as &lt;code&gt;@id&lt;/code&gt;, &lt;code&gt;@class&lt;/code&gt;, &lt;code&gt;@type&lt;/code&gt;, &lt;code&gt;@href&lt;/code&gt;, &lt;code&gt;@src&lt;/code&gt;, etc. In each case the name is that of the HTML attribute prefixed by &lt;code&gt;@&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another feature above reveals a built-in debugging capability. When hand-building HTML, errors are common, often resulting in strange layouts that are not at all as intended. Webson allows you to specify 3 different debug levels:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"#debug": 0&lt;/code&gt; - no debugging output&lt;br&gt;
&lt;code&gt;"#debug": 1&lt;/code&gt; - Show all &lt;code&gt;#doc&lt;/code&gt; properties&lt;br&gt;
&lt;code&gt;"#debug": 2&lt;/code&gt; - Show every item&lt;/p&gt;

&lt;p&gt;which enables you to see what is happening. The output for the above is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Build $Left
The left-hand sidebar
#element: div
Attribute id: "left" -&amp;gt; left
Style display: "inline-block" -&amp;gt; inline-block
Style width: "25%" -&amp;gt; 25%
Style height: "100%" -&amp;gt; 100%
Style background: "green" -&amp;gt; green
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a simple example where all values are constants. The values appear to be repeated but this will not always be the case. In more complex scripts you will often see the results of expressions being evaluated.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#doc&lt;/code&gt; items can be either single lines of text or arrays of lines. They are just there for the benefit of the programmer and have no effect on the screen being constructed.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;#debug&lt;/code&gt; directive affects its own block and those below it (defined using &lt;code&gt;#&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Nested bocks
&lt;/h2&gt;

&lt;p&gt;Webson implements nesting, whereby items declared at one level apply to all those in lower (contained) levels. Changing a value at one level only affects those at that level and beneath it; those above are unaffected.&lt;/p&gt;

&lt;p&gt;For example, let's suppose the two sidebars share a common feature; they each have an inner &lt;code&gt;div&lt;/code&gt; and padding to produce a border. Here's what it should look like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40rop60gtl0ool33fme4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F40rop60gtl0ool33fme4.png" alt="Alt Text" width="647" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To achieve this we can rewrite the last part of the script as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nl"&gt;"$Left"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#doc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The left column"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"left"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$Width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"25%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$Color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"green"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$LRPanel"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$Right"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#doc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The right column"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$ID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"right"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$Width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"15%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"$Color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"blue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$LRPanel"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$LRPanel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"display"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"inline-block"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"calc($Width - 2em)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"calc(100% - 2em)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"padding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1em"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$LRSubPanel"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"$LRSubPanel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"#element"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"@id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"background"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$Color"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I've left out the block for &lt;code&gt;$Center&lt;/code&gt; as it's unchanged. Both &lt;code&gt;$Left&lt;/code&gt; and &lt;code&gt;$Right&lt;/code&gt; now no longer declare their own &lt;code&gt;#element&lt;/code&gt;; instead they set up user-defined variables &lt;code&gt;$ID&lt;/code&gt;, &lt;code&gt;$Width&lt;/code&gt; and &lt;code&gt;$Color&lt;/code&gt; and invoke &lt;code&gt;$LRPanel&lt;/code&gt; to construct the element. I suggest using an initial capital letter for each user-defined name, to make them easier to spot, but it's not mandatory. Any variable declared or modified at a given level in the structure will be visible at all points beneath that one, but changes do not propagate upwards.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$LRPanel&lt;/code&gt; creates a &lt;code&gt;div&lt;/code&gt;, applies padding to it and creates an inner &lt;code&gt;div&lt;/code&gt; called &lt;code&gt;$LRSubPanel&lt;/code&gt;. Note how the &lt;code&gt;$Color&lt;/code&gt; variable is passed down and used here, resulting in a colored panel with a white border. Note also the use of &lt;code&gt;calc()&lt;/code&gt; in &lt;code&gt;$LRPanel&lt;/code&gt; to allow for the padding, which in a conformant browser adds to the width or height of the element.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to run it
&lt;/h2&gt;

&lt;p&gt;To view this demo on a PC, place the following HTML file on your server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Webson demo&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;'text/javascript'&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'resources/plugins/webson.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"main"&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"width:640px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`resources/json/simple.json`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
                &lt;span class="nx"&gt;Webson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;`keyboard`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For mobile, the width can be set to &lt;code&gt;`100%`&lt;/code&gt;. The JSON script is assumed to be in a folder on your server at&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(your domain)/resources/json/simple.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above uses the relatively-new standard function &lt;code&gt;fetch()&lt;/code&gt; to get the named script from a file on the server. It then calls &lt;code&gt;render()&lt;/code&gt; in the Webson package (&lt;code&gt;webson.js&lt;/code&gt; in the repository) to create the DOM tree that corresponds to the JSON script.&lt;/p&gt;

&lt;h2&gt;
  
  
  From here on in
&lt;/h2&gt;

&lt;p&gt;This has been a necessarily brief introduction to Webson, since to cover every feature in detail would result in a very lengthy article. A more in-depth treatment can be found in &lt;a href="https://github.com/easycoder/webson" rel="noopener noreferrer"&gt;the Webson repository&lt;/a&gt;. The example used is the following on-screen virtual keyboard:&lt;br&gt;
&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1z1yqhi0kq8jogzjq9ur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1z1yqhi0kq8jogzjq9ur.png" alt="Image description" width="640" height="263"&gt;&lt;/a&gt;&lt;br&gt;
The repository documentation starts with the page you are reading now, then goes on to describe how to make the virtual keyboard depicted above. It then shows how to make the keyboard respond to its &lt;code&gt;Shift&lt;/code&gt; and &lt;code&gt;?123&lt;/code&gt; keys being tapped, to change the key legends appropriately. This is all done with simple JSON commands and no conventional coding at all. You can see and test-drive the virtual keyboard with the above functionality &lt;a href="https://webson.netlify.app/keyboard.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Comments are welcome, as are suggestions on how to improve Webson.&lt;/p&gt;

</description>
      <category>markup</category>
      <category>html</category>
      <category>json</category>
      <category>dom</category>
    </item>
  </channel>
</rss>
