Apache Kafka with Node-Red

Apache Kafka is a distributed streaming and messaging system. There are a number of other excellent messaging systems such as RabbitMQ and MQTT. Where Kafka is being recognized is in the areas of high volume performance, clustering and reliability.

Like RabbitMQ and MQTT, Kafka messaging are defined as topics. Topics can be produced (published) and consumed (subscribed). Where Kafka differs is in the storage of messages. Kafka stores all produced topic messages up until a defined time out.

Node-Red is an open source visual programming tool that connects to Raspberry Pi hardware and it has web dashboards that can be used for Internet of Things presentations.

In this blog I would like to look at using Node-Red with Kafka for Internet of Things type of applications.

Getting Started

Kafka can be loaded on a variety of Unix platforms and Windows.  A Java installation is required for Kafka to run, and it can be installed on an Ubuntu system by:

apt-get install default-jdk

For Kafka downloads and installation instructions see: https://kafka.apache.org/quickstart. Once the software is installed and running there a number of command line utilities in the Kafka bin directory that allow you to do some testing.

To test writing messages to a topic called iot_test1, use the kafka-console-producer.sh  command and enter some data (use Control-C to exit):

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic iot_test1
11
22
33

To read back and listen for messages:

 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic iot_test1 --from-beginning
11
22
33

The Kafka server is configured in the /config/server.properties  file. A couple of the things that I tweeked in this file were:

# advertised the Kafka server node ip
advertised.listeners=PLAINTEXT://192.168.0.116:9092
# allow topics to be deleted
delete.topic.enable=true

Node-Red

Node-Red is a web browser based visual programming tool, that allows users to create logic by “wiring” node blocks together.  Node-Red has a rich set of add-on components that includes things such as: Raspberry Pi hardware, Web Dash boards, email, Tweeter, SMS etc.

Node-Red has been pre-installed on Raspbian since 2015. For full installation instructions see:  https://nodered.org/#get-started

To add a Node-Red component select the “Palette Manager”, and in the Install tab search for kafka. I found that the node-red-contrib-kafka-manager component to be reliable (but there are others to try).

For my test example I wanted to create a dashboard input that could be adjusted. Then read back the data from the Kafka server and show the result in a gauge.

This logic uses:

  • Kafka Consumer Group – to read a topic(s) from a Kafka server
  • Dashboard Gauge – to show the value
  • Dashboard Slider – allows a user to select a numeric number
  • Kafka Producer – sends a topic message to the Kafka server

nodered_kafka Double-click on the Kafka nodes and in the ‘edit configuration’ dialog create and define a Kafka broker (or server). Also add the topic that you wish to read/write to.

kafka_consume

Double-click on the gauge and slider nodes and define a Dashboard group. Also adjust the labels, range and sizing to meet your requirements.

kafka_gauge

After the logic is complete hit the Deploy button to run the logic. The web dashboard is available at: http://your_node_red_ip:1880/ui.

kafka_phone

Final Comment

I found Node-Red and Kafka to be easy to use in a simple standalone environment. However when I tried to connect to a Cloud based Kafka service (https://www.cloudkarafka.com/) I quickly realized that there is a security component that needs to be defined in Node-Red. Depending on the cloud service that is used some serious testing will probably be required.

 

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
  • basic color support

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 a 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

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 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.

Final Comments

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

Pi Sailboat

My daughters and I have built a number of boat projects with an assortment of Arduino, ESP-8266, Bluetooth and RFI components. I believe that this version using a Raspberry Pi and NodeRed offers one of the simplest solutions. This sailboat used a basic catamaran design with a Raspberry Pi mounting inside a waterproof container. Using NodeRed dashboards you can control the sailboat’s rudder from a smart phone. The complete NodeRed logic consisted of only 6 nodes.

Building the Sailboat

There are a lot of different building materials that you could choose from. K’Nex construction pieces are lighter than either Lego or Meccano and they allow you to create reasonably large structures with a minimal number of pieces. If you do not have access to K’Nex pieces then popsicle sticks and some card board would offer a good low cost solution.

To build the sailboat we used:
• K’Nex building pieces
• 4 plastic bottles
• 1 small plastic container with a lid
• String
• Duct tape
• Garbage bag
• Low torque servo
• Raspberry Pi Zero W or Pi 3
• Small USB phone charger

The base of the sailboat was a rectangular structure with 16 down facing K’Nex pieces that allowed plastic bottles to be duct taped in place.

boat_bottom

A few K’Nex pieces were used to create a compartment for the servo, and wire was used to secure the servo in place. A rudder was built by screwing a small piece of wood into the servo arm.

servobox

A garbage bag was cut to the required size and taped to the mast. The boom had a swivel connection to the mast and guide ropes were connected to both the boom and mast.

sailboat_details

Servo and Rudder Setup

Only very low torque servos can connected directly to Rasberry Pi GPIO pins.

Pi_servo_wiring

An example of a low torque servo would be the TowerPro SG90 ($4) that has a torque of 25.00 oz-in (1.80 kg-cm). If you have larger torque servos you will need to either use a custom Raspberry Pi servo hat (there are some good ones on the market), or you will need to use a separate power and ground circuit for the servo.

The wiringPi tool gpio can be used to control the servo. This package is pre-install in the Raspbian image, or it can be manually installed by:

sudo apt-get install -y wiringpi

Servos typically want a pulse frequency of 50 Hz, and the Raspberry Pi PWM (Pulse Width Modulation) pins have a frequency of 19200 Hz, so some range definitions and scaling is required:

gpio -g mode 18 pwm #define pin 18 as the PWM pin
gpio pwm-ms #use 'mark space' mode 
gpio pwmc 192 # set freq as 19200
gpio pwmr 2000 # use a range of 2000

The gpio pwm commands are not persistent after a reboot. A simple solution for this is to put these commands in the Pi user login file of: $HOME/.bash_login.

After the pwm setup commands are run you need to do some manual testing to define your different rudder (servo) positions (Figure 6), such as “Hard Left”, “Hard Right”, “Easy Left”, “Easy Right” and “Straight”. The pwr timing numbers will vary based on your requirements and servo arm positioning, for our sailboat we used:

gpio -g pwm 18 200 #straight
gpio -g pwm 18 260 #hard left
gpio -g pwm 18 140 #hard right
gpio -g pwm 18 230 #easy left
gpio -g pwm 18 170 #easy right

servo_settings

NodeRed Logic and Dashboards

NodeRed is pre-installed on the Raspbian image, but it will need to be set to autostart on a Pi reboot:  sudo systemctl enable nodered.service

NodeRed has a web configuration interface that is accessed by: http://localhost:1880 or http://pi_ip_address:1880.

On the options button (far right), by selecting: View -> Dashboard , you can define and change the web dashboard layouts.

dashboard

To create logic, nodes are selected from the left node panel and dragged and dropped on to the center flow panel. Logic flow are then created by clicking and joining together different inputs and outputs on the nodes. If a dashboard node is dropped on the flow panel it will be added to the default web dashboard. The gpio -g pwm commands can be called using the exec node. The button dashboard node will pass the defined payload value, for example a “Hard Left” 260 is passed when the button is pushed. The button’s payload value will be appended to the exec command to make a complete gpio -g pwm servo position command.

nodered

Once you’ve completed your logic setup press the Deploy button on the top right to make your configuration live and ready to test.

The final step is to enable a smart phone or tablet to connect to the Raspberry Pi, this can be done by either making the Raspberry Pi a WiFi access point or by tethering the Pi to a cell phone. There are some great guides on how to setup a Raspberry Pi as an access point. For this project the simple tethering method was used. Once the Pi is tethered to a phone, the PI’s IP address can be obtained from the hotspot users list.

pi_address

The NodeRed dashboard is accessed on your phone by: http://pi_ip_address:1880/ui .

nodered_ui

Assuming that everything is connected correctly you should be able to control the sailboard with your phone.

Summary

Once you’ve mastered the basic NodeRed and sailboat construction other projects such as motor boats, iceboats, airboats are possible.

airboat

 

 

 

Pi/Node-Red Car

The goal of the Pi/Node-Red car project was to create a small vehicle that can be controlled from a smart phone . For the project we used:

  • 1 Car chassis for Arduino ($15)
  • 1 Pimoroni Explorer HAT Pro  ($23)
  • 1 Portable microUSB charger
  • 1 USB WiFi Adapter
  • 4 short alligator clips and 4 connectors
  • Duct tape

The Arduino car chassis may require a small amount of assembly. Rather than soldering connections we like to use short alligator clips. It is not recommended to wire DC motors directly to a Raspberry Pi so the Pimoroni Explorer HAT Pro is used to connect the 2 DC motors.

The Raspberry Pi and the portable microUSB charger are secured to the top of the car chassis with duct tape. The left motor is wired to the motor 1 connectors on the Explorer Hat, and the right motor is wired to motor 2 connectors. Note you may have to do a little trial and error on the Explorer HAT “+” and “-” motor connections to get both wheels spinning in a forward direction.

The Explorer HAT Node-Red library is installed by:

 cd $HOME/.node-red
npm install node-red-dashboard 

The Web dashboard presentation is configured in the “dashboard” tab. For this example we create 2 groups: a control group to drive the vehicle, and a light group to turn on the Explorer Pro lights. Use the “+group” button to add a group, and the “edit” to change an existing group.
dash_conf

To control a motor, an “Explorer HAT” node and a dashboard button node are dropped and connected together. All the configuration is done in the button node . The button configure options are:

  • the group the button will appear in (Controls)
  • the size of the button (3×1 = 50% of width and narrow)
  • Topic, motor.one or motor.twois used for motor control
  • Payload, -100 = reverse, 0=stop, 100 = forward

Control_conf

The Explorer HAT has 4 colored LEDs. To toggle the LEDS, the topic is light.color with 1=ON, and 0=OFF . We thought that it would be fun to also add some Web dashboard button to control the colored lights.

light_conf

The Node-Red dashboard user interface is accessed by: ipaddress:1880/UI, so for example 192.168.1.102:1880/ui. Below is a picture that shows the final vehicle logic and the Web dashboard.

 

final_logic2