Text Graphics

While I was working on curses text based graphical interfaces I came across two interesting Linux text applications:

  • cowsay – generates ASCII pictures of a cow (or other animals) with a message
  • jp2a – a small utility that converts JPG images to ASCII.

I this blog I wanted to document some of the things that I found and how these utilities could be used in a ncurses program.

Cowsay

Cowsay has been around in the Linux world since 2007. It is now available in Windows and Android. To install cowsay in Linux or a Rasberry Pi :

sudo apt-get install cowsay

Coway takes text messages that you pass it.

~$ cowsay "I'm not worried about mad cow...because I'm a helicopter"
 _________________________________________
/ I'm not worried about mad cow...because \
\ I'm a helicopter                       /
 -----------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

If you install the Linux fortune app (sudo apt install fortune) you can pass random fortune messages:

~$ fortune | cowsay
 _______________________________________
/ You get along very well with everyone \
\ except animals and people.            /
 ---------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

There are a number of different images that can be used. To see the list of what is available:

~$ cowsay -l
Cow files in /usr/share/cowsay/cows:
apt bud-frogs bunny calvin cheese cock cower daemon default dragon
dragon-and-cow duck elephant elephant-in-snake eyes flaming-sheep
ghostbusters gnu hellokitty kiss koala kosh luke-koala mech-and-cow milk
moofasa moose pony pony-smaller ren sheep skeleton snowman stegosaurus
stimpy suse three-eyes turkey turtle tux unipony unipony-smaller vader
vader-koala www

To display all the images:

~$ for i in $(cowsay -l); do cowsay -f $i "$i"; done
 _____
< apt >
 -----
       \ (__)
         (oo)
   /------\/
  / |    ||
 *  /\---/\
    ~~   ~~
 ___________
< bud-frogs >
 -----------
     \
      \
          oO)-.                       .-(Oo
         /__  _\                     /_  __\
         \  \(  |     ()~()         |  )/  /
          \__|\ |    (-___-)        | /|__/
          '  '--'    ==`-'==        '--'  '
 _______
< bunny >
 -------
  \
   \   \
        \ /\
        ( )
      .( o ).

....and a bunch more

 Cowsay in Python

There is a native Python cowsay library:

~$ pip install cowsay --user

An example from the Python command line:

>>> import cowsay
>>> cowsay.cow("This is from Python")
  ___________________
< This is from Python >
  ===================
                        \
                         \
                           ^__^                             
                           (oo)\_______                   
                           (__)\       )\/\             
                               ||----w |           
                               ||     ||  

Cowsay in a Curses App

As an example I wanted to make a Raspberry Pi intrusion monitor. First I created a cowsay images with some eyes:

~$ cowsay -f eyes "Raspberry Pi - Intrusion Monitor"

eyes

Once I was happy with the presentation I saved the output to a file:

~$ cowsay -f eyes “Raspberry Pi – Intrusion Monitor” > eyes.txt

In my Python curses app I read the eyes.txt file and used the stdscr.addstr method to write it to the screen. (Note: For more info on writing Python/C curses or Lua curses)


# c_eyes.py - create a curses with a cowsay message
#
import curses , time, random

# create a curses object
stdscr = curses.initscr()
height, width = stdscr.getmaxyx() # get the window size

# define two color pairs
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_BLACK)

# Read the cowsay output file and write it to the screen

f = open("eyes.txt", "r")
eyes = f.read()
stdscr.addstr(0, 0, eyes,curses.color_pair(3))

# Add a footer
stdscr.addstr(height-1, 0, " " * (width-1),curses.color_pair(1))
stdscr.addstr(height-1, 0, " Key Commands : q - to quit " ,curses.color_pair(1))

# Add intrusion code here....
stdscr.addstr(15, 5, "PIR1 input: no movement" ,curses.color_pair(2) )
stdscr.addstr(16, 5, "PIR2 input: no movement" ,curses.color_pair(2) )

curses.curs_set(0) # don't show the cursor
stdscr.refresh()

# Cycle to update text. Enter a 'q' to quit
k = 0
stdscr.nodelay(1)
while (k != ord('q')):
    k = stdscr.getch()

curses.endwin()

c_eyes

jp2a –  converts JPG images to ASCII

jp2a is a Linux utility that is installed by:

apt-get install jp2a

I found that you’ve got to be selective of the jpeg image that you are trying to convert an example of a castle:

 jp2a castle.jpg --colors --background=light 

Another example was to convert a flag. For this example I found the width option to be useful:

 jp2a can.jpg --colors --width=70

 

 

Raspberry Pi with Lua LÖVE Graphics

In a couple previous blogs I looked at using Lua to read/write with Raspberry GPIO pin, and creating simple text based (ncurses) Lua interfaces.

The Lua LÖVE Graphic engine is very popular for 2D gaming.

In this blog I wanted to document how I used Lua LÖVE Graphics with Raspberry Pi data, specifically I’ll look at:

  • Writing Text with different colours and Font sizes
  • Use a timer to periodically update the screen with Raspberry Pi temperatures
  • User keys to toggle Pi GPIO
  • Use button images to toggle logic

Getting Started with LÖVE Graphics

Rather than LÖVE being a graphic library it’s a replacement Lua interpreter, so you run your code directly through LÖVE. To install LÖVE:

sudo apt-get update 
sudo apt-get install love

You can check that love is running by:

pi@raspberrypi:~/ $ love --version
LOVE 11.1 (Mysterious Mysteries)

There are a number of IDE’s that are available, however any text editor will work. I used leafpad on the Pi.

First Example with Text and Keypresses

The LÖVE expects a project to be in it’s own directory with a main.lua file.

My first example was to launch a window with some text and then catch keystrokes. A “q” key will exit the program.

-- My First App --

platform = {}
love.window.setTitle("Raspberry Pi Data")

function love.draw()
-- draw some text and a rectangle --
  love.graphics.setColor(1,0,0) -- rgb, use red
  love.graphics.setFont(love.graphics.newFont(72)) 
  love.graphics.print("Hit q to exit", 200, 200) 

end

function love.keypressed( key )
   print(key)
   if key == "q" then
      	print( "Quiting Now")
	love.event.quit()
   end
end

This program is call main.lua . An example of this program running with output:

pi@raspberrypi:~/lua1 $ love ./
a
b
q
Quiting Now

lua1

Add a Timer Loop

There are a number of timer loop options. I found that some of these options however would take a lot of background CPU resources. One solution that worked well for me was to add a cron function from : https://github.com/kikito/cron.lua

I downloaded this code (cron.lua) and put it into my working project directory.

For this example I wanted to show the Raspberry Pi’s  GPU temperatures. For the GPU temperature, a Pi command line utility exists:

pi@raspberrypi:~ $ /opt/vc/bin/vcgencmd measure_temp
temp=44.0'C

This code example includes:

  • A reference to a cron file, and a timer variable is setup with a getGPU function
  • a shell to the command line : /opt/vc/bin/vcgencmd measure_temp , and then the output was read into a variable (GPU).
  • the love.draw function is refreshed with the love.update function.
-- Dynamically show the GPU temperature

platform = {}
love.window.setTitle("Raspberry Pi Data")

local cron = require 'cron'  -- this file is in the working dir

local msg = 0
local timer = cron.every(5, function() msg = getGPU()  end)


function getGPU() -- read the command line output
	f = assert (io.popen ("/opt/vc/bin/vcgencmd measure_temp"))
	for line in f:lines() do
	GPU = line
		print(line)
	end
	return GPU 
end

 
function love.update(dt)
	timer:update(dt)
end
 

function love.draw()	
	love.graphics.setColor(0,1,0) 
	love.graphics.setFont(love.graphics.newFont(72)) 
	love.graphics.print(msg, 200, 200)

	love.graphics.setColor(0,0,1) 
	love.graphics.setFont(love.graphics.newFont(24)) 
	love.graphics.print("Hit q to quit", 10, 10)	
end

function love.keypressed( key )
	print(key)
   	if key == "q" then
		print( "Q - quit has been pressed!")
		love.event.quit()
   	end
end

The output will look something like:

lua2

After I got the basic timer function working my next step will be to look at creating dynamic bars for Pi Sensors.

lua3

GPIO Reads and Writes

There are couple of choices for doing Lua connections to GPIO pins.

The lua-periphery library works well but it required sudo rights. For a command line program this isn’t a problem but by default the Raspberry Pi will not allow X-windows to run under super-user. A way to run X-Windows with sudo is:

sudo XAUTHORITY=$HOME/.Xauthority love /mypath_dir

Perhaps a simpler approach is to run the gpio command line program (this is pre-install on the PI’s). Below is some code that uses keystrokes to toggle a GPIO pin.

-- Toggle GPIO pins with keystrokes
 
function love.draw()
	love.graphics.setColor(0,0,1) 
	love.graphics.setFont(love.graphics.newFont(24)) 
	love.graphics.print("Keys: 0 = off, 1 = on, q to quit", 10, 10)	
	
	love.graphics.setColor(0,1,0) 
	love.graphics.setFont(love.graphics.newFont(72)) 
	f = assert (io.popen ("gpio read 7"))
	for line in f:lines() do
		pin7 = line
		print(line)
	end
	love.graphics.print("Pin 7 : " .. pin7, 200, 200)

end

function love.keypressed( key )
	print(key)
	if key == "0" then
		os.execute("gpio write 7 0")
	end
	if key == "1" then
		os.execute("gpio write 7 1")
	end
   	if key == "q" then
		print( "Q - quit has been pressed!")
		love.event.quit()
   	end
end

The output will be:

lua4

Buttons Toggles

Button images can be defined in the load function, then in the draw function they can be drawn based on some logic. Below is a example with two buttons (red/green), the mousepressed function will catch mouse clicks on the x-y sizing of the image. Then some logic was added to toggle between the two red/green buttons and text feedback.

-- Love 2 Button Example --

platform = {}
love.window.setTitle("Raspberry Pi Data")

local buttoncolor = "red"
 
function love.load()

	red = love.graphics.newImage("red.png")
	green = love.graphics.newImage("greenbutton.jpeg")
	
end
 
function love.update(dt)

end
 
function love.draw()
	love.graphics.setBackgroundColor( 1,1, 1)
	buttonx = 10
	buttony = 10
	if buttoncolor == "red" then
		love.graphics.draw(red,buttonx,buttony)
		r = 1
		g = 0
	else
		love.graphics.draw(green,buttonx,buttony)
		r = 0
		g = 1
	end
	love.graphics.setFont(love.graphics.newFont(48)) 
	love.graphics.setColor(0,0,0) 
	love.graphics.print("Click button\n 'q' to quit", 350, 20)	
	love.graphics.setColor(r,g,0) 
	love.graphics.print("Button State: " .. buttoncolor, 10, 350)
	love.graphics.setColor(1,1,1) 
end

function love.mousepressed(mx, my, button)
   if button == 1
   and mx >= buttonx and mx < buttonx + red:getWidth()
   and my >= buttony and my < buttony + red:getHeight() then
		if buttoncolor == "red" then
			buttoncolor = "green"
		else
			buttoncolor = "red"
		end      
		
   end
end

function love.keypressed( key )
	print(key)
   if key == "q" then
     print( "q - quit has been pressed!")
	 love.event.quit()
   end
end

The button image and the “Button State” text will toggle each time the button is clicked.

red_button

Final Comments

I found that I was able to do all the things that I needed for my Raspberry Pi project, however I struggled with the documentation and examples.

For me this was a chance to learn Love, but I would pick Python over Lua/Love for Raspberry Pi projects. I found that Python with Tkinter was considerably faster with a lot more examples.

Sqlite and Node-Red

Sqlite is an extremely light weight database that does not run a server component.

In this blog I wanted to document how I used Node-Red to create, insert and view SQL data on a Raspberry Pi. I also wanted to show how to reformat the SQL output so that it could be viewed in a Node-Red Dashboard line chart.

Installation

Node-Red is pre-installed on the Pi Raspian image. I wasn’t able to install the Sqlite node using the Node-Red palette manager. Instead I did a manual install as per the directions at: https://flows.nodered.org/node/node-red-node-sqlite .

cd ~/.node-red 
npm i --unsafe-perm node-red-node-sqlite 
npm rebuild

Create a Database and Table

It is possible to create a database and table structures totally in Node-Red.

I connected a manual inject node to a sqlite node.

sqlite_create_table

In the sqlite node an SQL create table command is used to make a new table. Note: the database file is automatically created.

For my example I used a 2 column table with a timestamp and a value

sqlite_db_config

Insert Data into Sqlite

Data can be inserted into Sqlite a number of different ways. A good approach for a Rasp Pi is to pass some parameters into an SQL statement.

sqlite_insert_flow

The sqlite node can use a “Prepared Statement” with a msg.params item to pass in data. For my example I created two variable $thetime and $thevalue.

sqlite_insert_conf

A function node can be used to format a msg.params item.


// Create a Params variable
// with a time and value component
//
msg.params = { $thetime:Date.now(), $thevalue:msg.payload }
return msg;

Viewing Sqlite Data

A “select” statement is used in an sqlite node to view the data.

A simple SQL statement to get all the data for all the rows in this example would be:

select * from temps;

A debug node can used to view the output.
sqlite_select

Custom Line Chart

Node-Red has a nice dashboard component that is well formatted for web pages on mobile devices.

To add the dashboard components use the Node-Red palette manager and search for: node-red-dashboard.

By default the chart node will create its own data vs. time storage. For many applications this is fine however if you want long term storage or customized historical plots then you will need to pass all the trend data to the chart node.

For some details on passing data into charts see: https://github.com/node-red/node-red-dashboard/blob/master/Charts.md#stored-data

Below is an example flow for creating a custom chart with 3 values with times.custom_chart_data

The JavaScript code needs to create a structure with: series, data and labels definitions


msg.payload = [{
"series": ["A"],
"data": [
[{ "x": 1577229315152, "y": 5 },
{ "x": 1577229487133, "y": 4 },
{ "x": 1577232484872, "y": 6 }
]
],
"labels": ["Data Values"]
}];

return msg;

This will create a simple chart:

custom_chart_image

For reference, below is an example of the data structure for three I/O points with timestamps:


// Data Structure for: Three data points with timestamps

msg.payload = [{
"series": ["A", "B", "C"],
"data": [
[{ "x": 1577229315152, "y": 5 },
{ "x": 1577229487133, "y": 4 },
{ "x": 1577232484872, "y": 2 }
],
[{ "x": 1577229315152, "y": 8 },
{ "x": 1577229487133, "y": 2 },
{ "x": 1577232484872, "y": 11 }
],
[{ "x": 1577229315152, "y": 15 },
{ "x": 1577229487133, "y": 14 },
{ "x": 1577232484872, "y": 12 }
]
],
"labels": ["Data Values"]
}];

Sqlite Data in a Line Chart

To manually update a line chart with some Sqlite data I used the following nodes:

sqlite_2_chartThe SQL select statement will vary based on which time period or aggregate data is required. For the last 8 values I used:

select * from temps LIMIT 8 OFFSET (SELECT COUNT(*) FROM temps)-8;

The challenging part is to format the SQL output to match the required format for the Line Chart. You will need to iterate over each data row (payload object) and format a JSON string.

 //  
 // Create a data variable   
 //  
 var series = ["temp DegC"];  
 var labels = ["Data Values"];  
 var data = "[[";  
   
 for (var i=0; i < msg.payload.length; i++) {  
   data += '{ "x":' + msg.payload[i].thetime + ', "y":' + msg.payload[i].thetemp + '}';  
   if (i < (msg.payload.length - 1)) {  
     data += ","  
   } else {  
     data += "]]"  
   }  
 }  
 var jsondata = JSON.parse(data);  
 msg.payload = [{"series": series, "data": jsondata, "labels": labels}];  
   
   
 return msg;  

 

To view the Node-Red Dashboard enter: http://pi_address:1880/ui

Screen_chart_sqlite

Final Comments

For a small standalone Raspberry Pi project using sqlite as a database is an excellent option. Because a Pi is limited in data storage I would need to include a function to limit the amount of data stored.

 

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

Pan-Tilt-Shoot Webcam

PTZ or Pan-tilt-zoom cameras are off the shelf and reasonably low cost. This project is a home made pan-tilt camera that shoots nerf rockets.

I got the rocket launcher in a bargain bin, but they can be found online starting around $15.

For this project there were 3 main steps:

  • getting the Python code talking to the rocket launcher
  • loading some USB webcam software
  • loading a Python web framework (bottle).

The hardware could be a PC (preferably running Linux) or a Raspberry Pi. On this project I used a Pi clone (an Orange Pi Lite). The faster your hardware the better the streaming video performance that you’ll get.

pts_overview

Getting the Rocket Launcher Working

There are a number of Python libraries that need to loaded:

pip install setuptools
pip install usb

To control the rocket launcher Python can connect to the USB device using the vendor id (0x2123) and product id (0x1010). Rocket launcher commands are issued as USB transfer codes.

A command line test program (rocket1.py) would be:

 import usb  
 import sys  
 import time  
 device = usb.core.find(idVendor=0x2123, idProduct=0x1010)  
 # On Linux we need to detach usb HID first  
 try:  
   device.detach_kernel_driver(0)  
 # except Exception, e:  
 except Exception:  
   pass # already unregistered  
 device.set_configuration()  
 endpoint = device[0][(0,0)][0]  
 down = 1 # down  
 up = 2 # up  
 left = 4 # rotate left  
 right = 8 # rotate right  
 fire = 16 # fire  
 stop = 32 # stop  
 #device.ctrl_transfer(0x21, 0x09, 0x0200, 0, [signal])  
 while True:  
   print('r = right, l = left, u = up, d = down, f = fire ')  
   key = raw_input ('enter key:')  
   if (key == 'l'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, left, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (key == 'u'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, up, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (key == 'r'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, right, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (key == 'd'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, down, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (key == 'f'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, fire, 0x00,0x00,0x00,0x00,0x00,0x00])  
     time.sleep(4)  
   time.sleep(0.1)  
   device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, stop, 0x00,0x00,0x00,0x00,0x00,0x00])  

By default the USB port requires superuser rights, so run the program using sudo:

$ sudo python rocket1.py
r = right, l = left, u = up, d = down, f = fire
enter key:r
r = right, l = left, u = up, d = down, f = fire
enter key:

Getting the Web Cam working

A standard USB Web Cam can be connected to a Linux  PC or Raspberry Pi using the  motion package. Motion is super easy to setup and it’s got lots of added features if you want to look at enhancing things later. To install motion:

sudo apt-get install motion

Once you have motion installed you’ll need to tweek some of it’s parameters by:

sudo nano /etc/motion/motion.conf

The /etc/motion/motion.conf file contains a lot of  parameters, some of the more important ones are:

# Image width (pixels). Valid range: Camera dependent, default: 352
width 800

# Image height (pixels). Valid range: Camera dependent, default: 288
height 600

# Maximum number of frames to be captured per second.
framerate 1

# Maximum framerate for stream streams (default: 1)
stream_maxrate 1

# Restrict stream connections to localhost only (default: on)
stream_localhost off

The speed of your hardware and network will determine how many frames per second you can use.

To run the video server enter:

sudo motion &

The motion package has a built in web server that is accessed by: http://your_ip:8081

livevideo

Getting the Bottle Web Server Running

There are a lot of web programming options that are available. In my case I wanted to run the project as a simple standalone app on an Orange Pi (a Raspberry Pi clone). To install bottle:

sudo apt-get install python-bottle

The Pan-Tilt-Shoot Web page (ptscam.html) was designed with 5 buttons and the Web Cam video below. When a button is clicked a javascript function called sendcmd passes a command as a header item in a AJAX request.  The motion web server camweb video is included using the html img tag, with the source being the web link.

<!DOCTYPE html>
<html>
<head> 
<title>Pan-Tilt-Shoot Webcam</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: #4286f4; border: none; 
  border-radius: 4px; color: white; text-decoration: none; font-size: 30px; width:100%}
  .button2{background-color: green ;width:31%}
  .button3{background-color: red; width:33%}
</style>
</head>
<script>
function sendcmd(thecmd) {
  // send the action as a header item 
  var xhttp = new XMLHttpRequest();
  xhttp.open("GET","/action" , true);
  xhttp.setRequestHeader("myaction", thecmd);
  xhttp.send()
}
</script> 
<body>
<h2>Pan-Tilt-Shoot Webcam</h2> 
<button onclick="sendcmd('up')" class="button">UP</button>
<button onclick="sendcmd('left')" class="button button2">LEFT</button>
<button onclick="sendcmd('fire')" class="button button3">FIRE</button>
<button onclick="sendcmd('right')" class="button button2" >RIGHT</button>
<button onclick="sendcmd('down')" class="button button">DOWN</button>
  
<p><img src='http://192.168.0.117:8081/'></p>
</body>
</html>

The Python web server application sends the web page at startup, and then it processes AJAX requests and passed the requested action (as a header item) to the rocket launcher USB device.

 # Python Bottle   
 #  
 import os, socket  
 from bottle import route, run, static_file, request  
 import usb  
 import sys  
 import time  
    
   
 # Send an action to the rocket launcher  
 def do_action(theaction):  
    
   print("Action : " + theaction)  
   down = 1 # down  
   up = 2 # up  
   left = 4 # rotate left  
   right = 8 # rotate right  
   fire = 16 # fire  
   stop = 32 # stop  
   if (theaction == 'left'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, left, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (theaction == 'up'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, up, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (theaction == 'right'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, right, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (theaction == 'down'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, down, 0x00,0x00,0x00,0x00,0x00,0x00])  
   if (theaction == 'fire'):  
     device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, fire, 0x00,0x00,0x00,0x00,0x00,0x00])  
     time.sleep(4)  
   time.sleep(0.1)  
   device.ctrl_transfer(0x21, 0x09, 0, 0, [0x02, stop, 0x00,0x00,0x00,0x00,0x00,0x00])  

# Send the starting page   
 @route('/')  
 def server_static():  
   return static_file("ptscam.html", root='')  
# Process an AJAX GET request, pass the action to the rocket launcher code   
 @route('/action')  
 def get_action():  
   print("Requested action: " + request.headers.get('myaction'))  
   do_action(request.headers.get('myaction'))  
       
   
 # On Linux we need to detach usb HID first  
 device = usb.core.find(idVendor=0x2123, idProduct=0x1010)  
 try:  
   device.detach_kernel_driver(0)  
 except Exception:  
   pass # already unregistered  
   
 # Start the bottle web server  
 run(host="192.168.0.117" , port=8000, debug=False)  

Because of the USB connection the Python application need to be run under sudo. When the program is running some diagnostics will show connections and actions.

$ sudo python ptscam.py
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://192.168.0.117:8000/
Hit Ctrl-C to quit.

192.168.0.114 - - [23/Nov/2019 19:26:24] "GET / HTTP/1.1" 200 1251
192.168.0.114 - - [23/Nov/2019 19:26:24] "GET /:8081 HTTP/1.1" 404 736
192.168.0.114 - - [23/Nov/2019 19:26:53] "GET / HTTP/1.1" 200 1271
Requested action: up
Action : up

The web page is accessed by : http://your_ip:8000

pts_screen

Final Comments

The next step will be to mount the rocket launcher with the USB web cam on a little rover.

Control Rasp Pi’s with Simple Lua GUIs

I was struggling to find a simple Lua graphic library. Love2D appears to be well regarded, but I wanted to find something that I could get up and running fast.

An old 1980’s graphic technology called curses has been available for years in most languages and I was familiar with it from C and Python.

In this blog I wanted to shared an example of using the Lua curses library to read and write Raspberry Pi general purpose I/O (GPIO).

Installing Lua

To install Lua on a Raspberry Pi:

sudo apt-get update
sudo apt-get install lua5.1
sudo apt-get install liblua5.1-0-dev -- development files, need by LuaRocks
sudo apt-get install lua-socket
sudo apt-get install luarocks -- package manager for Lua modules

sudo luarocks install luasocket

Lua has a package manager called luarocks, (this is similar to pip on Python), where you can install custom libraries or packages on the Pi.

There are a number of choices on how Lua can access Pi GPIO pin. I found that the lua-periphery library to be a reliable option. The Lua version of curses is not 100% compatible to the C version but it’s close.

To install these libraries enter:

sudo luarocks install lua-periphery
sudo luarocks install curses

Raspberry Pi Hardware

I used a Pimoroni Explorer Hat because it has some built in colored LEDs, but you could easily use some LEDs and resistors and wire your own equivalent setup.

 

For some details on how to use the Lua Raspberry Pi GPIO library see: https://funprojects.blog/2019/04/20/lua-and-raspberry-pi/

The Lua Curses App

My goal was to create a simple GUI with a title and a footer with the key commands, then show the values on the screen.

lua_curses_screen

To use colored text there are a few steps that are required:

  • enable color (curses.start_color())
  • define some color pairs (curses.init_pair)
  • create an attribute variable that is defined by a color pair(a_red = curses.color_pair(4))

Then use the attribute “ON” function to set the color  (stdscr:attron(a_red)).

The mvaddstr function is used to write text to position on the screen  object. (stdscr:mvaddstr(2, 5,”SET RASPBERRY PI LEDS” )).

Below is my code to setup 4 LED outputs, and use the keys 1,2,3 and 4 to write to these outputs. The “q” key is used to exit the code.

 -- A Lua curses example with some Raspberry Pi Data  
 -- Define Rasp Pi variables  
 local GPIO = require('periphery').GPIO  
 local gpio_in = GPIO(10, "in")  
 local led1 = GPIO(4,"out")  
 local led2 = GPIO(17,"out")  
 local led3 = GPIO(27,"out")  
 local led4 = GPIO(5,"out")  
 led1:write(1)  
 led2:write(1)  
 led3:write(1)  
 led4:write(1)  
 -- Define curses  
 local curses = require 'curses'  
 curses.initscr()  
 curses.echo(false) -- not noecho !  
 local stdscr = curses.stdscr() -- the screen object  
 -- setup color pairs and attribute variables  
 curses.start_color()  
 curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)  
 curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_BLACK)  
 curses.init_pair(3, curses.COLOR_BLUE, curses.COLOR_BLACK)  
 curses.init_pair(4, curses.COLOR_YELLOW, curses.COLOR_BLACK)  
 curses.init_pair(5, curses.COLOR_RED, curses.COLOR_BLACK)  
 curses.init_pair(6, curses.COLOR_GREEN, curses.COLOR_BLACK)  
 a_rw = curses.color_pair(1)  
 a_white = curses.color_pair(2)  
 a_blue = curses.color_pair(3)  
 a_yellow = curses.color_pair(4)  
 a_red = curses.color_pair(5)  
 a_green = curses.color_pair(6)  
 stdscr:clear()  
 -- Create a background  
 ncols = curses.cols()  
 nrows = curses.lines()  
  
 -- Create a top and bottom color strip  
 stdscr:attron(a_rw) -- set the fore/background colors  
 for i=0, (ncols - 1), 1 do -- write a top and bottom strip  
      stdscr:mvaddstr(0,i, " ")  
      stdscr:mvaddstr(nrows -1,i, " ")  
 end  
 stdscr:mvaddstr(0,0, " Curses Lua Dynamic Text Example")  
 stdscr:mvaddstr((nrows -1), 0, " Key Commands: q - to quit, 1,2,3,4 - to toggle LED")  
 -- Add the main screen static text  
 stdscr:attron(a_white) -- set the fore/background colors  
 stdscr:mvaddstr(2, 5,"SET RASPBERRY PI LEDS" )  
 for i=1,4,1 do   
      stdscr:mvaddstr(4+ i, 5, "LED " .. tostring(i) .. " : " )  
 end  
 stdscr:refresh()  
 local c = stdscr:getch ()  
 while c ~= 113 do -- 113 = q ,quit  
      if c == 49 then led1:write(not led1:read()) end  
      if c == 50 then led2:write(not led2:read()) end  
      if c == 51 then led3:write(not led3:read()) end  
      if c == 52 then led4:write(not led4:read()) end  
      -- show the inputs  
      stdscr:attron(a_blue)  
      stdscr:mvaddstr(5, 15, tostring(led1:read() ) .. " " )  
      stdscr:attron(a_yellow)  
      stdscr:mvaddstr(6, 15, tostring(led2:read() ) .. " " )  
      stdscr:attron(a_red)  
      stdscr:mvaddstr(7, 15, tostring(led3:read() ) .. " " )  
      stdscr:attron(a_green)  
      stdscr:mvaddstr(8, 15, tostring(led4:read() ) .. " " )  
      c = stdscr:getch ()  
 end  
 curses.endwin()  

Some Final Comments

Unfortunately I found the Lua curses documentation to be quite weak and there were very few examples.

My only major stumbling block was to find a stdscr.nodelay() function that allows the code to continue without waiting for a key stroke. This feature exists in the Python and C libraries.

Raspberry Pi Internet Radio

Node-Red is graphical programming interface that allows logic to be created using nodes that are wired together. Node-Red has been pre-installed in the Raspian OS since 2015 and it has a very easy learning curve.

In this blog I wanted to show an example of Node-Red being used with a five button LCD faceplate to play Internet radio stations.

For this project I used a basic USB powered speaker, a Rasp Pi and a Pi 5-button LCD faceplate. The cost of the faceplates start at about $10.

pi_radio2

Getting Started with Internet Radio

There are a good number of Internet radio resources, https://www.internet-radio.com has a good selection of stations to choose from.

To find a URL of a radio station, look through the stations until you find what you like and then right click on the .pls link, and “Save Link as…”. Save this link as a file and then open the file in a text editor to get the URL.

radio_stations

 

MPD – Music Player Daemon

MPD is a Linux based music service that supports the playing of both music files and internet based radio stations. For command line operations that is also a MPD client application called mpc. To install both the service and client:

sudo apt-get install mpd mpc

Before I started building the node-red application I played with the mpc commands to ensure that I understood the basics.

Internet radio stations are added like a song list:

mpc add 'http://uk2.internet-radio.com:8062'
mpc add 'http://live.leanstream.co/CKNXFM'
mpc add 'http://66.85.88.2:7136'

Some key mpc control commands are:

mpc play  # play the current station
mpc play 3 # play radio station 3
mpc pause  # pause the music
mpc next  # play the next radio station
mpc prev  # play the previous radio station 

mpc volume 90 # set the volume to 90%
mpc volume +5 # increase the volume 5%
mpc volume -5 # decrease the volume 5%

The mpc status command will show the volume, what is playing along with the current station number and total number of stations:

$ mpc status
Comedy104 - A Star104.net Station: Doug Stanhope - To Tell You the Truth
[playing] #2/4 1:45/0:00 (0%)
volume: 75% repeat: off random: off single: off consume: off

Node-Red Logic

Node-Red can be started from the Raspberry Pi menus, or from the command line:

node-red-start &

To access the Node-Red web page, enter the Raspberry Pi ip address with port 1880, for example : http://192.168.0.121:1880

For this project two extra Node-Red components are needed, and they are for the LCD faceplate and the MPD music player. To add components use the “Palette Manager” option.

palette

For the LCD faceplate, search for Adafruit, and select the i2c-lcd-adafruit-sainsmart component.

adafruit_palette

Similarly search for mpd and add the node-red-contrib-mpd component.

mpd_palette To create logic select a node from the left node panel and drag it onto the center flow palette, and then “wire” the nodes together.

For the Internet music example I used four function nodes, and the two i2cLED and the two MPD nodes. (Comment nodes are only used to explain the logic).

node_red_radio

The first step is to double click on the MPD nodes and add an MPD server.

Select Button Logic

I used the select button to turn on and off the music player.

A context variable is created to save the current state of the player. Note: a context variable is only accessible for the node where it is defined..

The ic2_LCD_Input node message has a msg.button_name and msg.button_state item that is used to determine which button is pushed.

For the select button logic a group of messages was used to add the different radio stations.


// create an "is setup" variable
var issetup = context.get('issetup')||0;

if ((msg.button_name == "SELECT") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
if (issetup === 0) {
context.set('issetup',1);
var msg0 = { payload:"clear"};
var msg1 = { payload:"add http://185.33.21.112:11029" }; // 1.FM Trance
var msg2 = { payload:"add http://66.85.88.2:7136" }; // Comedy 104
var msg3 = { payload:"add http://live.leanstream.co/CKNXFM"}; // The One - Wingham
var msg4 = { payload:"add http://185.33.21.112:11269" }; // Baroque
var msg5 = { payload:"play" };
return [ [ msg0, msg1, msg2, msg3, msg4, msg5] ];
} else {
context.set('issetup',0);
msg0 = { payload:"pause"};
return msg0;
}
}

Up/Down Button Logic

The UP button will issue an MPD command equivalent to :

mpc volume +5

This will up the volume by 5%. The total volume will max at 100%.

The DOWN button will issue an MPD command equivalent to :

mpc volume -5

// Raise/Lower the volume
var msg1;
var thevolume = 5; //volume % increment to change

if ((msg.button_name == "UP") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
msg1 = { payload:"volume +" + thevolume };
return msg1;
}
if ((msg.button_name == "DOWN") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
msg1 = { payload:"volume -" + thevolume};
return msg1;
}

Current and Max Radio Station Logic

The ‘Current and Max Radio Stations’ node is updated from the MPD in node when there are any changes to the volume or when a new song or station is played.

This logic creates two flow variables (stnmax, stncnt) that are available in any node in this flow.  The station max (stnmax) and current radio station (stncnt) variables are used in the LEFT/RIGHT button logic to determine which station to change to.


// Get the max number of radio stations and the current radio statio
// Make context variables that can be used in other node, like the LEFT/RIGHT button

var msg1 = msg.payload.status ; //create a simplier message
var stnmax = msg1.playlistlength;
flow.set('stnmax',stnmax);
var stncur = msg1.nextsong;
if (isNaN(stncur)) {stncur = stnmax;} // ensure a valid station

flow.set('stncur',stncur);

return msg1; // only needed for possible debugging

While the code is running it is possible to view the context date.

context_flow

UP/DOWN Button Logic

The UP / DOWN logic changes between the radio stations using the mpc commands:

mpc next
mpc prev

It is important to not move past the range of the radio stations or MPD will hang. The stnmax and stncur variables are used to determine if the next or previous commands are to be allowed.


// Move left and right in radion stations
var stnmax = flow.get('stnmax');
var stncur = flow.get('stncur');
if ((msg.button_name == "LEFT") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
if (stncur > 1) {
var msg0 = {payload:"previous"};
return msg0;
}
}
if ((msg.button_name == "RIGHT") && (msg.button_state == "pressed" )){
// if the setup hasn't been run, add a radio station playlist
if (stncur < stnmax)
var msg1 = {payload:"next"};
return msg1;

}

Final Comments

The Pi LCD faceplate is an excellent hardware add-on for any Raspberry Pi project. However it important to know that clone hardware may work as expected. For my hardware I was not able to easily turn off the extra LED.

A future enhancement would be to add a Web interface so that you could change the volume or stations without using the 5 button Pi faceplate.

 

Simple Terminal Interfaces

Typically our interfaces for projects use colorful web pages or custom GUIs. However there are many cases where a simple text interface is all that is required. This is especially true for SSH or remote connections from a Window’s client into a Raspberry Pi or Linux server.

In this blog I’d like to review a 1980’s technology called curses, with three examples. The first example will be simulated Rasp Pi scanning app in “C” and Python. The second and third examples will be in Python and they will show large text presentation and dynamic bars.

Python Curses

Python curses are standard in Python, and they include features such as:

  • support ASCII draw characters
  • basic color support
  • window and pad objects which can be written to and cleared independently

As a first example I wanted to have a colored background, a header and footer and some dynamic text.

curses_text

The first step is to define a curses main screen object (stdscr). The next step is to enable color and to create some color pairs. Using color pairs and the screen size (height, width = stdscr.getmaxyx()) it is possible to add a header and footer strip using the srtdscr.addstr command.

The stdscr.nodelay command allow the program to cycle until the stdscr.getch() call returns a key.

# curses_text.py - create a curses app with 10 dynamic values
#
import curses , time, random

# create a curses object
stdscr = curses.initscr()
height, width = stdscr.getmaxyx() # get the window size

# define two color pairs, 1- header/footer , 2 - dynamic text, 3 - background
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_BLUE)

# Write a header and footer, first write colored strip, then write text
stdscr.bkgd(curses.color_pair(3))
stdscr.addstr(0, 0, " " * width,curses.color_pair(1) )
stdscr.addstr(height-1, 0, " " * (width - 1),curses.color_pair(1) )
stdscr.addstr(0, 0, " Curses Dynamic Text Example" ,curses.color_pair(1) )
stdscr.addstr(height-1, 0, " Key Commands : q - to quit " ,curses.color_pair(1) )
stdscr.addstr(3, 5, "RASPBERRY PI SIMULATED SENSOR VALUES" ,curses.A_BOLD )
stdscr.refresh()

# Cycle to update text. Enter a 'q' to quit
k = 0
stdscr.nodelay(1)
while (k != ord('q')):
# write 10 lines text with a label and then some random numbers
for i in range(1,11):
    stdscr.addstr(4+ i, 5, "Sensor " + str(i) + " : " ,curses.A_BOLD )
    stdscr.addstr(4+ i, 20, str(random.randint(10,99)) ,curses.color_pair(2) )
    time.sleep(2)
    k = stdscr.getch()

curses.endwin()

The simulated Pi values will refresh every  10 seconds until the “q” key is pushed and then the terminal setting are returned to normal (curses.endwin()) and the program exits.

“C” Curses Example

For this “C” example I used a Raspberry Pi. The curses library needs to be installed by:

 sudo apt-get install libncurses5-dev

The curses syntax is similar between “C” and Python but not 100%. For example in Python the addstr command includes a color pair reference, but in “C” this is not supported so an attribute on/off (attron/attroff) command is used to reference the color pair. Below is the “C” code:

/* c1.c - Basic Curses Example */

#include <curses.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int row, col, k;    
// Create a curses object and define color pairs
    initscr();
    getmaxyx(stdscr,row,col);
    start_color();
    init_pair(1,COLOR_RED,COLOR_WHITE);
    init_pair(2,COLOR_GREEN,COLOR_BLACK);
    init_pair(3,COLOR_WHITE,COLOR_BLUE);
    curs_set(0);
    noecho();
    //keypad(stdscr,TRUE);
    nodelay(stdscr, TRUE);
// Write a header and footer, first write colored strip, then write text
    bkgd(COLOR_PAIR(3));
    attron(COLOR_PAIR(1));
// Create a top and bottom color strip
    for (int i = 0; i < col; i++) {
        mvaddstr(0, i,  " ");
        mvaddstr(row-1, i,  " ");
    }
    mvaddstr(0, 0,  " Curses C Dynamic Text Example");
    mvaddstr(row-1, 0,  " Key Commands: q - to quit");
    attroff(COLOR_PAIR(1));   
    mvaddstr(2, 5,"RASPBERRY PI SIMULATED SENSOR VALUES" );
    refresh();
// Cycle with new values every 2 seconds until a q key (133) is entered    
    while (k != 113)
    {
        attroff(COLOR_PAIR(2));
        for (int i = 0; i < 10; i++) {
            mvprintw((4+i), 5,  " Sensor %d : ",i);
        }
        attron(COLOR_PAIR(2));
        for (int i = 0; i < 10; i++) {
            mvprintw((4+i), 20,  "%d",rand() %100);
        }
        k = getch();
        sleep(2);
    }
    endwin();
    exit(0);
}

To compile and run the program (c1.c) enter:

gcc -o c1 c1.c -lncurses
./c1

The “C” example should look very similar to the earlier Python example.

Figlet for Large Custom Text

Large Custom Text can be generated using the Python Figlet library.  Figlet has an extensive selection of text presentations and it uses standard ASCII character to generate the large text presentations. The Figlet library is installed by:

pip install pyfiglet

An example from the Python shell:

pyshell_figlet

For a Figlet example, I wanted to create a large heading and a large dynamic value.

curses_di

The Figlet library can be used to generate a string with user defined texted presented a large text-like format. A little bit of testing is required because the Figlet generated text can be 3,4,5 or more characters tall and the string needs to be added to very left end of the window.

# curses_di.py - show a large heading and large dynamic value
#
import curses, time
import pyfiglet, random

def get_io():
    global value1
    testvalue = str(random.randint(100,1000)/10) + " C"
    value1 = pyfiglet.figlet_format(testvalue, font = "starwars" )

# Create a string of text based on the Figlet font object
title = pyfiglet.figlet_format("Raspi Data", font = "small" ) 

stdscr = curses.initscr() # create a curses object
# Create a couple of color definitions
curses.start_color()
curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)

# Write the BIG TITLE text string
stdscr.addstr(1,0, title,curses.color_pair(1) )
stdscr.addstr(8,0, "Sensor 1: GPIO 7 Temperature Reading" ,curses.A_BOLD)

# Cycle getting new data, enter a 'q' to quit
stdscr.nodelay(1)
k = 0
while (k != ord('q')):
    get_io() # get the data values
    stdscr.addstr(10,0, value1,curses.color_pair(2) )
    stdscr.refresh()
    time.sleep(2)

    k = stdscr.getch()

curses.endwin()

I found that the the small and doom fonts worked well in my testing. To check out and test Figlet fonts online see:

http://patorjk.com/software/taag/#p=display&f=Slant&t=Dude%20what%20are%20you%20doing%20%3F

Curses Windows

By defining a curses window it is possible to clear and write to a window that it is independent from the background. The syntax to create a curses window object is:

mynewwindow = curses.newwin(height, width, begin_y, begin_x)

Windows are ideal for applications where multiple items such as Figlet objects are used. Below is an example with two large Figlet values.

Figlet2win


# Create a static 2 large values example
#
import curses, time
import pyfiglet, random
# Create a string of text based on the Figlet font object
title = pyfiglet.figlet_format("Weather Station 2", font = "small" )

stdscr = curses.initscr() # create a curses object
# Create a couple of color definitions
curses.start_color()
curses.init_pair(1, curses.COLOR_YELLOW, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)

# Write the BIG TITLE text string
stdscr.addstr(1,0, title,curses.color_pair(1) )
stdscr.refresh()

win1 = curses.newwin(9, 44, 6, 4)
win1.addstr(8,0, "Sensor 1: Temperature Reading" ,curses.A_BOLD)

win2 = curses.newwin(9, 44, 6, 50)
win2.addstr(8,0, "Sensor 2: Humidity Reading" ,curses.A_BOLD)
value1 = pyfiglet.figlet_format("23 C", font = "doom" )
win1.addstr(0,0,value1,curses.color_pair(2) )
win1.refresh()
value2 = pyfiglet.figlet_format("35 %", font = "doom" )
win2.addstr(0,0, value2 ,curses.color_pair(2) )
win2.refresh()

# Hit any key to exit
stdscr.getch()
curses.endwin()

Dynamic Bars Example

For the Dynamic bars example I created a get_io function to simulate two real time data  values.

As a first step I created some background information such as headings, a header and a footer. By using the call: height, width = stdscr.getmaxyx() , I am able to position banners at the top and bottom of the terminal window. All of the background info is written to the stdscr object.

Two windows objects (win1 and win2) are used for the real time dynamic bars. Old bar data is removed using the win1.clear() and win2.clear() calls. Like the static example the dynamic bars are created by writing a fill character multiplied by the actual real time value (win1.addstr(1, 1, bar * value1) ). A window.refresh() command is used to show the changes.

The stdscr.getch() method is used to catch keyboard input, and the terminal program is exited when a quit character, “q” is entered.

The complete two dynamic bar program is shown below:


# Simple bar value interface
#
import curses
import time

# get_io is using random values, but a real I/O handler would be here
def get_io():
    import random
    global value1, value2
    value1 = random.randint(1,30)
    value2 = random.randint(1,30)

bar = '█' # an extended ASCII 'fill' character
stdscr = curses.initscr()
height, width = stdscr.getmaxyx() # get the window size
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_WHITE)
curses.init_pair(2, curses.COLOR_GREEN, curses.COLOR_BLACK)
curses.init_pair(3, curses.COLOR_YELLOW, curses.COLOR_BLACK)

# layout the header and footer
stdscr.addstr(1,1, " " * (width -2),curses.color_pair(1) )
stdscr.addstr(1,15, "Raspberry Pi I/O",curses.color_pair(1) )
stdscr.hline(2,1,"_",width)
stdscr.addstr(height -1,1, " " * (width -2),curses.color_pair(1) )
stdscr.addstr(height -1,5, "Hit q to quit",curses.color_pair(1) )

# add some labels
stdscr.addstr(4,1, "Pi Sensor 1 :")
stdscr.addstr(8,1, "Pi Sensor 2 :")

# Define windows to be used for bar charts
win1 = curses.newwin(3, 32, 3, 15) # curses.newwin(height, width, begin_y, begin_x)
win2 = curses.newwin(3, 32, 7, 15) # curses.newwin(height, width, begin_y, begin_x)

# Use the 'q' key to quit
k = 0
while (k != ord('q')):
    get_io() # get the data values
    win1.clear()
    win1.border(0)
    win2.clear()
    win2.border(0)
# create bars bases on the returned values
    win1.addstr(1, 1, bar * value1, curses.color_pair(2))
    win1.refresh()
    win2.addstr(1, 1, bar * value2 , curses.color_pair(3))
    win2.refresh()
# add numeric values beside the bars
    stdscr.addstr(4,50, str(value1) + " Deg ",curses.A_BOLD )
    stdscr.addstr(8,50, str(value2) + " Deg ",curses.A_BOLD )
    stdscr.refresh()
    time.sleep(2)
    stdscr.nodelay(1)
    k = stdscr.getch() # look for a keyboard input, but don't wait

curses.endwin() # restore the terminal settings back to the original

cbars

For testing I used a random simulator for the data but the get_io function could be easily configured to connect to a Raspberry Pi or Arduino module.

The outline boxes in the window object could look strange if you are using a Window’s based SSH client like Putty. To create the problem in Putty’s settings, select: Window ->  Translations and use VSCII as the remote character set.

putty

Final Comments

Curses is definitely an ‘old school’ technology but it offers a simple solution for SSH and terminal based connections.

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.