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.

Lua and Raspberry Pi

Over the years I’ve been seeing Lua programs pop up in places that I didn’t expect, for example:

In this blog I wanted to document a couple of examples of using Lua on Raspberry Pi projects. (For a blog on using Lua with simple curses GUI’s).

An Introduction of Lua

Lua is a lightweight interpreted scripting language. The Lua interpreter is supported on most operating systems, however like Python not all of its libraries are support on all OS’s.

Lua’s greatest following is in the gaming world. The love2d  graphic framework is open source and it works on Windows, Mac OS X, Linux, Android and iOS.

There are some good Lua tutorials to get you jump started. If you’re familiar with Python and Basic programming the Lua language is fairly easy to learn.

Install of Lua on a Raspberry Pi

To install Lua  on a Pi enter the following lines:

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

There a number of versions of Lua, going from 5, 5.1, 5.2 to 5.3. I used version 5.1 because most of the examples used this version, but there isn’t a problem loading multiple versions.

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. To install this library enter:

sudo luarocks install lua-periphery

GPIO Examples

To read a GPIO input:

local GPIO = require('periphery').GPIO

-- Open GPIO 10 with input direction
local gpio_in = GPIO(10, "in")

local value = gpio_in:read()
print ("GPIO pin 10 :", value)

gpio_in:close()

To toggle an LED with a keyboard value :

-- toggle.lua : get user a user value to send to a GPIO

local GPIO = require('periphery').GPIO

-- Open GPIO 4 with output direction
local gpio_out = GPIO(4, "out")

while (true)
do
        print ("Enter an output value:")
        s = io.read("*n")
        gpio_out:write(s)
        print ("Output value:", gpio_out:read(),"\n")
end
gpio_out:close()

To run the LED toggle program:

$ sudo lua5.1 gpio1.lua
Enter an output value:
1
Output value: true

Enter an output value:
0
Output value: false

To exit the program enter “Control-C”

Lua Socket Applications

For many application you want to remotely view or control data. One way to do this is to create an application that is a socket server. A simple Lua socket server application that show what a remote socket client sends is:

-- sock.lua : a socket server that prints client input
local socket = require("socket")

-- create a TCP socket and bind it to the local host, at any port
local server = socket.bind("*", 444)

-- loop forever waiting for clients
print ("Lua Socket Server on Port 444")
while 1 do
  -- wait for a connection from any client
  local client = server:accept()

  -- receive the line
  local line, err = client:receive()
  print("Input:", line)

  client:close()

The socket server can be tested by opening a second terminal window and then use a bash script to send a text string to the open port (444 for this example):

$ echo "1" > /dev/tcp/localhost/444
$ echo "0" > /dev/tcp/localhost/444

Our Lua socket server application will show the client text that is sent:

 $ sudo lua5.1 sock.lua
Lua Socket Server on Port 444
Input: 1
Input: 0

 

A Lua Socket Server with GPIO Control

The next step is combine that socket server with GPIO call. For the next example if a 0 or 1 is sent, then the GPIO output will be set to 0 or 1.

-- load libraries
local socket = require("socket")
local GPIO = require('periphery').GPIO

local gpio_out = GPIO(4, "out")

-- create a TCP socket and bind it to the local host, at any port
local server = socket.bind("*", 444)


-- loop forever waiting for clients
print ("Lua Socket Server on Port 444")
while 1 do
  -- wait for a connection from any client
  local client = server:accept()

  -- receive the line
  local line, err = client:receive()
  print("Input:", line)
  if (line == "0")
  then
    gpio_out:write(0)
  elseif (line == "1")
  then
    gpio_out:write(1)
  end

  client:close()

You can use the same bash script from the above example to toggle a GPIO pin.

Summary

I like how in 1 day I was able to go from, no real knowledge on Lua, to remotely using sockets to control Raspberry Pi GPIO pins.

I found my biggest issue was a standard and simple graphic interface. Love2D has a lot of potential but it’s a little like Python PyGame great for games but a little over kill if you want to create a dialog with a couple of buttons. The Lua curses library offers an “old style” interface but the documentation and examples are a little weak.

I can’t see myself giving up Python for Lua but I could perhaps see using Lua on projects where:

  • Lua is used on ESP-8266/NodeMCU and you want to include some Raspberry Pi integration
  • Lua is been used on a Redis database server and either a Pi or a ESP-8266 is passing up/down data

 

Raspberry Pi and Go Programming

Go or GoLang is a compiled programming language developed by Google.

Go is widely used for Web Development and it is one of the fastest growing programming languages today. Unlike Java which is runs in a JVM (Java Virtual Machine) , Go compiles directly to Windows, OS X or Linux  executable files. 

In this blog we will look at creating two Go programs that talk to Raspberry Pi GPIO. The first will be a simple keyboard input program and the second will be a standalone Go web app to control GPIO pins.

Installation

To install go enter:

sudo apt-get install golang

To test that the install is working you can check the Go version number:

$ go version
go version go1.7.4 linux/arm

A “Hello World” example (hello.go) is:


package main

import "fmt"

func main() {

  fmt.Println("Hello World");

}

The hello.go code is compiled and ran by:

$ go build hello.go  # compile the go code

$ ./hello   # run the go code

Hello World

Raspberry Pi GPIO

There are a number of different ways to have Go connect to the Pi General Purpose Inputs and Outputs (GPIO). For this example I am going to look at shelling out to the gpio command line utility, but there are also go rpi libaries that can be used.

For testing I like to use the gpio utility because it offers a good selection of commands and I can manually test and verify the command before I use them in my Go code. For help on gpio  use the -h option.

The Raspberry Pi hardware setup used an LED with a resistor on physical pin 7 (BCM pin 4).

Led_setu

Our first go program (keyin.go) will read keyboard input and then to shell out twice to gpio, first time to write a value and the second time to read the value back.

package main

import (
    "bufio"
    "fmt"
    "os/exec"
    "os"
)

func main() {
    // Get keyboard input
    reader := bufio.NewReader(os.Stdin)
    fmt.Print("Enter value for GPIO pin 7 : ")
    pinval, _ := reader.ReadString('\n')

    // Write to GPIO pin 7 using keyboard input 
    testCmd := exec.Command("gpio","write", "7", pinval)
    testOut, err := testCmd.Output()
    if err != nil {
        println(err)
    }
    // Read back the GPIO pin 7 status
    testCmd = exec.Command("gpio","read", "7")
    testOut, err = testCmd.Output()
    if err != nil {
        println(err)
    } else {
      fmt.Print("GPIO Pin 4 value : ")
      fmt.Println(string(testOut))
    }
}

To compile and run the keyin.go program:

 $ go build keyin.go
 $ ./keyin
Enter value for GPIO pin 7 : 1
GPIO Pin 4 value : 1

Simple Go Web Application

For a starting example we’ll make a go web application (web_static.go) show a web page called web_static.html.

The web_static.html file will be :

<html>
  <head>
    <title>GO PI Static Page</title>
  </head>
  <body>
    <h1>GO PI Static Page</h1>
    <hr>
    This is a static test page
  </body>
</html>

The web_static.go program will need to import the “net/http” library. A http.HandleFunc call is used to look for the default address “/” and serve up our web_static.html file. The http.ListenAndServe function listens for web requests on port 8081.

package main

import (
    "log"
    "net/http"
)

func main() {
    // Create default web handler, and call a starting web page
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        http.ServeFile(w, r, "web_static.html")
        println("Default Web Page")
    })
    // start a listening on port 8081
    log.Fatal(http.ListenAndServe("8081", nil))

}

The Go code can be compiled and run by:

$ go build web_static.go
$ ./web_static
Default Web Page

From a web browse pointed at the Raspberry Pi on port 8081, our web page will show as:

web_static

Go Web App with Pi GPIO

The next step is to create a Web Page that can pass some parameters. For this application we’ll turn a GPIO output on and off.

A new web page (go_buttons.html) is created with two buttons. HTML anchor tags are used to pass  /on and /off parameters to our Web app.

A CACHE-CONTROL meta tag set to NO-CACHE is needed to ensure that the web page always refreshes.  I also included an EXPIRES meta tag (= 0) so that the browser always see the page as expired . If you don’t include meta tags the web page may only update once.

<html>
  <head>
    <title>GO PI GPIO</title>
    <META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE">
    <META HTTP-EQUIV="EXPIRES" CONTENT="0">
  </head>
  <body>
    <h1>Go Raspberry Pi GPIO Test</h1>
    <a href="/on"><button>Set LED ON</button></a><br>
    <a href="/off"><button>Set LED OFF</button></a>
  </body>
</html>

Our new Go Web app (go_buttons.go) now includes two more http.HandleFunc handler functions, one for /on and for /off. These handler functions call a new function called gpio that is used to write a outputs and read back the output status. 

Our newly created gpio function shells out twice to the gpio command line utility, first time to write a value and the second time to read the value back.

package main

import (
	"log"
	"net/http"
	"os/exec"
)
func gpio( pinval string) {
    testCmd := exec.Command("gpio","write", "7", pinval)  
    testOut, err := testCmd.Output()      
    if err != nil {
        println(err)
    }
    testCmd = exec.Command("gpio","read", "7") 
    testOut, err = testCmd.Output()
    if err != nil {
        println(err)
    } else { 
      print("GPIO Pin 4 value : ")  
      println(string(testOut))
    }
}

func main() {

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, "go_buttons.html")
		println("Default Web Page")
	})

	http.HandleFunc("/on", func(w http.ResponseWriter, r *http.Request) {
                 http.ServeFile(w, r, "go_buttons.html")
		 println("Web Page sent : ON")
		 gpio("1")

	})

	http.HandleFunc("/off", func(w http.ResponseWriter, r *http.Request) {
                 http.ServeFile(w, r, "go_buttons.html")
		 println("Web Page sent : OFF")
		 gpio("0")
	})

	log.Fatal(http.ListenAndServe(":8081", nil))

}

To compile and run of web app go_buttons:

$ go build go_buttons.go
$ ./go_buttons

Default Web Page
Web Page sent : ON
GPIO Pin 4 value : 1

Default Web Page
Web Page sent : OFF
GPIO Pin 4 value : 0

The web page should look something like:

go_buttons

Summary

For a polished application I would rather use a native Go library for GPIO calls, but for prototyping I found that the gpio command line utility to be easier for trouble-shooting.

After getting a basic web page working for anchor tags for /on and /off, the next step will be to use some Javascript with AJAX to show dynamic values.

 

Android Python App Talking to a Raspberry Pi

If you’d like to use some of your Python experience on Android, then the QPython Android IDE may surprise you with what it has to offer.

Out of the box QPython includes some excellent libraries, especially if you’re interested in doing some data mining or modeling.

libraries

In this blog I’d like to look at an example of building an Android QPython GUI app that controls some lights on a Raspberry Pi.

A Button Interface

Luckily you can do all your development on a Windows/Linux/MacOS/Raspberry Pi before you need to move the application to Android.

QPython comes with a number of pre-installed libraries such as: a standalone Web Server, a low level Android interface, sockets, and the QPython supports the Pygame library.

I used a simple 4 button application, but there are lots of other graphic features that are possible in Pygame. The only real difference with a pygame QPython app is that a #qpy:pygame reference is required at the top of the file. This reference is overlooking in Window/Linux/MacOS.

#qpy:pygame

import pygame
import socket

pygame.init()

def draw_button(button, screen):
    #Draw the button rect and the text surface
    pygame.draw.rect(screen, button['color'], button['rect'])
    screen.blit(button['text'], button['text rect'])

def create_button(x, y, w, h, bg, text, callback):
    # Create a buttondictionary of the rect, text,
    # text rect, color and the callback function.
    FONT = pygame.font.Font(None, 50)
    text_surf = FONT.render(text, True, pygame.Color('black'))
    button_rect = pygame.Rect(x, y, w, h)
    text_rect = text_surf.get_rect(center=button_rect.center)
    button = {
        'rect': button_rect,
        'text': text_surf,
        'text rect': text_rect,
        'color': pygame.Color(bg),
        'callback': callback,
        }
    return button

def main():
    screen = pygame.display.set_mode((640, 480))
    pygame.display.set_caption("Rasp Pi Interface: ")
    clock = pygame.time.Clock()    

    def bt_func(input):  # A callback function for the button.
        pygame.display.set_caption("Rasp Pi Interface: Send - " + input)
        print(input)

    button1 = create_button(100, 50, 250, 80,'blue','BLUE LED',lambda: bt_func('blue'))
    button2 = create_button(100, 150, 250, 80,'yellow', 'YELOW LED',lambda:bt_func('yellow'))
    button3 = create_button(100, 250, 250, 80,'red', 'RED LED', lambda: bt_func('red') )
    button4 = create_button(100, 350, 250, 80,'green', 'GREEN LED',lambda: bt_func('green'))
    
    # A list that contains all buttons.
    button_list = [button1, button2,button3, button4]

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
            # This block is executed once for each MOUSEBUTTONDOWN event.
            elif event.type == pygame.MOUSEBUTTONDOWN:
                # 1 is the left mouse button, 2 is middle, 3 is right.
                if event.button == 1:
                    for button in button_list:
                        # `event.pos` is the mouse position.
                        if button['rect'].collidepoint(event.pos):
                            # Increment the number by calling the callback
                            # function in the button list.
                            button['callback']()


        screen.fill(pygame.Color('white'))
        for button in button_list:
            draw_button(button, screen)
        pygame.display.update()
        clock.tick(30)

main()
pygame.quit()

When the Pygame application is running the buttons will send toggle the display caption and write a print() message.

rasp_app_linux

Socket Communications

Sockets are a simple way to have multiple devices communicate together. For this example the Raspberry Pi will be a Socket Server and a PC, Linux node or an Android phone could be socket clients.

# Simple Python Socket Server
#
import socketserver

class Handler_TCPServer(socketserver.BaseRequestHandler):
    # Handler to manage incoming requests
    def handle(self):
        # self.request - TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        # self.data - is the incoming message
        print("{} sent:".format(self.client_address[0]))
        print(self.data)
        # if required a request could be send back
        #self.request.sendall("ACK from TCP Server".encode())

if __name__ == "__main__":
    # Define the host and port to use
    HOST, PORT = "192.168.0.133", 9999

    # Init the TCP server object, bind it to the localhost on 9999 port
    tcp_server = socketserver.TCPServer((HOST, PORT), Handler_TCPServer)
    print("Socket Server Started on : " + HOST + " port: " + str(PORT))
    # Activate the TCP server.

    tcp_server.serve_forever()

A simple Python TCP socket client would be:

import socket

host_ip, server_port = "192.168.0.133", 9999
data = " Hello how are you?\n"

# Initialize a TCP client socket using SOCK_STREAM
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    # Establish connection to TCP server and exchange data
    tcp_client.connect((host_ip, server_port))
    tcp_client.sendall(data.encode())

    # Read data from the TCP server and close the connection
    received = tcp_client.recv(1024)
finally:
    tcp_client.close()

print ("Bytes Sent:     {}".format(data))
print ("Bytes Received: {}".format(received.decode()))

Put It Together

Once the socket communications and the Pygame interface is working, the full program can be completed. The first step is to define some output LED pins on the Raspberry Pi. For my project I used a Pimoroni Explorer Hat Pro. This Pi top had some built in colored LED pins.

# Pi Socket Server to toggle 4 color LEDs
import socketserver

class Handler_TCPServer(socketserver.BaseRequestHandler):
    # Handler to manage incoming message requests

    def handle(self):
        # self.request - TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} sent:".format(self.client_address[0]))
        print(self.data)
        if self.data == "red":
            GPIO.output(redpin, not GPIO.input(redpin))
        if self.data == "yellow":
            GPIO.output(yellowpin,not GPIO.input(yellowpin))  
        if self.data == "blue":
            GPIO.output(bluepin,not GPIO.input(bluepin))
        if self.data == "green":
            GPIO.output(greenpin,not GPIO.input(greenpin))            
        # just send back ACK for data arrival confirmation
        #self.request.sendall("ACK from TCP Server".encode())

if __name__ == "__main__":
    # Define 4 LED pins as outputs
    import RPi.GPIO as GPIO
    GPIO.setwarnings(False)
    GPIO.setmode(GPIO.BCM)
    yellowpin = 17
    bluepin = 4
    redpin = 27
    greenpin = 5
    GPIO.setup(redpin,GPIO.OUT)
    GPIO.setup(yellowpin,GPIO.OUT)
    GPIO.setup(bluepin,GPIO.OUT)
    GPIO.setup(greenpin,GPIO.OUT)
     
    HOST, PORT = "192.168.0.133", 9999

    # Init the TCP server object, bind it to the localhost on 9999 port
    tcp_server = socketserver.TCPServer((HOST, PORT), Handler_TCPServer)
    print("Socket Server Started on : " + HOST + " port: " + str(PORT))
    # Activate the TCP server.
    # To abort the TCP server, press Ctrl-C.
    tcp_server.serve_forever()

Next our Pygame interface needs to include  some TCP socket client code that sends the color message.

#qpy:pygame

import pygame
import socket

pygame.init()

def draw_button(button, screen):
    #Draw the button rect and the text surface
    pygame.draw.rect(screen, button['color'], button['rect'])
    screen.blit(button['text'], button['text rect'])

def create_button(x, y, w, h, bg, text, callback):
    # Create a buttondictionary of the rect, text,
    # text rect, color and the callback function.
    FONT = pygame.font.Font(None, 50)
    text_surf = FONT.render(text, True, pygame.Color('black'))
    button_rect = pygame.Rect(x, y, w, h)
    text_rect = text_surf.get_rect(center=button_rect.center)
    button = {
        'rect': button_rect,
        'text': text_surf,
        'text rect': text_rect,
        'color': pygame.Color(bg),
        'callback': callback,
        }
    return button

def main():
    screen = pygame.display.set_mode((640, 480))
    pygame.display.set_caption("Rasp Pi Interface: ")
    clock = pygame.time.Clock()    

    def bt_func(input):  # A callback function for the button.
        pygame.display.set_caption("Rasp Pi Interface: Send - " + input)
        print(input)
        host_ip, server_port = "192.168.0.133", 9999
        tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
        # Establish connection to TCP server and exchange data
            tcp_client.connect((host_ip, server_port))
            tcp_client.sendall(input.encode())
        finally:
            tcp_client.close()

    button1 = create_button(100, 50, 250, 80,'blue','BLUE LED',lambda: bt_func('blue'))
    button2 = create_button(100, 150, 250, 80,'yellow', 'YELOW LED',lambda: bt_func('yellow'))
    button3 = create_button(100, 250, 250, 80,'red', 'RED LED', lambda: bt_func('red'))
    button4 = create_button(100, 350, 250, 80,'green', 'GREEN LED',lambda: bt_func('green'))
    
    # A list that contains all buttons.
    button_list = [button1, button2,button3, button4]

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
            # This block is executed once for each MOUSEBUTTONDOWN event.
            elif event.type == pygame.MOUSEBUTTONDOWN:
                # 1 is the left mouse button, 2 is middle, 3 is right.
                if event.button == 1:
                    for button in button_list:
                        # `event.pos` is the mouse position.
                        if button['rect'].collidepoint(event.pos):
                            # Increment the number by calling the callback
                            # function in the button list.
                            button['callback']()


        screen.fill(pygame.Color('white'))
        for button in button_list:
            draw_button(button, screen)
        pygame.display.update()
        clock.tick(30)

main()
pygame.quit()

Loading the Application on Android

To load the application on my phone, I connected my phone to a PC and I move the file to the phone’s /Download directory. QPython files can be created/edited and run using the Editor button. New Python applications can be created using the PygameApp menu option.

It’s important to note that if you are using print() functions in your application then a log directory file location prompt will come up the first time that your app is run.

QPython applications can be made into a desktop short cut.

Once everything is configured, you should be able to click on the desktop QPython icon and start controlling the Raspberry Pi.

android2pi

 

 

 

Pi Network Monitoring

There are some great full featured networking packages like Nagios and MRTG that can be loaded on Raspberry Pi’s. If, however, you are looking for something smaller scale that you can play with then Node-Red might be your answer. Node-Red is a visual programming environment that allows users to create applications by dragging and dropping blocks (nodes) on the screen. Logic flows are then created by connecting wires between the different blocks (nodes). Node-Red also comes with Web Dashboards, so you can view data or do control from your smart phone.

In this blog we’ll look at:

  • running some SNMP commands
  • setup NodeRed for SNMP
  • making read/write SNMP values on the Pi

 

Getting Started with SNMP

Simple Network Management Protocol (SNMP) is the standard for communicating and monitoring of network devices. Common device information is grouped into MIBs or Management Information Bases. Data items are called OIDs or Object Identifiers. OIDs are referenced by either their MIB name or by their OID numeric name. So for example the SNMP device name object could be queried by either its MIB name of: SNMPv2-MIB::sysName.0 or the object identifier number of: .1.3.6.1.2.1.1.5.0.

To install both the SNMP monitor and server on your Pi enter:

sudo apt-get update
sudo apt-get install snmp snmpd snmp-mibs-downloader

To show meaningful MIB names, you will need to modify the SNMP config file by:

sudo nano /etc/snmp/snmp.conf

The first line should be commented out, it should just read: #mibs .

There are many configuration options in the SNMP server agent that need to be considered. For a real/product system you will need to consider your user security but for a test system we can open up the Pi by:

sudo nano /etc/snmp/snmpd.conf

Then uncomment the agentAddress line so that all interfaces are open, and in the Access Control section comment out all the existing user access and add a new line with public access set to read/write (definitely not recommended in a real system):

# Listen for connections on all interfaces (both IPv4 *and* IPv6)
agentAddress udp:161,udp6:[::1]:161

# ACCESS CONTROL
#
# Set read/write access to public anywhere
#
rwcommunity public

After saving the changes to snmpd.conf, the service needs to be restarted:

sudo service snmpd restart

There are a number of useful SNMP command line programs, such as:

  • snmpget – gets a SNMP message for a specific OID
  • snmpset – sets a SNMP OID (OID needs to be writeable)
  • snmpwalk – gets multiple OID values in a MIB tree

The basic syntax for these commands is:

command -c community -v version node OID

To test that SNMP is working, enter the following:

pi@raspberrypi:~ $ snmpwalk -c public -v 1 localhost .1.3 

SNMPv2-MIB::sysDescr.0 = STRING: Linux raspberrypi 4.4.21-v7+ ...
SNMPv2-MIB::sysObjectID.0 = OID: NET-SNMP-MIB::netSnmpAgentOIDs.10
DISMAN-EVENT-MIB::sysUpTimeInstance = Timeticks: (319234) 0:53:12.34
SNMPv2-MIB::sysContact.0 = STRING: Me <me@example.org>
SNMPv2-MIB::sysName.0 = STRING: raspberrypi
...

If SNMP is working correctly a very long list of SNMP objects will be shown.

Get a Specific SNMP Result

There are thousands of SNMP results that can be read. Some of the more common and useful SNMP are:

1 minute CPU Load: .1.3.6.1.4.1.2021.10.1.3.1
5 minute CPU Load: .1.3.6.1.4.1.2021.10.1.3.2
15 minute CPU Load: .1.3.6.1.4.1.2021.10.1.3.3

Idle CPU time (%): .1.3.6.1.4.1.2021.11.11.0

Total RAM in machine: .1.3.6.1.4.1.2021.4.5.0
Total RAM used: .1.3.6.1.4.1.2021.4.6.0
Total RAM Free: .1.3.6.1.4.1.2021.4.11.0

Total disk/partion size(kBytes): .1.3.6.1.4.1.2021.9.1.6.1
Available space on the disk: .1.3.6.1.4.1.2021.9.1.7.1
Used space on the disk: .1.3.6.1.4.1.2021.9.1.8.1

An example to get the Idle CPU time with the SNMP command line tool would be:

$ snmpget -c public -v 1 localhost .1.3.6.1.4.1.2021.11.11.0

UCD-SNMP-MIB::ssCpuIdle.0 = INTEGER: 97

If the syntax is correct, a result is returned with the OID identifier, result type and the  result value.

Getting Started with NodeRed

NodeRed is pre-installed with the Raspbian images but it will need to have some SNMP and some support options loaded. At a terminal window enter the commands:

sudo apt-get update
sudo apt-get install npm
cd $HOME/.node-red
npm install node-red-node-snmp
npm install node-red-dashboard
npm install node-red-contrib-bigtimer

node-red-start &

Once Node-RED starts, you use a web browser to build applications. If you are working directly on your Pi, enter 127.0.0.1:1880 in the URL address box of your browser. You drop palettes from the left pane into the large flow window in the middle and wire them together in the correct order.

NodeRed Ping Monitor

A good starting program is to make a Web Dashboard that shows ping (node-to-node) delay times. The dashboards are defined in the right panel of Node-Red. A dashboard items are put into groups, and groups are put into tabs. Each tab will be shown as a separate page on your smart phone.

pinglogic

By double-clicking on the Ping node you can enter the different IP address.

pingnode

Similarly by double-clicking on the Chart node, you can define the label and look and feel of the chart.

chartnode

After the configuration is finished, click the Deploy button,  (top right on menu  bar). The Node-RED dashboard user interface is accessed by entering <IPaddress>:1880/ui (e.g., 192.168.1.102:1880/ui). Chart data values are shown by clicking on the chart line.

pingSS

NodeRed with SNMP Nodes

The ping node is quite simple and it returns just the ping value. The snmp node is more complex and it returns multiple pieces of information. To use snmp nodes in a Node-Red program you need some support nodes to parse/pass the payload messages. To send SNMP data to a chart dashboard, the following nodes are used:

  • Big Timer – to trigger the polling of data
  • snmp – gets SNMP/OID information
  • split – split the message into addressable variables
  • change – put the OID value into the message playload
  • chart – show the payload

snmplogic

The SNMP node configuration can get multiple values, a simple example to get the CPU free time is below (Note: the leading “.” is NOT included) :

snmpnode

The split node is used move the SNMP array data in a payload string. The change node is then used to move just the value into the payload.

changenode

The final step is to configure the charts to show the correct label information.

snmpSS

An SNMP Readable Pi Value

The Net-SNMP agent (snmpd) supports the creation of custom read/write objects (OIDs). The “Pass-through” MIB extension command in snmpd.conf allows for script files to be called.

Pass-through script files need to follow a few rules:

  • snmpget request passes a “-g” parameter (get)
  • the snmpget response needs to be 3 lines: OID, data type, and value
  • an snmpset request passes a “-s” parameter (set)

A simple example would be to define the Rasperry Pi board temperature as an SNMP object. The board temp is available by:

cat /sys/class/thermal/thermal_zone0/temp
46160

This returns the value in 1/1000s of a degree C. So to get just the Celsius temperature we can use:

echo $(($(cat /sys/class/thermal/thermal_zone0/temp) / 1000))
46

A SNMP bash script (/home/pi/pitemp), with our Pi CPU temp as OID  .1.3.6.1.4.1.8072.2.1 would be:

#!/bin/bash
if [ "$1" = "-g" ]
then
echo .1.3.6.1.4.1.8072.2.1
echo integer
echo $(($(cat /sys/class/thermal/thermal_zone0/temp) / 1000))
fi
exit 0

After the file is saved remember to make it executable (chmod +x pitemp).

In the “Pass-through” section of  /etc/snmp/snmpd.conf  a line is added to reference the new OID, shell and command:

#
# "Pass-through" MIB extension command
#
pass .1.3.6.1.2.1.8072.2.1 /bin/bash /home/pi/pitemp

After snmpd.conf is updated the snmpd service needs to restarted (sudo service snmpd restart), then our Pi temp OID can be accessed by:

snmpget -c public -v 1 localhost .1.3.6.1.2.1.8072.2.1
NET-SNMP-EXAMPLES-MIB::netSnmpExampleScalars = INTEGER: 47

Pi Writable SNMP GPIO Value

My goal was to use SNMP to turn on and off powered devices, so for this I used a PowerSwitch Tail II, however simple low cost relays could also be used.

The PowerSwitch Tail II ($26) is a power cord that is enabled/disabled with I/O pins. The PowerSwitch pins connect to the Pi pins 6 and 12.

pi_setup

The gpio tool is used to read and write to GPIO pins.  GPIO 1 is made writable by:

gpio mode 1 out

The SNMP script (/home/pi/powerswitch) to read and write to GPIO pin 1 (physical pin 12) is:

#!/bin/bash
if [ "$1" = "-g" ]
then
echo .1.3.6.1.2.1.8072.2.2
echo integer
gpio read 1
fi

if [ "$1" = "-s" ]
then
gpio write 1 $4
fi

exit 0

This new script file needs to made executable by: chmod +x powerswitch. The powerswitch script file is referenced in the SNMP server configuration file (/etc/snmp/snmpd.conf ):

#
# "Pass-through" MIB extension command
#
pass .1.3.6.1.2.1.8072.2.1 /bin/bash /home/pi/pitemp
pass .1.3.6.1.2.1.8072.2.2 /bin/bash /home/pi/powerswitch

Once again the smnpd needs to be restarted. Our read/write actions can be tested by:

$ snmpget -c public -v 1 localhost .1.3.6.1.2.1.8072.2.2
SNMPv2-SMI::mib-2.8072.2.2 = INTEGER: 0
$ snmpset -c public -v 1 localhost .1.3.6.1.2.1.8072.2.2 i 1
SNMPv2-SMI::mib-2.8072.2.2 = INTEGER: 1
$ snmpget -c public -v 1 localhost .1.3.6.1.2.1.8072.2.2
SNMPv2-SMI::mib-2.8072.2.2 = INTEGER: 1

NodeRed Setting SNMP Values

The exec node can be used to call the snmpset command.  A example with an ON and OFF button is:

gpio_logic

The configuration for the exec node is:

exec_node

And the web dashboard is:

gpioSS

Summary

Network monitoring and SNMP is huge topic, hopefully this will give you a good start.

If you are looking for a lightweight approach to monitoring older linux servers with Node-Red take a look at using remote SSH with command line tools.