Aegidius
 Plüss   Aplulogo
     
 www.aplu.ch      Print Text 
© 2021, V10.4
 
  Bluetooth Library
 
RaspiBrick: The Basic Concepts
 

 

Three Modes of Operation

The RaspiBrick firmware has been designed to support essentially 3 different programming modes:

  • Python programs running locally on the brick
    Called Autonomous Python Mode


  • Java Programs running locally on the brick
    Called Autonomous Java Mode


  • Programs written in any programming language (Python, Java, C#, C/C++, Objective-C, Scratch, etc.) running on a remote device (PC, Smartphone, Raspberry Pi, Arduino, etc).
    Called Remote Python Mode, Remote Java Mode, etc.

Moreover there are tools to download and start the autonomous programs without the need of a file transfer utility nor a remote SSH terminal/virtual desktop. With RaspiBrick you can build a real autonomous robot that runs applications without any locally or remotely attached device.


RaspiPyLib: The Kernel Robotics Library written in Python

Because the language of choice for the Raspberry Pi is Python (accessing low-level C routines), a Python Library RaspiPyLib was developed that uses the well-known Python modules for the GPIO and camera ports. Some third party Python code from Adafruit and others for the I2C and PWM support are integrated. They are taken as is without any modifications.

  raspibrick45

RaspiPyLib has the same neat object-oriented design like other libraries developed during the recent years for the Lego NXT and EV3 robotics hardware (NxtJLib, EV3JLib) with the difference that these older libraries are written in Java.

RaspiPyLib shields the user from the low-level code to access the GPIO, the I2C and camera devices. So Python scripts are simplified by an order of magnitude to run simple robotics applications like rotating a motor, displaying information on the 7-segment display or reading I2C- based sensors.

 

RaspiJLibA: A Failed Approach

Because we used Java for many years in our robotics classes with pleasure and success, and also because there are many colleagues all over the world that like our OOP based Java robotics libraries for the Lego NXT and EV3, we wanted to support Java on the RaspiBrick robot too. It is obvious to apply the same software design as for Python: The well-known Java GPIO Pi4J library (based on JNI) makes the link between the hardware and our Java library.

  raspibrick45

The work was almost accomplished when we realized that the support for the ultrasonic sensor failed, because pulse length of around 1 ms must be measured with high precision. This is just an impossible task for Java SE because of the overhead of internal processes and the JNI. So we had to look for another solution to support Java.

 


BrickGate:
A TCP/IP Socket Server Gateway

The idea was to still use the successful Python RaspiPyLib library, but link it to Java by a TCP/IP socket client/server. A socket server called BrickGate written in Python runs locally on the brick and uses RaspiPyLib to access the hardware. BrickGate listens for IP clients connecting to a specific IP port (1299). Once connected, the client sends coded information to the BrickGate server (typically commands for the actuators) and reports results (typically sensor values). Because an IP localhost connection can be used, Java and Python can run at the same time on the same system.

  raspibrick45

The advantage if this concept is evident: Because an IP link is used, the client may either run on the brick itself or on a remote client located anywhere on the Internet and written in any programming language that support TCP/IP clients.

  raspibrick45

 

BrickGate: Command Protocol

The BrickGate server is fully written in Python and uses the classes of the RaspiBrick Python library extensively. The program behaves like an autonomous Python robotics program with the addition of a socket server and a parser that interprets and executes the incoming commands.

All communications from the client to the BrickGate server (called commands) and back to the client (called responses) use a human readable text based protocol (strings). Commands consist of 2, 3, 4 or 5 fields separated by a colon and must be terminated by a newline character (the linefeed character is used on the server side as end-of-command indicator). The general format is:

<device>.<method>.<param1>.<param2>.<param3>

Device field:

Device Name
Robot robot
Left DC motor, right DC motor mot1, mot2
Gear gear
Servo motor at header
S12, S13, S14, S15
svo0, svo1, svo2, svo3
Light sensor
front left, front right, rear left, rear right
lss0, lss1, lss2, lss3
Ultrasonic sensor uss
7-segment display display
Camera cam

The param fields may be omitted or set to n, if no parameter is needed. Prior to invoke a method of a device, the device object must be created by sending

<device>.create

(with the exception of the robot device, that is always created by default)

The method and parameter fields corresponds to the autonomous Python library and are invoked by calling the Python eval() function. Example:

The command gear.forward(2000) is sent by the client program as "gear.forward.2000" . The BrickGate server splits the fields, creates a new Gear object gear (if not yet done) and invokes eval("gear.forward(2000)").

As you see this is similar to remote method invocation (RMI), The BrickGate server in Java uses reflection for the same task.

Every command execution will be confirmed by the server by sending back a response string. Until the response is received no other command should be sent to the server. This provides a simple handshaking between client and server. The response is normally an integer in string format with the following meaning:

Responses:

Response
(converted to int)
Meaning
>= 0
Data from a sensor
0 Command successfully executed
-1 Send failed
-2 Illegal method
-3 Illegal instance
-4 Command error
-5 Instance creation failed

If the command invokes a method with a boolean return type, the response is "0" for false and "1" for true.

 

A Linux Console For Testing Remote Commands

For testing purposes and during development of your own direct mode client library, a console program where you can send single commands and get the responses may be of great help. As a generic hint how to develop direct-mode programs in any programming language, refer to the following program written in good-old plain-C:

#include <stdio.h>
#include 
<stdlib.h>
#include 
<unistd.h>
#include 
<string.h>
#include 
<sys/types.h>
#include 
<sys/socket.h>
#include 
<netinet/in.h>
#include 
<netdb.h> 

void
 error(const char *msg)
{
    
perror(msg);
    
exit(0);
}

int
 main(int argc, char *argv[])
{
    
int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent 
*server;

    
char buf[256];
    
if (argc < 3) 
    
{
        
fprintf(stderr, "usage %s hostname port\n", argv[0]);
        
exit(0);
    
}
    portno 
= atoi(argv[2]);
    sockfd 
= socket(AF_INET, SOCK_STREAM, 0);
    
if (sockfd < 0) 
        
error("ERROR opening socket");
    server 
= gethostbyname(argv[1]);
    
if (server == NULL) 
    
{
        
fprintf(stderr, "ERROR, no such host\n");
        
exit(0);
    
}
    
bzero((char *)&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family 
= AF_INET;
    
bcopy((char *)server->h_addr, 
         
(char *)&serv_addr.sin_addr.s_addr,
         server
->h_length);
    serv_addr.sin_port 
= htons(portno);
    
if (connect(sockfd,(struct sockaddr *&            
               
 serv_addr,sizeof(serv_addr)) < 0) 
        
error("ERROR connecting");
    
while (1)
    
{
        
printf("Please enter a command: (^C to quit): ");
        
bzero(buf, 256);
        
fgets(buf, 255, stdin);
        n 
= write(sockfd, buf, strlen(buf));
        
if (n < 0) 
            
error("ERROR writing to socket");
        
bzero(buf, 256);
        
int done = 0;
        
char response[4096];
        
bzero(response, 4096);
        
int k = 0;    
        
while (!done)
        
{
            n 
= read(sockfd, buf, 255);
            
if (n < 0) 
                 
error("ERROR reading from socket");
           
if (buf[n - 1] == 10)
               done 
= 1;
           
int i;
           
for (i = 0; i < n; i++)
               response
[k + i] = buf[i];
           k 
+= n;
        
}
        
printf("Response: %s\n", response);
    
}
    
close(sockfd);
    
return 0;
}

Under Linux/MacOS you compile the program in a terminal using the built-in gcc command line compiler:

gcc client.c -o client

and execute it with

./client 192.168.0.5 1299

 

A Java Terminal For Testing Remote Commands

Here a version in Java using the convenient Console window from the ch.aplu.util package. Every command execution also reports the response time, so you can convince yourself that the system is fast enough to be used in most robotics applications.

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import ch.aplu.util.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;

public class RaspiClientConsole
{
  class StreamReader extends Thread
  {
    private InputStream is;

    public StreamReader(InputStream is)
    {
      this.is = is;
    }

    public void run()
    {
      while (true)
      {
        String response = "";
        try
        {  
          response = readResponse(is);
        }
        catch (Exception ex)
        {
          System.out.println("Exception in readResponse: " + ex.getMessage());
          break;
        }  
        int rc = 0;
        try
        {
          rc = Integer.parseInt(response);
        }
        catch (NumberFormatException ex)
        {
        }
        if (rc >= 0)
        {
          System.out.print("Response: " + response);
          System.out.println(" in " + timer.getTime() / 1000 + " ms");
        }
        else
          System.out.println("Response (error): " + responseMsg[-rc]);
      }
    }

    private String readResponse(InputStream is) throws IOException
    {
      if (is == null)
        return "";
      byte[] buf = new byte[4096];
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      boolean done = false;
      while (!done)
      {
        int len = is.read(buf);
        if (len == -1)
          throw new IOException("Stream closed");
        baos.write(buf, 0, len);
        if (buf[len - 1] == 10)  // \n
          done = true;
      }
      String s = baos.toString("UTF-8");
      return s.substring(0, s.length() - 1);  // Remove \n
    }

  }

  String[] responseMsg =
  {
    "OK""SEND_FAILED""ILLEGAL_METHOD",
    "ILLEGAL_INSTANCE""CMD_ERROR""CREATION_FAILED"
  };

  private int port = 1299;
  private OutputStream os = null;
  private InputStream is = null;    
  private HiResTimer timer = new HiResTimer();

  public RaspiClientConsole()
  {
    ch.aplu.util.Console c = new ch.aplu.util.Console();
    try
    {
      c.print("IP address? ");
      String ipAddress = c.readLine();
      System.out.println("Trying to connect to " + ipAddress);
      Socket s = new Socket(ipAddress, port);
      c.println("Connection established.");
      c.println("Enter command<cr>");
      os = s.getOutputStream();
      is = s.getInputStream();
      StreamReader sr = new StreamReader(is);
      sr.start();
      String command = "";
      while (true)
      {
        command = c.readLine();
        if (command.length() == 0)
        {
          System.out.println("Illegal command");
          continue;
        }
        timer.start();
        sendCommand(command);
      }
    }
    catch (Exception ex)
    {
      c.println("Connection failed");
    }
  }

  private void sendCommand(String cmd) throws IOException
  {
    if (cmd == null || cmd.length() == 0 || os == null)
      throw new IOException("sendCommand failed.");
    cmd += "\n";  // Append \n
    byte[] ary = cmd.getBytes(Charset.forName("UTF-8"));
    os.write(ary);
    os.flush();
  }

  public static void main(String[] args)
  {
    new RaspiClientConsole();
  }
}

Execute the program locally using WebStart

Sample session:

raspibrick11

Your find information about all classes and their methods here.

At the moment three remote libraries are available (download see right column). If you want to implement a remote library for another programming language, the easiest way is to download and port the Python or Java library.

  • RaspiPyLib (Python V2.7)

  • RaspiJlib (Java)

  • RaspiAndroidLib (Java for Android)