Serial IO Example - RFID Cat Flap

NB Server persisted data storage has not yet been implemented. This example demonstrates various techniques but cannot be used verbatim until persisted storage is available.

This example will demonstrate use of a UART serial port and persistent memory. It also explores object oriented techniques such as context binding for callback handlers.

We'll create code for an imaginary Impee:

  • Intelligent cat flap
    1. RFID chip reader module (connected by asynchronous serial)
    2. Infra-Red cat presence detector
    3. Electric latch to release flap
    4. Programming button

The purpose of the impee is to identify cats by their pet RFID chip (ISO11784) and allow them to enter only if their ID has been previously registered. This will keep out strays and allow for the tracking of authorised pets.

When a cat enters scanning range an IR beam is broken, causing a GPIO input pin to change state (high = beam broken).

The RFID reader is powered up via a GPIO output pin - while powered up it continuously scans for RFID chips, returning the ID via serial when discovered. The protocol is:

Successful scan: 0x02 XX XX XX XX XX XX 0x0A 0x0D (XX = ASCII hex representation of code, length may vary)
Leaving range:   0x1B

The RFID reader is powered down when not in use to save power. The project would most likely be battery powered, so is power sensitive.

A solenoid latch releases the flap when energised, allowing the cat to pass through the flap. The solenoid circuit is connected to a GPIO output pin.

Finally a programming button may be pressed by the user to indicate that the next chip scanned is an authorised pet. This button is connected to a GPIO input pin and requires a pull up. It drives low when pressed. The authorised ID will be saved to non volatile memory.

The cat flap will send the index of the authorised cat to a node port each time the flap is released. We'll also write this information to the server log. In this way the owner may monitor cat activity or, perhaps, send an SMS message when a particular pet enters the house.

The flap will accept an override code on an input port to allow the flap to be force locked or unlocked remotely, for example from a web page or phone app.

An extension to the project might allow for controlling and tracking egress as well as ingress, so the owner could program the flap to trap a particular cat inside or outside the house, and review its current whereabouts remotely. This would require a different arrangement of sensors.

The Code

// Electric Imp code example - RFID Cat Flap

We'll derive from InputPort for our input class. This will save lock mode commands to its parent's member data. A reference to the parent is passed to the constructor.

// Input class for accepting lock override commands
class inputFlap extends InputPort
{
    parentClass = null;
 
    // Our constructor will save its parent class reference
    constructor(parent)
    {
        base.constructor("Control", "number");
        parentClass = parent;
    }
 
    // Override set function to receive lock mode messages
    function set(mode)
    {
        // Save the mode if valid
        if(mode >= 0 && mode <= 2) parentClass.lockMode = mode;
    }
}

We'll make a class to represent the RFID reader. This will be constructed with references to the serial port and power enable pin to use, and to a callback function to which we'll pass received RFID codes.

// RFID reader device class
class rfidDevice
{
    eventHandler = null;        // Callback function to handle ID read events
 
    serPort = null;             // Serial port to which RFID reader is attached
    powerEn = null;             // Pin which controls RFID power
 
    enabled = false;            // Reader enabled flag
    rxData = "";                // Received data
 
    // Constructor takes hardware assignments and callback handler reference
    constructor(port, power, handler)
    {
        eventHandler = handler;
        serPort = port;
        powerEn = power;
 
        // RFID reader requires 9600bps, 8 bits, no parity, 1 stop bit, no flow control
        serPort.configure(9600, 8, PARITY_NONE, 1, NO_CTSRTS);
 
        // Power enable line driven push-pull (high = enabled)
        powerEn.configure(DIGITAL_OUT);
 
        // Start offline
        disable();
    }
 
    // Enable RFID reader and start polling
    function enable()
    {
        rxData = "";
        powerEn.write(1);
        enabled = true;
        poll();
    }
 
    // Disable RFID reader
    function disable()
    {
        enabled = false;
        powerEn.write(0);
    }
 
    // Poll for data from RFID reader
    function poll()
    {
        if(enabled)
        {

Here we call imp.wakeup to schedule the next poll for serial data, however we cannot pass a simple reference to the method. While this would work fine for a free (top level) function, to pass this member function we must bind its environment (this object) to ensure that it has the correct context when the callback occurs. Squirrel provides a method bindenv for this purpose, which we will use whenever passing member functions when a free function parameter is expected (such as most callback parameters).

            // Schedule the next poll in 100ms
            imp.wakeup(0.1, poll.bindenv(this));
 
            // Read a byte
            local byte = serPort.read();
 
            // Continue reading while data is available
            while(byte != -1)
            {
                switch(byte)
                {
                    case 0x02 : // Start of data
                    case 0x1B : // Ignore out of range
                        rxData = "";
                        break;
 
                    case 0x0A: // Data complete
                        eventHandler(rxData);
                        rxData = "";
                        break;
 
                    case 0x0D:  // Ignore CR
                        break;
 
                    default:    // ID byte
                        rxData += byte;
                        break;
                }
 
                // Get the next byte
                byte = serPort.read();
            }
        }
    }
}

We'll make a class to represent the catflap product, bringing together all of its functionality.

// Cat flap class represents our example product
class catFlap
{
    output = null;          // Output for reporting feline ingress activity
    input = null;           // Input for accepting lock override commands
 
    rfid = null;            // RFID device
 
    detect = null;          // Cat proximity detect input pin, 1 = beam broken
    prog   = null;          // Program button input pin, 0 = pressed
    power  = null;          // RFID power control output pin, 1 = power on
    latch  = null;          // Solenoid latch control output pin, 1 = latch released
 
    lockMode = 0;           // Lock mode: 0 = Normal, 1 = Force Unlocked, 2 = Force Locked
    programMode = false;    // Program mode flag
    state = 0;              // Current state: 0 = Idle, 1 = Scanning
 
    // Constructor takes device assignments
    constructor(rfidDev, detectDev, progDev, powerDev, latchDev)
    {
        // Save device assignments
        detect = detectDev;
        prog = progDev;
        latch = latchDev;
 
        // Create RFID device
        rfid = rfidDevice(rfidDev, powerDev, rfidEvent.bindenv(this))
 
        // Configure GPIOs
        detect.configure(DIGITAL_IN_PULLUP, detectEvent.bindenv(this));
        prog.configure(DIGITAL_IN_PULLUP, progEvent.bindenv(this));
        latch.configure(DIGITAL_OUT);
 
        // Create output and input objects
        output = OutputPort("Activity", "number");
        input = inputFlap(this);
 
        // Register with the server
        imp.configure("Cat Flap", [input], [output]);
    }
 
    // Handle RFID code received event
    function rfidEvent(id)
    {
        // Disable the RFID scanner
        rfid.disable();
 
        // If programming, add this ID to the authorised table
        if(programMode)
        {
            nv[id] <- ++nv["kittyIdx"];
            programMode = false;
            server.log("Authorised ID " + id + " as cat " + nv[id] + ".");
        }
 
        // If ID is authorised, consider opening the latch
        if(id in nv)
        {
            if(lockMode == 2)
            {
                // Forced locked, log but do not open the latch
                server.log("Locked against cat " + nv[id] + ".");
            }
            else
            {
                // Open the latch
                latch.write(1);
                imp.wakeup(2.0, latchEvent.bindenv(this));
                server.log("Opened for cat " + nv[id] + ".");
            }
        }
    }
 
    // Handle latch period elapsed event
    function latchEvent()
    {
        // Close the latch
        latch.write(0);
 
        // New state: Idle
        state = 0;
    }
 
    // Handle program button event
    function progEvent()
    {
        // Update the program mode flag (set when pin is low)
        if(prog.read() == 0) programMode = true;
    }
 
    // Handle cat proximity event
    function detectEvent()
    {
        // Get beam state
        local beam = detect.read();
 
        if(beam == 1)
        {
            // Beam broken, animal is present
            if(lockMode == 1)
            {
                // Forced open, no need to read chip
                latch.write(1);
                imp.wakeup(2.0, latchEvent.bindenv(this));
            }
            else
            {
                // Check for a new beam break
                if(state == 0)
                {
                    // New state: Scanning
                    state = 1;
 
                    // Enable the RFID scanner
                    rfid.enable();
                }
            }
        }
        else if(beam == 0 && state == 1)
        {
            // Cat has departed, disable the RFID scanner
            rfid.disable();
 
            // New state: Idle
            state = 0;
        }        
    }
}

We'll record authorised cat IDs in the nv table. This is a special global table which persists across power cycles (it is flushed to the cloud when the script sleeps, and retrieved at each start up). Every Imp has its own unique nv table per script executed upon it.

NB Persistence across power cycles is a planned feature which has not been implemented at time of writing. Details of the implementation may change.

The first time the script runs on an imp it is necessary to create its nv table. A short sleep is included to force sync the table to the cloud.

// Check whether non-volatile table has been created
if(!("nv" in this))
{
    nv <- { kittyIdx = 0 };
    server.sleepfor(1);
}

We'll create one slot in the table “kittyIdx” as an index counter of authorised cats. We'll check for this at start up to determine whether the nv table has already been initialised.

// Create index slot if not present
if(!("kittyIdx" in nv))
{
    nv["kittyIdx"] <- 0;
}

Each authorised cat will have one slot in the table identified by its chip ID and containing its cat index. We'll log this at start up as a convenience for advanced users.

// Log the authorised ID list at start up
server.log("Authorised ID list:");
foreach(k,v in nv)
{
    if(k != "kittyIdx") server.log(v + " : " + k);
}

Finally we instantiate our catflap object. Instead of using a local we'll create our object as a table. This is necessary because there are no other strong references to the object; it would otherwise go out of scope when execution reached the end of the script. You might think that the callback parameters passed to the pin.configure and imp.wakeup methods would qualify, however the bindenv method stores only a weak reference to the environment object, in this case our class instantiation. Instantiating to a table adds the object to the root environment, keeping it in scope for the whole run time.

// Create a cat flap instance
thisCatFlap <- catFlap(hardware.uart1289, hardware.pin1, hardware.pin2, hardware.pin8, hardware.pin9);
 
// End of code.

For the full code listing click here.

examplerfid.txt · Last modified: 2012/08/16 23:52 by rob
 
Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki