NxtLib
 
 

 

Sixth example: Feedback control (NxtJLibA autonomous mode)

Purpose: Use the NXT brick as a controller in a feedback control system that keeps the luminosity of a light bulb at a predefined value.

Control theory is an interesting interdisciplinary branch that deals with the behavior of dynamical systems. The desired output of a system is called the reference (Sollwert). The current value from the system measured by a sensor (Istwert) is fed back and compared with the reference. The difference (error value) is fed to the controller that controls the system to keep the error as small as possible. The controller's algorithm determines strongly the time behavior of the system output.

 

Fig. 1: General case

In our example the system consists of a light bulb attached to a standard light regulator that is rotated by a NXT motor. The motor axe is coupled to the light regulator with a (double) heat shrink tube.

Fig. 2: NXT motor coupled to a standard light regulator

The sensor is a NXT light sensor attached the NXT brick. The NXT reads the light value. If the luminosity is smaller than the reference value, the NXT rotates the regulator so that the light becomes brighter, if the luminosity is greater than the reference value, the NXT rotates the regulator in the reverse direction.

Fig. 3: Light control system using a Lego NXT brick

When using a simple brute force algorithm to control the system, the program becomes straightforward.

// LightRegulator.java
// pos = 0..300

import ch.aplu.nxt.*;
import ch.aplu.util.*;
import lejos.nxt.Button;

public class LightRegulator
{
  private final int sollWert = 900;

  public LightRegulator()
  {
    NxtRobot robot = new NxtRobot();
    Motor mot = new Motor(MotorPort.A);
    robot.addPart(mot);

    LightSensor ls = new LightSensor();
    robot.addPart(ls);

    System.out.println("Initializing...");
    init(mot);
    int pos = 0;
    int istWert;
    System.out.println("ESCAPE to quit");
    while (!Button.ESCAPE.isPressed())
    {
      istWert = ls.getValue();
      System.out.println("pos " + pos + ",ist " + istWert);
      if (istWert > sollWert)
      {
        pos = pos - 5;
        if (pos < 0)
          pos = 0;
      }
      else
      {
        pos = pos + 5;
        if (pos > 300)
          pos = 300;
      }
      mot.continueTo(pos);
    }

    mot.continueTo(300);
    robot.exit();
  }

  private void init(Motor mot)
  {
    mot.setSpeed(5);
    mot.rotateTo(-300);
    mot.resetMotorCount();
  }

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

For the sake of a demonstration we use a second NXT to lower the luminosity periodically by moving a colored plastic sheet in front of the light bulb.

     
  Feedback Control Video (Flash)  

Discussion: The control process is not very accurate due to the rough steps. The typical oscillations of the control system are clearly visible.

 

Seventh example: Remote data collection (NxtJLibA autonomous mode)

Purpose: Use the NXT brick as a wireless remote measuring device and transfer data to a PC via Bluetooth. In this example data is collected periodically every 100 ms and shown in a simple graph.

For the purpose of the demonstration the NXT uses a light sensor, but it may be replaced by any other measuring device (temperature sensor, distance sensor, etc). The NXT runs as a Bluetooth server and waits for a PC client to connect. After the connection is established, the NXT waits in a loop to get a single character from the PC that signals that a new value is requested. The NXT then performs the measurement and sends the value back to the PC.

// BtAcquire.java
// Remote measuring device. Data transfered to a PC client.
// This is a Bluetooth server waiting for a connecting client.
// When the PC client disconnects, the server should wait for the
// next client to connect.
// For a demo, run NxtAcquire.java from ch.aplu.bluetooth distribution

import
 ch.aplu.bluetooth.*;
import
 ch.aplu.nxt.*;
import
 lejos.nxt.Button;
import
 lejos.nxt.LCD;
import
 java.io.*;

public
 class BtAcquire implements BtListener
{
  
private final boolean isNxtClient = false;  // true, if client is a NXT brick
  
private NxtRobot robot = new NxtRobot();
  
private LightSensor ls = new LightSensor();
  
private BluetoothServer bs;

  
public BtAcquire()
  
{
    robot.
addPart(ls);
    LCD.
drawString("Server started", 0, 0);
    LCD.
drawString("Waiting...", 0, 1);
    LCD.
drawString("Press ESCAPE", 0, 5);
    bs 
= new BluetoothServer(this, isNxtClient);
    
while (!Button.ESCAPE.isPressed()) {}
    bs.
cancel();
    robot.
exit();
  
}

  
public void notifyConnection(InputStream is, OutputStream os)
  
{
    LCD.
drawString("Connection OK", 0, 1);
    
DataInputStream dis = new DataInputStream(is);
    
DataOutputStream dos = new DataOutputStream(os);
    robot.
playTone(600, 100);
    Tools.
delay(100);
    robot.
playTone(1000, 100);

    
while (true)
    
{
      
try
      
{
        dis.
readInt();   // Wait for request
      
}
      
catch (Exception ex)
      
{
        
if (!bs.isCanceled())
        
{
          
// Client disconnected, wait for next connect
          LCD.
clear();
          LCD.
drawString("Disconnected", 0, 0);
          LCD.
drawString("Waiting", 0, 1);
          LCD.
drawString("Press ESCAPE", 0, 5);
          robot.
playTone(1000, 100);
          Tools.
delay(100);
          robot.
playTone(600, 100);
        
}
        
break// Quit while loop
      
}

      
try
      
{
        
int value = ls.getValue();
        dos.
writeInt(value);
        dos.
flush();
        LCD.
drawString("Lux: " + value,  0, 2);
      
}
      
catch (IOException ex)
      
{}
    
}
  
}

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

Discussion: After installing the Bluetooth server the main process stays in a (not very efficient) loop until the ESCAPE button is hit. All work is done in the callback method notifyConnection() that is called when a client connects. Here we enter a data collection loop where we wait for any single incomming character that triggers the measuring process. The acquired data is sent back to the client as an integer value.

The Java program running an the PC uses a simple graphics window GPanel from the package ch.aplu.util for convenience. Any other more sophisticated graphics package may be used. It imports the package ch.aplu.bluetooth (from www.aplu.ch/bluetooth) that provides the Bluetooth communication facilities.

// NxtAcquire.java
// Get data from remote NXT in autonomous mode
// For a demo, run BtAcquire.java from the NxtJLibA distribution

import
 java.io.*;
import
 ch.aplu.bluetooth.*;
import
 javax.swing.JOptionPane;
import
 ch.aplu.util.*;

public
 class NxtAcquire implements ExitListener
{
  
private final int channel = 1;
  
private BluetoothClient bc;
  
private DataInputStream dis;
  
private DataOutputStream dos;
  
private GPanel g = new GPanel(0, 10000, 0, 1000);

  
public NxtAcquire()
  
{
    g.
addExitListener(this);
    g.
title("NXT Remote Data Acquiring");
    
drawGrid();
    
String prompt = "Enter NXT Bluetooth Name";
    
String init = "NXT";
    
String serverName;
    
do
    
{
      serverName 
= JOptionPane.showInputDialog(null, prompt, init);
      
if (serverName == null)
        
System.exit(0);
    
}
    
while (serverName.trim().length() == 0);

    bc 
= new BluetoothClient(serverName, channel);
    g.
title("Trying to connect to " + serverName + "...");
    
if (!bc.connect())
    
{
      g.
title("Connection to " + serverName + " failed");
      
return;
    
}

    g.
title("Connection to " + serverName + " established");
    BaseTimer.
delay(2000);
    dis 
= new DataInputStream(bc.getInputStream());
    dos 
= new DataOutputStream(bc.getOutputStream());

    
int t = 0;  // in ms
    HiResAlarmTimer timer 
= new HiResAlarmTimer(100);
    
try
    
{
      
while (true)
      
{
        
while (timer.isRunning()) {}  // Wait until period is over
        timer.
start();
        dos.
writeInt(0);  // Request data

        dos.
flush();

        
int value = dis.readInt();
        g.
title("Time: " + t + " ms  Value: " + value);
        
if (t == 0)
        
{
          
drawGrid();
          g.
move(t, value);
        
}
        
else
          g.
draw(t, value);

        t 
+= 100;  // Time step

        
if (t == 10000)
          t 
= 0;
      
}
    
}
    
catch (Exception ex)
    
{}
    g.
title("Disconnected. Click [x] to quit");
    bc.
disconnect();
  
}

  
public void notifyExit()
  
{
    
if (bc.isConnected())
      bc.
disconnect();
    
else
      
System.exit(0);
  
}

  
private void drawGrid()
  
{
    g.
clear();
    
for (int x = 0; x <= 10000; x += 1000)
      g.
line(x, 0, x, 1000);
    
for (int y = 0; y <= 1000; y += 100)
      g.
line(0, y, 10000, y);
  
}

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


Discussion: Because the data should be acquired periodically with a precise period, a simple loop using a delay() or sleep() is not appropriate. We use a HiResAlarmTimer that runs (backward) in a background thread parallel to the loop code. When the loop code is accomplished, we wait for the remaining time until the timer reaches zero.

The acquired data is shown in a simple graphics windows that provides user defined double coordinates. We should be careful to terminate the program properly by calling disconnect(). This is established by implementing an ExitListener with notifyExit() that is called when the title bar's close button is hit.

When we walked around near a light bulb with the light sensor attached the the NXT brick, the following time dependency of the measured luminosity was displayed.

Using the same technique, measurements could be transfered to a mobile phone. NxtAquire.java is easily ported to J2ME using the package ch.aplu.gidlet. Instead of displaying the data, they could be stored in a file for later processing (data logger).

The PC or the mobile phone could also act as a remote logger for debugging information sent from the NXT program.