Same Code for Python GUI and Web Apps

The Python PySimpleGUI project has two main goals:

  • A simpler method for creating graphical interfaces, and
  • Common code for Tkinter, QT, xW and Web graphics

I feel comfortable doing my own Tkinter and Web interfaces, but common code for both is very cool and it could be very useful for Rasp Pi applications. However things didn’t work “out of the box”, so this blog will look at some workarounds and issues.

A Simple Example

If you are used to coding in QT or xW PySimple is absolutely easier to use.

PySimpleGUI has some defined graphic objects (like Text or Button) and the screen placement is defined by how they are grouped with [ ] brackets. For example two objects in a [ ] bracket puts the two objects on the same line. 

If you’re a Tkinter user PySimpleGUI may seem a little more cumbersome in it’s layout design. (I find Tkinter’s grid methods to be a superior layout approach).

For my first example I used the published example and I added an option for the graphic method (tkinter, Qt, Wx and Web):

 import sys, random, time  
   
 mode = "tkinter"  
 # Check for command line arguments   
 if len(sys.argv) > 1: # if there is use the Web Interface  
   if sys.argv[1] == 'web':  
     import PySimpleGUIWeb as sg  
     mode = "Web"  
   elif sys.argv[1] == 'wx':  
     import PySimpleGUIWx as sg  
     mode = "Wx"  
   elif sys.argv[1] == 'qt':  
     import PySimpleGUIQt as sg  
     mode = "Qt"  
   else:  
     import PySimpleGUI as sg  
   
 else: # if no arguments use the standard GUI  
   import PySimpleGUI as sg  
   
 # Basic example from PySimpleGUI
   
 sg.change_look_and_feel('DarkAmber')  # Add a touch of color  
 # All the stuff inside your window.  
 layout = [ [sg.Text('Some text on Row 1')],  
       [sg.Text('Enter something on Row 2'), sg.InputText()],  
       [sg.Button('Ok'), sg.Button('Cancel')] ]  
   
 # Create the Window  
 window = sg.Window('PySimpleGUI ' + mode , layout)  
 # Event Loop to process "events" and get the "values" of the inputs  
 while True:  
   event, values = window.read()  
   if event in (None, 'Cancel'):  # if user closes window or clicks cancel  
     break  
   print('You entered ', values[0])  
   
 window.close()  
 print('Program terminating normally')  
   

The output for the different graphic methods looks incredibly similar for this example:

GUI_wxGUI_webGUI_tkGUI_qt

It’s important to note that PySimpleGUI is still under development so many of the features will be in transition. If you stick to basic functions like: buttons, images and text you should be good.

I found that the default Tkinter version to be very solid, and the other versions would complain on some of the added features such as: frames, sliders, and progressbars.

A Realtime Update Example

For my next example I wanted to do a Rasp Pi type example with some simulated values from a temperature sensor.

To write back to a graphic object:

  • define a key name when the object is defined (key = “myojbname”)
  • use:  window[‘myojbname’]( new_value) to update the object
 import sys, random, time  
   
 mode = "tkinter"  
 # Check for command line arguments   
 if len(sys.argv) > 1: # if there is use the Web Interface  
   if sys.argv[1] == 'web':  
     import PySimpleGUIWeb as sg  
     mode = "Web"  
   elif sys.argv[1] == 'wx':  
     import PySimpleGUIWx as sg  
     mode = "Wx"  
   elif sys.argv[1] == 'qt':  
     import PySimpleGUIQt as sg  
     mode = "Qt"  
   else:  
     import PySimpleGUI as sg  
   
 else: # if no arguments use the standard GUI  
   import PySimpleGUI as sg  
   
 # Basic example of PSGWeb  
   
 def main():  
   sg.change_look_and_feel('LightGrey')  
   dataframe = [  
     [sg.Text('Temperature (C):',size=(12,2)),  
     sg.Text(' ', font='Courier 48', text_color='red', key='degC')],  
     [sg.Text('Humidity (%)  :',size=(12,2)),  
     sg.Text(' ', font='Courier 48', text_color='red', key='humid')],  
       
   ]  
   layout = [  
     [sg.Image(filename = "dht11.png"),sg.Frame("DHT11 Sensor", dataframe,font='Any 12', title_color='blue')]  
     ]  
     
   if mode == "web":  # Note: customization on the Web interface
     window = sg.Window('Pi Weather Data ' + mode, layout,web_ip='192.168.0.107', web_port = 8888, web_start_browser=False, disable_close=True)  
   else:  
     window = sg.Window('Pi Weather Data ' + mode, layout)  
     
   while True:  
     event, values = window.read(timeout=10)  
     temp_degC = random.randint(20,30)  
     humidity = random.randint(0,100)  
     window['degC']( temp_degC)  
     window['humid'](humidity)  
     time.sleep(1)  
       
   window.close()  
   
 main()  
 print('Program terminating normally')  
   

The Wx code would not run, and the development team has commented on compatibility issues that they are having with Wx so future support may be dropped.

The Tkinter and Qt, but for this example the Web presentation dropped the frame outline.

GUI2_qtGUI2_tkGUI2_web

PySimpleGUIWeb

The PySimpleGUIWeb project is based on the Remi Project which defines graphics objects in an HTML/Web environment.

One big difference that I found between the Remi project and PySimpleGUIWeb is that Remi supports callbacks. I found that callbacks are extremely useful if you are trying to mix reads and writes in the same application. PySimpleGUI uses a read to get user input but this gets confusing if you are using a timer for writing new data to the GUI.

The PySimpleGUIWeb library by default was missing some key documentation. I found that “out of the box” it wasn’t usable for my applications . However everything that I needed was in the Window class it just wasn’t clearly documented.

The following web features can be controlled:

  • web_ip=’xx.xx.xx.xx’ , default is localhost (127.0.0.1), not good for remote connections.
  • web_port= xxxx , default is a random port id. Again not useful for remote connections.
  • web_start_browser=False, default a web browser is launched (not useful for SSH).
  • disable_close=True , default closes the app when the web browser closes.

An example to open a Web connection (not local) that doesn’t auto-open a browser and stays open would be:

window = sg.Window('Pi Weather Data ',  layout,web_ip='192.168.0.107', web_port = 8888, web_start_browser=False, disable_close=True)

Final Thoughts

I like the concept of the same code being used on both a Tkinter and a Web  app. I believe that this has a lot of potential. However at this point only the simplest features are available in the Web, so I’d rather use a Python Web framework like Bottle.

I really like some of the concepts of PySimpleGUI but I think that understanding and using the standard Tkinter allows you more flexibility and it lets you include add-in modules like Tk_tools.

 

Pi Appliance

My goal was to make a Pi kitchen appliance that shows me the key things that I want to see and music I want to listen to while I’m getting my morning coffee. For this project I used a Rasp Pi with 2.8″ TFT touchscreen. These screens start at a round $15.

People’s morning interests will vary so in this blog I just wanted to highlight some of the issues that I needed to worked through. For me the main stumbling blocks were:

  • Hiding the top Rasp Pi menu bar
  • Creating a GUI that uses the full screen
  • Getting weather data
  • scraping web pages to extract what I need

Getting Started

There are some great Raspberry Pi TFT screens that come with buttons and cases. You will need to look at the documentation that comes with your screen, but a good reference is: https://learn.adafruit.com/adafruit-pitft-28-inch-resistive-touchscreen-display-raspberry-pitft_case

For my project I simply used some of my kids Lego blocks.

pi_kitch2

Remove the Pi Menu Bar

The Pi TFT screen isn’t super large, so I wanted to remove the Pi menu bar and run my application at full size.

tft_w_menu

To remove the menu bar tweek two files. First:

sudo nano /etc/xdg/lxsession/LXDE-pi/autostart

Comment out the line  (with #) :

@lxpanel --profile LXDE

Then do the same for:

nano /home/pi/.config/lxsession/LXDE-pi/autostart

After this reboot the system.

Create a Full Size App

There are a multitude of choices for a screen layout. I was looking for lines of text, with maybe the bottom line used for buttons. I found that 7 lines was a reasonable fit. To remove the Python Tkinter title I positioned the top of the screen above the physical screen position (-30 instead of 0).


# My Kitchen Appliance App
#
import urllib.request as urllib2
import tkinter as Tkinter
from tkinter.ttk import *

from tkinter.font import Font
from tkinter import messagebox
top = Tkinter.Tk()
top.title("My Kitchen Appliance")
top.geometry("320x240+-5+-30") # set screen size, left (-5) and top (-30)
top.resizable(False, False)
top.details_expanded = False

#Define the buttons
myfont = Font(family="Times New Roman Bold",size= 12) # Should try a few more sizes

tft_rows = 7 # try 7 rows of buttons
tftbutton = ['' for i in range(tft_rows)]
for i in range(tft_rows):
    tftbutton[i] = Tkinter.Button(top, text = "Line " + str(i+1), fg = "blue", bg = "white", anchor="w", width= 35, height= 1,font=myfont).grid(row=(i+1),column=1) # a buttpn arra

top.mainloop()

The Python GUI will look like this:

tft_7bttns

Get Weather Data

There are a number of good weather API’s. I used OpenWeather because I can use it in variety of apps like Node-Red. OpenWeather has a free user API but you should login and get an appid.

A Python example to get some current weather data for a city:


# get Open Weather (REST API) data
import requests

# api-endpoint

URL = "https://openweathermap.org/data/2.5/weather?q="
mycity = "burlington,CA"
myappid = "&appid=b6907d289e10d714a6e88b30761fae22"
# sending get request and saving the response as response object
fullURL = URL + mycity + myappid
r = requests.get(fullURL)

# extracting data in json format
data = r.json()

print (data)

# Check out the structure
#for index, value in enumerate(data):
# print(index, value)

# Show some weather data
print (data['weather'][0]['description'])
print (data['weather'][0]['main'])
print (str(int(data['main']['temp'])) + " C")
# convert wind speed from meters/sec to kph
print (str((data['wind']['speed'] * 3.6)) + " kph")

This code will give output such as:

$Python3 burlweather.py
{'coord': {'lon': -79.8, 'lat': 43.32}, 'weather': [{'id': 803, 'main': 
'Clouds', 'description': 'broken clouds', 'icon': '04n'}], 'base': 
'stations', 'main': {'temp': 5.81, 'pressure': 1014, 'humidity': 93, 
'temp_min': 3.33, 'temp_max': 7.78}, 'visibility': 24140, 'wind': 
{'speed': 2.1, 'deg': 50}, 'clouds': {'all': 75}, 'dt': 1574816701,
 'sys': {'type': 1, 'id': 818, 'country': 'CA', 'sunrise': 1574771158, 
'sunset': 1574804839}, 'timezone': -18000, 'id': 5911592, 'name': 'Burlington', 'cod': 200}
broken clouds
Clouds
5 C
7 kph

Scraping Web Pages

I wasn’t able to find an API for all the things I was after, so I need to scrape web pages. The Python Beautiful Soup library is a great for finding and grabbing stuff on web pages. To install it:

$ apt-get install python-bs4 (for Python 2)

$ apt-get install python3-bs4 (for Python 3)

I had an example where I wanted to find the ski lifts and runs open. I had the Web page but I needed to search the ugly HTML code.

ski_bs0

ski_bs

In the HTML code I found that the lift and run information is contained in a <p class=“open value” tag. Beautiful Soup allows you to make searches based on attributes. The results can be HTML code or the .text property will return the results as simple text (no HTML code).

The following Python code would search my URL and extract the number of lifts open:


$ python
Python 3.7.4
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib.request as urllib2
>>> from bs4 import BeautifulSoup
>>> theurl = 'https://www.onthesnow.ca/ontario/blue-mountain/skireport.html'
>>> page = urllib2.urlopen(theurl)
>>> soup = BeautifulSoup(page, 'html.parser')
>>> liftopen = soup.find("p", attrs={"class":"open value"})
>>> liftopen.text
'2 of 11'

Final Comments

There are a ton of different “Pi Appliance” applications that could be done. I hope that some of these hints that I’ve documented are helpful.

pi_kitch1

Arduino Yún for IoT

There are some great Internet of Things hardware platforms, such as Raspberry Pi’s, ESP-32 based Arduino modules, Particle and Beaglebone to name just a few. Each of these systems have their strengths and weakness. Some are strong on the hardware side, like the Arduino modules, and others excel on the programming side like the Raspberry Pi.  

The Yún is somewhat unique in that it’s a module with two processors, one that supports standard Arduino programming and a second processor that supports Linux and the OpenWrt wireless stack. The Yún ($59) has an Arduino Uno form factor and there are clones like the LinkIt Smart 7688 Duo ($18) in an Arduino Nano form factor.

yun_overview

In this blog I wanted to document some of key feature and functions that I worked through, namely:

  • moving file – scp and ftp
  • Python bridging to Arduino
  • uhttpd Web Server – with Python CGI
  • Yún REST API
  • MQTT
  • Yún mailbox

Yún Overview

The Yún, now in revision 2, seems to have been somewhat overlooked because of the all the low cost ESP-8266 and ESP32 based Arduino modules starting around $2.

Some the interesting features of the Yún include:

  • Arduino code can launch and get feedback from Linux apps
  • A bridge library allows Linux programs and Arduino to share data
  • Arduino libraries for:
    • Web client
    • Web server
    • Mailbox to Linux
  • Yún has a lightweight web server, uhttpd, that can be used to Python, PHP, Lua CGI web programs
  • Yún has a read/write REST API web service

Getting Started

The Arduino side of the Yún is available “out of the box” like any other Arduino module when you connect a USB cable into the module.

The Linux side however requires some configuration. Please see one of the guides on the Linux setup. Once your module is connected to your network, the Yun webserver can be used to configure features and add software components.

yun_software1

Another option for loading software is to use the opkg package manager from an SSH connection.

yun_opkg

So for example to install nano, the simple text editor, enter:

 opkg install nano

Moving File and Working in OpenWrt Linux

The OpenWrt Linux isn’t loaded with with an X-windows environment and this means that you can not run idle or leafpad etc. to do easy editing of program files. Nano is a good command line text editor but it’s not the same as a windows based editor.

To move files between a PC and OpenWrt you have some options. The two that I like are:

  • scp – secure copy, this is built in both OpenWrt and Microsoft Window 10
  • ftp – file transfer protocol. Needs to be installed in OpenWrt.

There are a few ftp servers that could be installed in OpenWrt, vsftp is a lightweight option and it can be installed by:

opkg update
opkg install vsftpd

Once vsftpd is installed it needs to be enabled, this can be done from the command line or via the web interface.

yun_ftp_start.png

Yún Bridge

The Yun Arduino bridge library allow variables to be passed between the Arduino code and a Python program. Below is an example that writes two random variables available, and it creates a “bridge1” variable that can be written from the Python side.

// Simple Yun Bridge Example
//

#include <Bridge.h>
// create a bridge variable to get remote data
char bridge_Value[10];

void setup()
{
    Bridge.begin();     // this launches /usr/bin/run-bride on Linino
}

void loop()
{    
    // create 2 random bridge values, that are sourced from the Arduino
    Bridge.put("random1", String(random(1, 100)));
    Bridge.put("random2", String(random(1, 100)));
    // Called the bridge value "bridge1". This is the name used on the Python side
    Bridge.get("bridge1", bridge_Value, 6);
    delay(1000);  
}

An example to read/write in Python:


#!/usr/bin/python

import sys

sys.path.insert(0, '/usr/lib/python2.7/bridge/')

from bridgeclient import BridgeClient as bridgeclient
value = bridgeclient()

message = value.get("random1") # get a value from Arduino

print "Random1: ", message

value.put("bridge1","1111")  # set a value to Arduino

Yún uhttpd web server

The uhttp web server is used for Yun setup and configuration. This web server can also be used to for custom static pages and user web apps. To view/modify the web server settings:

nano /etc/config/uhttpd

Within this config file, you can enable custom web applications by defining an interpreter:

# List of extension->interpreter mappings.
# Files with an associated interpreter can
# be called outside of the CGI prefix and do
# not need to be executable.
# list interpreter ".php=/usr/bin/php-cgi"
# list interpreter ".cgi=/usr/bin/perl"
list interpreter ".py=/usr/bin/python

# Lua url prefix and handler script.
# Lua support is disabled if no prefix given.
# option lua_prefix /luci
# option lua_handler /usr/lib/lua/luci/sgi/uhttpd.lua

The default directory for user programs is: /www/cgi-bin

Python CGI – Get Values

To read the Arduino bridge values in a Python CGI program, add a file to the /www/cgi-bin directory. For my example I called the file p1.py :


#!/usr/bin/python

import sys
import cgi

sys.path.insert(0, '/usr/lib/python2.7/bridge/')

from bridgeclient import BridgeClient as bridgeclient
value = bridgeclient()

print "Content-type:text/html\r\n\r\n"
print '<html>'
print '<head>'
print '<title>Python CGI Bridge</title>'
print '</head>'
print '<body>'
print '<h2>Python CGI Bridge</h2>'
print 'Random Value1: ' + value.get("random1")
print 'Random Value2: ' + value.get("random2")
print '</body>'
print '</html>'

To run the python script on the web page you will need to change the file right to executable:

chmod +x p1.py

You can debug and see output from the command line.


root@yun1:/www/cgi-bin# ./p1.py
Content-type:text/html

<html>
<head>
<title>Python CGI Bridge</title>
</head>
<body>
<h2>Python CGI Bridge</h2>
Random Value1: 13
Random Value2: 24
</body>
</html>

If the output looks good, try the app from the Web page:

CGI_bridge1

Python CGI – Put Values

There are a number of methods that can be used to send user input from a web page. A simple approach is to use a form. The form data can be read from the cgi.FieldStorage object, using a form.getvalue() call.

#!/usr/bin/python

# Import modules for CGI handling
import cgi
import sys

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
value = bridgeclient()

# Create instance of FieldStorage
form = cgi.FieldStorage()

# Get data from fields
bridge1 = form.getvalue('bridge1')
if bridge1 != "" :
    value.put("bridge1",bridge1)


print "Content-type:text/html\r\n\r\n"
print """
<html>
<head>
<title>Python CGI - Put Bridge Value</title>
</head>
<body>
<h2>Python CGI Form - Put Bridge Value</h2>
<form action = "/cgi-bin/p2.py" method = "post">
Enter BRIDGE1 value: <input type = "text" name = "bridge1"><br />

<input type = "submit" value = "Submit" />
</form>
</body>
</html>"""

 

The web page will call itself when the submit button is pressed.

CGI_bridge2

Yún REST API

The Yún REST API is a web service that allow remote users and web browsers to view and set bridge values. I found the REST API to be a good tool for testing my Web CGI and Python applications

To view all bridge variables enter: http://yourYun_IP/data/get

yun_rest_1

To get a specific bridge value enter:  http://yourYun_IP/data/get/my_bridge_value

yun_rest_2

To put a bridge value enter: http://yourYun_IP/data/put/my_bridge_value/value 

yun_rest_3

IoT Connections – MQTT

For Internet of Things (IoT) projects you need to pass data from the Arduino and some server. The communications and the server would be something like MQTT or Redis.

The Yún does not have direct access to the Wifi or ethernet port so the standard Arduino libraries for MQTT or Redis etc. will not work. An alternative approach is load the protocol’s command line client on the Linux side and then Arduino can shell out to the Linux tool.

For example to load the  Mosquitto MQTT command line tools:

opkg update
opkg install mosquitto-client

To test MQTT publishing to a topic (mytag1) with a message of 249 on remote client (192.168.0.116) :

mosquitto_pub -h 192.168.0.116 -u username -P password -t mytag1 -m 249

To remotely subscribe to

mosquitto_sub -h 192.168.0.116 -u username -P password -t mytag1

An example of sending out four MQTT messages in Arduino:

/*
Shell out to pass values to a MQTT command line pub
*/
#include <Process.h>

void setup() {
  Bridge.begin();	// Initialize the Bridge
  Serial.begin(9600);	// Initialize the Serial

  // Wait until a Serial Monitor is connected.
  while (!SerialUSB);
}

void loop() {
  Process p;
  String thecmd;
  String strval = String( random(0,100));
  // create a string with host, username and password, -t is for the topic
  String theparams = "-h 192.168.0.116 -u pete -P pete -t ";
  int numtopics = 4;
  String topics[4] = {"tag1","tag2","tag3","tag4"};

  for (int i=0; i < numtopics ; i++) {
    strval = String( (i*100) + random(1,99)); // create a random value - 0,100+,200+,300+
    thecmd = "mosquitto_pub " + theparams + topics[i] + " -m " + strval;
    Serial.println(thecmd);
    p.runShellCommand(thecmd);
    // do nothing until the process finishes, so you get the whole output:
    while (p.running());
  }
  // Wait 5 seconds and repeat
  delay(5000);  // wait 5 seconds before you do it again
}

It is also possible to create some bridge variables and pass them to a Python program that could do the MQTT communications.

Yún Mailbox

At present the mailbox only works in one direction, and this into the Arduino. Both the REST web interface and Python have writing functionality but no read capabilities (on the forums this has been identified, so a future revision may add this).

To write using REST:

http://yun_ip/mailbox/mymessagetext

To write using Python:


import sys
sys.path.insert(0, '/usr/lib/python2.7/bridge')
from bridgeclient import BridgeClient
client = BridgeClient()
client.mailbox("my_message")

The Arduino code to read message:

// Mailbox Read Example
//
#include <Mailbox.h>

void setup() {

  // Initialize Bridge and Mailbox
  Bridge.begin();
  Mailbox.begin();

  // Initialize Serial
  SerialUSB.begin(9600);

  // Wait until a Serial Monitor is connected.
  while (!SerialUSB);
  SerialUSB.println("Mailbox Read Message\n");
}

void loop() {
  String message;

  // if there is a message in the Mailbox
  if (Mailbox.messageAvailable()) {
    // read all the messages present in the queue
    while (Mailbox.messageAvailable()) {
      Mailbox.readMessage(message);
      SerialUSB.println(message);
    }

    SerialUSB.println("Waiting 10 seconds before checking the Mailbox again");
  }
  // wait 30 seconds
  delay(10000);
}

I’m not totally sure when I’d use a mailbox. The mailbox is a generic message and it’s not queued (only 1 message) so I think that using the standard bridging of values with get/put is more useful.

Final Comments

The Yun is missing a lot of networking libraries that are available with the ESP-8266 and ESP32 family of Arduino modules. In some cases like with MQTT that was a good Linux command line tool that could be used, but in cases like where you want to connect to a Redis server, you might have some challenges.

I really liked Yun’s built in uhttpd web server, this is far superior to the ESP-8266 Arduino web server library calls.

I found that putting logic into a combination of Python and Arduino could be a little confusing, but for projects with a lot of text or json data using Python would be a real plus. Also for projects where multitasking is required use the Linux process would be ideal.

Pi with Neopixels/Simulated Neopixels

Neopixels are addressable full-colour RGB LEDs that come in a variety of different arrangements. Ranging from single LEDs, to matrix arrays and a variety of sewable components that can be used on wearable products.

dif_neopixels

Neopixels were originally made available for Arduino projects, but now there are also Python libraries for Raspberry Pi’s.

In this blog I will be looking at setting up neopixels components on Raspberry Pi’s, and then I will show some “soft” neopixel layouts using the Python Tkinter graphic library.

Getting Started

To load the Raspberry Pi neopixel libary comes from the nice people at Adafruit, and it is loaded by:

sudo pip3 install rpi_ws281x adafruit-circuitpython-neopixel

It is important to note that neopixels can draw a lot of power so consider looking at using external 5V power for projects with a lot of LEDs. The LED power consumption is based on:

  • How many neopixel LEDs are lit at one time, and
  • What the intensity of the LEDs is.

A few other import points are:

  • not all neopixel strips are the same. Different strips will vary greatly from the LED intensity, and more importantly on the RGB vs. GRB addressing.
  • NeoPixels must be connected to D10, D12, D18 or D21 to work
  • For NeoPixels to work on Raspberry Pi, you must run the code as root

The neopixels are wired with 3 pins : 5V (VCC), GND and signal in. The default signal in wires to Pi pin 18. Neopixel component can be connected in series with data in and out connectors.

data_in_out

Below is an example that will set all the LEDs to a light magenta and then it will cycle one LED to a brighter RED. The overall neopixel string has a 10% brightness.


# Python neopixel example to cycle an LEDs

import board
import neopixel
import time

ORDER = neopixel.RGB  # or neopixel.GRB
numpixels = 12

# Create a pixel object with 12 pixels and low intensity
pixels = neopixel.NeoPixel(board.D18,numpixels , brightness=0.10, auto_write=True, pixel_order=ORDER)
while True:
for i in range(numpixels):
   pixels.fill((10, 0, 40)) # fill all pixels in light magenta
   pixels[i] = (80,0,0) # fill one pixel in brighter red
   time.sleep(1)

Depending on the type and manufacturer of the neopixels the result will look a little different. Some trial and error testing will be required to determine if the strips are RGB or GRB.

 

Simulated Neopixels

If you don’t have neopixels or if what to simulate neopixels then the Python Tkinter graphic library can be used to create a variety of different arrangements. For my testing I create two arrangements: a strip and a matrix.

The important things that I learned was how to create a array object that could simulate the neopixel object. To do this in Python:


import tkinter as tk

root = tk.Tk()
root.title("Soft NeoPixel Strip")

numleds = 25

# Create an array that can be used later in Tkinter

ledstrip = ['' for i in range(numleds)]

for i in range(numleds):
   ledstrip[i] = tk.Label(root,relief='raised',width=3 ) # a label array
   ledstrip[i].grid(row = 0, column = i) # position the labels is a horizontal row
root.mainloop()

Simulated Strip Neopixel

Below is an example of a soft “strip” neopixel application with a demo function.

py_neo_Strip

# Python Neopixel Single Strip Presentation
#
import tkinter as tk

numleds = 25

theled = 0

def stringdemo():
    # move a coloured LED around the string
    global theled
    ledstrip[theled].configure(background= 'white')
    theled = theled + 1
    if theled >= numleds:
        theled = 0
    ledstrip[theled].configure(background= 'sky blue')
    root.after(500, stringdemo)
    
root = tk.Tk()
root.title("Soft NeoPixel Strip")

# create an LED object 
ledstrip = ['' for i in range(numleds)]

# put the LED object into a horizontal strip
for i in range(numleds):
    ledstrip[i] = tk.Label(root,width=2,height=1,relief='raised',background = 'white')
    ledstrip[i].grid(row = 0, column = (i+1))

root.after(500, stringdemo) #start a demo

root.mainloop()

Simulated Matrix Neopixels

Below is an example of a soft matrix neopixel application.

py_neo_Matrix

# Python Neopixel Matrix Presentation
#
import tkinter as tk

numleds = 100
rowcnt = 10
colcnt = int (numleds/rowcnt)

theled = 0

def stringdemo():
    # move a coloured LED around the string
    global theled
    ledstrip[theled].configure(background= 'dark gray')
    theled = theled + 1
    if theled >= numleds:
        theled = 0
    ledstrip[theled].configure(background= 'red')
    root.after(500, stringdemo)
    
    
root = tk.Tk()
root.title("Soft NeoPixel Matrix")

# create LED object
ledstrip = ['' for i in range(numleds)]

# put the LED object into a grid
for i in range(rowcnt):
    for j in range(colcnt):
        ledstrip[theled] = tk.Label(root,width=4,height=2,relief='raised',background = 'dark gray')
        ledstrip[theled].grid(row = i, column = j)
        theled = theled + 1

theled = 0 #reset the led index for the demo
root.after(500, stringdemo)

root.mainloop()

Summary

Neopixels can be used on custom lighting applicatons, for example I used them on a water fountain project.

Given a choice I would recommend using Arduino hardware over the Raspberry Pi hardware for neopixel projects. I found that the Arduino neopixel library to be much more stable and considerably faster than the Pi version.

Pi Thermometer using Python Turtles

Python Turtles are a great way to start kids in programming. Turtles offer a simple step-by-step graphical presentation that has tons of tutorials and examples.

Turtles can also be used on Raspberry Pi projects. In this blog I wanted to look at a Turtle example that reads a temperature sensor and graphically shows the result as an “old style” thermometer.

Getting Started

Python Turtles is probably already  loaded on your system, if not enter:

pip install turtles

The turtle library opens a graphic screen with the very center of the screen being (0,0). This is a little different than many other graphic systems (like PyGame) where the top left  is (0,0).

Different turtle objects can be defined and moved around the screen. A useful feature of turtles is that you can clear all the drawing from one turtle without effecting the what the other turtles have done. (Note: this is useful in this thermometer example where we can have static background turtle and a dynamic turtle that updates with new information).

Below is an example with 3 turtles. The first turtle (t1) is set to red and then moved forward, left, forward and then sent home. The second turtle (t2) is set to purple and given a turtle symbol. The third turtle (t3) is set to green and then moved to a position and a thick circle is drawn.


from turtle import *

setup(500, 400)
Screen()
title(" 3 Turtles")

# First red turtle goes forward, left, forward and back home
t1=Turtle()
t1.color("red")
t1.forward(100)
t1.left(90)
t1.forward(100)
t1.home()

# Second purple turtle has a turtle shape
t2=Turtle()
t2.shape("turtle")
t2.color("purple")
t2.right(45)
t2.forward(100)
t2.left(90)
t2.forward(100)

# Third green turtle goes to a location and makes a thick circle
t3=Turtle()
t3.color("green")
t3.pensize(10)
t3.up()
t3.goto(-100,-10)
t3.down()
t3.circle(80)

turtles1

Drawing an “Old Style” Thermometer

For this project we wanted to draw an “old style” mercury thermometer, with a bulb of red mercury at the bottom and a tube above it.

therm_bg

Using the simple turtle commands like : move, left, right, forward etc. is great to learn but it can be awkward for more difficult drawings.

A more efficient approach is to define an array of x,y coordinates and then move to each position in the array. For example the upper tube can be drawn by:


# Define an array of x,y coordinates for the top tube
outline = ((0,-50),(25,-50),(25,210),(-25,210),(-25,-50),(0,-50))
for pos in outline: # move to each tube x,y point
       thermo.goto(pos)
       thermo.pendown()

Circles are created using a turtle.circle(width) command. To fill an object or a group of objects a turtle.begin_fill() and a turtle.end_fill() set of command is used.

For our example the filled circle for the bulb is created by:


# put the pen up and move to the circle starting
thermo.penup()
thermo.goto(0,-137)
thermo.pendown()
thermo.pensize(5)
thermo.color("black","red")
# draw the circle with fill
thermo.begin_fill()
thermo.circle(50)
thermo.end_fill()

The complete code to draw the complete thermometer background would be:


# Create a background for an "old style" thermometer
from turtle import Turtle,Screen, mainloop
import random, time

# Define a Turtle object
thermo = Turtle()
thermo.penup()
thermo.hideturtle()
thermo.pensize(5)

# Define an array of x,y coordinates for the top tube
outline = ((0,-50),(25,-50),(25,210),(-25,210),(-25,-50),(0,-50))
for pos in outline: # move to each tube x,y point
       thermo.goto(pos)
       thermo.pendown()
# put the pen up and move to the circle starting
thermo.penup()
thermo.goto(0,-137)
thermo.pendown()
thermo.pensize(5)
thermo.color("black","red")
# draw the circle with fill
thermo.begin_fill()
thermo.circle(50)
thermo.end_fill()

mainloop()

Raspberry Pi Hardware Setup

There are a number of different temperature sensors that can be used. For our example we used a low cost ($5) DHT11 temperature/humidity sensor. The DHT11 sensor that we used had 3 pins, (Signal, 5V and GND), and we wired the Signal pin to the Pi physical pin 7.

DHT11-Wiring-Diagram

There is a DHT temperature/sensor Python library that is installed by:

sudo pip install Adafruit_DHT

A Python DHT test program would be:


#!/usr/bin/python
import sys
import Adafruit_DHT

sensor_type = 11 # sensor type could also be 22, for DHT22

dht_pin = 4 # Note: BCM pin 4 = physical pin 7

humidity, temperature = Adafruit_DHT.read_retry(sensor_type, dht_pin)

print( "Temp: ", temperature, " deg C")

print( "humidity: ", humidity, " %")

Turtle Thermometer

Now for the final project we can start pulling things together.

For the thermometer project we used 2 turtles, a static background turtle (thermo) and a dynamic turtle (bar). The bar turtle is cleared and redrawn in the drawbar() function.

A screen object wn is used to resize the window and add a title.

For testing a random integer can be used. This is also useful for check the 0-40C range of the bar.

The full code and an screen shot are below:


from turtle import Turtle,Screen, mainloop
import random, time
import Adafruit_DHT

# Update the temperature bar height and value
def drawbar(temp):
       top = (-50 + 260 * temp/40)
       boutline = ((0,-50),(20,-50),(20,top),(-20,top),(-20,-50),(0,-50))
       bar.penup()
       bar.clear()  # clear the old bar and text
       bar.begin_fill()
       for pos in boutline:
              bar.goto(pos)
       bar.end_fill()
       bar.goto(30,top)
       bar.write(str(temp) + " C",font=("Arial",24, "bold"))

# Setup a default screen size and Title
wn = Screen()
wn.setup(width = 500, height = 500)
wn.title("RaspPi Temperature Sensor")

# define a static thermo backgroup object and a dynamic bar object
thermo = Turtle()
thermo.penup()
bar = Turtle()
bar.color("red")
bar.hideturtle()

# define an array for the top tube
outline = ((0,-50),(25,-50),(25,210),(-25,210),(-25,-50),(0,-50))
thermo.hideturtle()
thermo.pensize(5)
for pos in outline:
       thermo.goto(pos)
       thermo.pendown()

# add some temperature labels
thermo.penup()
thermo.goto(50,-50)
thermo.write("0 C")
thermo.goto(50,210)
thermo.write("40 C")

# draw the filled bulb at the bottom
thermo.goto(0,-137)
thermo.pendown()
thermo.pensize(5)
thermo.color("black","red")
thermo.begin_fill()
thermo.circle(50)
thermo.end_fill()

# Update the temperature
while True:
       humidity, temperature = Adafruit_DHT.read_retry(11, 4)
       # use a random number for testing
       #temperature = random.randint(0,40)
       drawbar(temperature)
       time.sleep(5)

PI_thermo

Final Comments

Compared to other Python graphic libraries (like PyGame, Tkinter or Qt) Turtle graphics can be slow and perhaps limiting, but for kids Turtles projects are super easy to configure. If you are doing simple stuff Turtles requires a lot less code than the other graphic libraries (keyboard input is a good example of this).

There a lot of possible fun Raspberry Pi projects that can be done with Turtle. Some of the other projects that we have done include:

  • use a Wii remote to draw pictures
  • create a Turtle drawing as you drive a rover (show the path)

DynamoDB with Python and JavaScript

Amazon Web Services (AWS) is a probably the best known Cloud services offering on the Internet.  At first glance AWS is a quite intimidating, learning about all the different features of AWS could be a full time job. For this blog I wanted to introduce one component of AWS called DynamoDB.

DynamoDB is a NoSQL database that could be connected to sensor hub devices such as Raspberry Pi’s for Internet of Things (IoT) solutions.

aws_services

Getting Started with DynamoDB

The first step to getting started with AWS is to create an account. You can open a free “testing” account but unfortunately you’ll need to show that you’re serious by including a credit card number.

After creating an account you can sign into the AWS Management Console (https://aws.amazon.com/console/). With a single user account it is possible to run different services in different locations. It is important to note that not all services are available in all locations, so select a location that offers all the services that you need.

AWS_Region

After a region is selected go to the Services menu item and search for DynamoDB, next select “create table”. A DynamoDB table has a NoSQL database structure, so this means that all that is required is a table name and a primary key. A primary key allows rows of data to have a way to be connected together.

For my IoT table example I created a primary key called signalname. (If you plan to have a very large data set and you need extremely high speed response you might want to consider defining multiple keys, such as location or sensor type).

create_table

Once the table is created, you can view and modify data.

the_table

The “Items” tab is used to add, view and delete data. Note, the viewing of updates is not automatic, so you need to press the “refresh” button periodically.

mytag1

Users and Security

The AWS user security is quite detailed. To get started select your account name on the menu bar and “My Security Credentials“.  The Groups item allows you to create security classifications for activities such as admin, viewing, etc.

New users can be defined and added into security groups.

user_security

When a new users is created an Access Key ID and a Secret Access Key is generated. The Secret Access Key is only shown when the user is created so ensure that you save it, and/or download the CSV file.

newuser

After an Access key ID and Secret Access Key is created for a user, you can start  programming interfaces to DynamoDB.

DynamoDB and Python

The Python AWS library is called boto3 and it is installed  in Windows by :

pip install boto3

and in Linux by:

sudo pip install boto3

The AWS security can be either added directly into the Python (or JavaScript) application or it can reside on the node or PC that you are working on. Both methods have pro’s and con’s. To add the credentials locally, you’ll need to install the AWS command line tool (awscli) and then enter your specific information:

$ sudo pip install awscli
$ aws configure
AWS Access Key ID [None]: AKIAIDxxxxx
AWS Secret Access Key [None]: kzzeko8Fxxxxxxxxxxxxxxxxx
Default region name [None]: Central
Default output format [None]:

A Python example to write and read a row into DynamoDB with the credentials in the program could be:

import boto3
import time

thesec = int(time.time() % 60)

mysession = boto3.session.Session(
    aws_access_key_id= 'AKIAID7xxxxxxxxx',
    aws_secret_access_key='kzzeko8xxxxxxxxxxxxxxxx')
db = mysession.resource('dynamodb', region_name='ca-central-1')

table = db.Table('my_iot_burl1')

response = table.put_item(
   Item={
        'signal': 'mytag1',
        'tagdesc': 'test tag1',
        'tagvalue': thesec
    })

response = table.get_item(Key={'signal': 'mytag1'})
print(thesec)
print(response)
print(response['Item']['tagvalue'])

If the credentials are locally used then the code to write data would be:

import boto3

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('users01')

table.put_item(
   Item={
        'signal': 'mytag1',
        'tagdesc': 'test tag1',
        'tagvalue': 16
    }
)

The response comes back as JSON data. For this example the result would be:

16
{‘Item’: {‘tagvalue’: Decimal(’16’), ‘tagdesc’: ‘test tag1’, ‘signal’: ‘mytag1’}, ‘ResponseMetadata’: {‘RequestId’: ’66M7R4CVCACA4KNA24LM90KG0NVV4KQNSO5AEMVJF66Q9ASUAAJG’, ‘HTTPStatusCode’: 200, ‘HTTPHeaders’: {‘server’: ‘Server’, ‘date’: ‘Fri, 15 Feb 2019 16:29:48 GMT’, ‘content-type’: ‘application/x-amz-json-1.0’, ‘content-length’: ’84’, ‘connection’: ‘keep-alive’, ‘x-amzn-requestid’: ’66M7R4CVCACA4KNA24LM90KG0NVV4KQNSO5AEMVJF66Q9ASUAAJG’, ‘x-amz-crc32’: ‘220267538’}, ‘RetryAttempts’: 0}}
16

For large amounts of data being written to DynamoDB it is possible to use a batch writing function:

import boto3
import time

db = boto3.resource('dynamodb', region_name='ca-central-1')

table = db.Table('my_iot_burl1')

thesec = int(time.time() % 60)

with table.batch_writer() as batch:
    for i in range(1,11):
        batch.put_item(
            Item={
                'signal': 'mytag' + str(i),
                'tagdesc': 'test tag'+ str(i),
                'tagvalue': thesec,
                'status': 'NORMAL'
            }
        )

The AWS console can be used to verify that the data was written in.

Writing to DynamoDB will clear the existing row with a completely new set of data. If you only want to update a field in a row then the update_item call is used. In the example below only the sensor tagvalue is updated and all other fields are left unchanged.

import boto3

mysession = boto3.session.Session(
    aws_access_key_id= 'AKIAIDxxxxxxxxx',
    aws_secret_access_key='kzzekxxxxxxxxxxxxxxx'
)

db = mysession.resource('dynamodb', region_name='ca-central-1')

table = db.Table('my_iot_burl1')

# set the value for mytag1 to bet 29
table.update_item(
    Key={ 'signal': 'mytag1' },
    UpdateExpression='SET tagvalue = :newval1',
    ExpressionAttributeValues={
        ':newval1': 29
    }
)

Python “Scan the Data” Example

The boto3 library supports the ability to scan the data based on some filtering criteria. The filter syntax is: Attr(‘the field’).eq(‘thevalue’).

The following comparison operators are available:

eq | ne | le | lt | ge | gt | not_null | null | contains | not_contains | begins_with | in | between

Multiple statements can be created with | (OR) , & (AND) operators. Below is an example that scans the database for rows with an alarm status or with a tag value greater than 100.

scan_view

import boto3
from boto3.dynamodb.conditions import Key, Attr

db = boto3.resource('dynamodb', region_name='ca-central-1')

table = db.Table('my_iot_burl1')

response = table.scan(
    FilterExpression=Attr('status').eq('ALARM') | Attr('tagvalue').gt(100)
)

#print (response)
for i in response['Items']:
    print(i)
    print(i['signal'], "=", i['tagvalue'])

The results were:

{‘tagvalue’: Decimal(‘150’), ‘signal’: ‘mytag2’, ‘tagdesc’: ‘test tag2’, ‘status’: ‘NORMAL’}
mytag2 = 150
{‘tagvalue’: Decimal(‘999’), ‘signal’: ‘mytag1’, ‘tagdesc’: ‘test tag1’, ‘status’: ‘ALARM‘}
mytag1 = 999

JavaScript

For standard HTML/JavaScript projects the credentials are included in the source file. This obviously has some serious security issues, so it’s recommended that you define a low level user that can only do a small subset of functions.

Below is a simple example that can read our DynamoDB table and it can write a new row with an updated value.

<html>
<head>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.7.16.min.js"></script>

<script>
AWS.config.update({
  region: "ca-central-1",
  accessKeyId: "AKIAID7xxxxxxxxxxxx",
  secretAccessKey: "kzzeko8xxxxxxxxxxxxx"
});

var docClient = new AWS.DynamoDB.DocumentClient();

function createItem() {
  var d = new Date();
  var n = d.getSeconds();
    var params = {
        TableName :"my_iot_burl1",
        Item:{
      'signal': 'mytag4',
      'desc': 'test tag4',
      'info': {
        'value':n,
        'quality': "questionable"
        }
    }
    };
    docClient.put(params, function(err, data) {
        if (err) {
            document.getElementById('textarea').innerHTML = "Unable to add item: " + "\n" + JSON.stringify(err, undefined, 2);
        } else {
            document.getElementById('textarea').innerHTML = "PutItem succeeded: " + "\n" + JSON.stringify(params,undefined, 2);
        }
    });
}

function readItem() {
    var table = "my_iot_burl1";
    var signal = 'mytag4';
    var params = {
        TableName: table,
        Key:{'signal' : 'mytag4' }
    };
    docClient.get(params, function(err, data) {
        if (err) {
            document.getElementById('textarea2').innerHTML = "Unable to read item: " + "\n" + JSON.stringify(err, undefined, 2);
        } else {
            document.getElementById('textarea2').innerHTML = "GetItem succeeded: " + "\n" + JSON.stringify(data, undefined, 2);
        }
    });
}

</script>
</head>

<body>
<input id="createItem" type="button" value="Create Item" onclick="createItem();" />
<br><br>
<textarea readonly id= "textarea" style="width:400px; height:300px"></textarea>
<hr>
<input id="readItem" type="button" value="Read Item" onclick="readItem();" />
<br><br>
<textarea readonly id= "textarea2" style="width:400px; height:300px"></textarea>
</body>
</html>

aws_html

Summary

On first impressions I was pretty overwhelmed using Amazon Web Services. However after some time playing with it I found that it wasn’t unlike many other Web services that are available.

DynamoDB can be used to communicate with sensor values from a Raspberry Pi, but unfortunately there are no mainstream libraries for Arduino projects. AWS does offer an MQTT solution that can be used to update a DynamoDB table.

For small IoT projects I feel that AWS is overkill, but for larger projects I would definitely consider using it.

MicroPython Air Boat

My daughter’s and I have built a number of air boat projects, but this time I thought that I’d try it using MicroPython. MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of the Python standard library and is optimised to run on microcontrollers.

For this project my goal was to create a MicroPython application that was a standalone WiFi access point, and its used a small web server for controlling the air boat fans.

Hardware

The hardware that I used on this project included:

• 1 – ESP8266 module ($5-$10)
• 2 – L9110 Fans ($5 each)
• 1 – Uno proto shield (optional) or use a small breadboard
• 9V battery or portable phone charger
• K-Nex blocks
• small plastic box
• 2 – plastic water bottles
• duct tape

MicroPython is supported on a number of Wifi enabled ESP32 and ESP8266 modules. These modules are well priced with the NodeMCU modules starting around $5. It’s important to note that most of the ESP-8266 modules also support Lua and Arduino C/C++ programming.

nodemcu

For this project I used an Wemos ESP-8266 module, that comes in a Arduino Uno form factor. I like the Wemos modules because I can used my Uno proto shields and these modules supports 5V DC, as opposed to the typical MicroPython modules that only support 3.3V. The fans will work with 3.3V but they generate a lot more wind power at 5V.

The L9110 fans are designed for Arduino projects and they only cost about $5. These fans have four pins: VCC, GND, INA (direction), and INB (on/off). For this project I only used forward spinning fans, so only pin INB was used.

L9110_fan

Boat Construction

For the boat frame I used K’Nex pieces because they are light weight and sturdy, however there a lot of other materials that could be used. Water bottles were used for flotation, and duct tape was used to secure the bottles to the frame. To help protect the electronics a small plastic snack container was used. I wire wrapped the fans to the boat frame.

airboat

MicroPython Setup

There are a few choices for MicroPython development. I found that the uPyCraft IDE offered a nice integrated environment (Figure 5), and there are some excellent tutorials to help you get started.

upycraft

For MicroPython projects you typically create 2 Python applications, a boot.py and a main.py file. I like to equate this to Arduino where you have a setup() function and a loop() function. For this example I wanted to keep the documentation simple so I put everything in the boot.py file, but it’s recommended to use main.py for larger projects.

Creating an Access Point

The picture below shows how to setup an access point in just a few lines of code. For this example the access point is called ‘ESP32’ with a password of ‘12345678’. When the code is run you will be able to see when a remote user connects and disconnects to the access point.

access_point

MicroPython Web Server

After getting the Access Point working the next step is to create a Web server. This is a simple Web server project so I embedded the HTML content into my Python code, however for a more complex application I would definitely have my web pages as files that are independent from the code.

For the Web Server example application , we start with the access point connection code, then we setup a socket on port 80. The HTTP request/response sequence is passed through a function called web_page(request). In web_page(request) the code looks for keywords in the HREF request.

# MicroPython boot.py - Access Point Web Server

try:
  import usocket as socket
except:
  import socket

import network

station = network.WLAN(network.AP_IF)
station.active(True)
station.config(essid='ESP32')
station.config(authmode=3,password='12345678')

while station.isconnected() == False:
  pass

print('Connection successful')
print(station.ifconfig())

def web_page(request):
  
  fans_state = ""
  if request.find('/?forward') > 0:
    fans_state="Going Forward"
  if request.find('/?Stopped') > 0:
    fans_state="Stopped" 
  
  html = """<html><head> <title>Ice Boat Web Server</title> 
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,"> <style>
  html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center;}
  h1{color: #0F3376; padding: 2vh;}p{font-size: 1.5rem;}
  .button{display: inline-block; background-color: #e7bd3b; border: none; 
  border-radius: 4px; color: white; text-decoration: none; font-size: 30px; width:100%}
  </style></head>
  <body> <h1>Ice Boat Web Server</h1> 
  <p>ICE Boat : <strong>""" + fans_state + """</strong></p>
  <p><a href='/?forward'><button class="button">Forward</button></a></p>
  <p><a href='/?stop'><button class="button button">STOP</button></a></p>
  
  </body></html>"""
  
  return html

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
  conn, addr = s.accept()
  print('Got a connection from %s' % str(addr))
  request = conn.recv(1024)
  request = str(request)
  print('The Content = %s' % request)
  response = web_page(request)
  conn.send(response)
  conn.close()

The embedded HTML code passes keywords using anchor tags, for example: <a href=’/?forward’>. Often we use mobile frameworks like Bootstrap to help with formatting, however because we’re running a standalone access point we need to manually define all our style codes.

To access our MicroPython web server, use the address: 192.168.4.1. This is the default address (in the access point setup you can change this). When our web server is running we should be able to toggle the stop/forward states.

screen2

Writing Outputs

The MicroPython command line interface is good way to test outputs. To access the command line interface, use the connect button and then enter “Control-C”.

To manage pins on the hardware, machine library is used :

from machine import Pin

Then pin objects can be defined as either inputs or outputs:

Pin14 = Pin(14, Pin.IN)
Pin5 = Pin(5, Pin.OUT)

The value of a pin is read using: pinobject.value(), and set with: pinobject.value(thevalue) .

motor_test

Final Application

Now that we have all the pieces working independently we can pull it all together. For the final code I’ve defined two fans (motorR and motorL), and a fan control function is called from the web requests.

# MicroPython boot.py - Access Point and Airboat Web Controls
import time
try:
  import usocket as socket
except:
  import socket

from machine import Pin
import network

# Define the left, right and back fans pin
motorR = Pin(12,Pin.OUT)
motorL = Pin(4,Pin.OUT)

# start with fans stopped, Note: my FANs are 0=run
motorR.value(1)
motorL.value(1)


station = network.WLAN(network.AP_IF)
station.active(True)
station.config(essid='ESP32')
station.config(authmode=3,password='12345678')

while station.isconnected() == False:
  pass

print('Connection successful')
print(station.ifconfig())

def fancontrol(left,right):
  motorL.value(left)
  motorR.value(right)

def web_page(request):
  
  fans_state = "Stopped"
  if request.find('/?forward') > 0:
    fans_state="Going Forward"
    fancontrol(0,0)
  if request.find('/?left') > 0:
    fans_state="Going Left"
    fancontrol(1,0)
  if request.find('/?right') > 0:
    fans_state="Going Right" 
    fancontrol(0,1)
  if request.find('/?stop') > 0:
    fans_state="Stopped"
    fancontrol(1,1)
  
  html = """<html><head> <title>Air Boat Web Server</title> 
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,"> <style>
  html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center;}
  h1{color: #0F3376; padding: 2vh;}p{font-size: 1.5rem;}
  .button{display: inline-block; background-color: #e7bd3b; border: none; 
  border-radius: 4px; color: white; text-decoration: none; font-size: 30px; width:100%}
  .button2{background-color: #4286f4; width:49%}
  </style></head>
  <body> <h1>Air Boat Web Server</h1> 
  <p>Air Boat : <strong>""" + fans_state + """</strong></p>
  <p><a href='/?forward'><button class="button">Forward</button></a></p>
  <p><a href='/?left'><button class="button button2">LEFT</button></a>
  <a href='/?right'><button class="button button2" >RIGHT</button></a></p>
  <p><a href='/?stop'><button class="button button">STOP</button></a></p>
  
  </body></html>"""
  
  return html

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
  conn, addr = s.accept()
  print('Got a connection from %s' % str(addr))
  request = conn.recv(1024)
  request = str(request)
  print('The Content = %s' % request)
  response = web_page(request)
  conn.send(response)
  conn.close()

airboat_moving

Summary

You’ll be quite surprised about how fast even 2 fans will move the air boat. Balancing the direction of the fans or adding a simple rudder may be required to ensure that it keeps a straight forward direction.

I’ve done the same project on the exact same hardware in Anduino C++ and I would say the response speed is similar but the Python code might be slightly leaner. I found that the MicroPython IDE wasn’t as robust as the Arduino IDE, but I really enjoyed doing interactive Python testing from a command prompt.

I won’t be giving up on Arduino C++, but I can definitely see a place for MicroPython, especially for projects with lots of string manipulation.