IoT on Azure: AzureBot - CodeProject

:

Introduction

This project is intended to give you an introduction to hosting a service in Azure. The end goal is to be able to control an inexpensive, but somewhat capable, robotic arm from a PC, with the robotic arm potentially located elsewhere in the world. I know, what you're thinking, "Why would I want to control a robotic arm, if it's somewhere else?" I don't have a good answer for that other than because you and can and from a computer geek perspective, it's kind of cool. That being said, this will hopefully give a beginner/novice in the IoT world, a platform to build upon to achieve much greater things while demonstrating the relative ease with which you too can make this work.

Download AzureBotSvc.zip

Background

The architecture for this project was kept very straightforward. The diagram below shows all of the relevant pieces and how they connect to one another.

 

                   

 

Parts needed before you begin:

1 - Robotic arm (I chose the OWI Robotic Arm Edge. It's inexpensive and only took a couple of hours to assemble)

1 - OWI Robotic Arm USB interface kit (I believe its purpose it self-evident)

1 - Raspberry Pi2 (RPi, also extremely inexpensive for the capability it brings to the table)

Technology used:

Azure Cloud Service - Used to host a simple service to provide the communication between the control application and the robotic arm.

SignalR - Communication library which makes spinning up and notification service quite easy with minimal code.

Mono - A port of the .Net framework to, in this case, the Linux distribution (Raspbian) used on the Raspberry Pi.

LibUsbDotNet - Excellent open-source library used to communicate between the Raspberry Pi and the robotic arm via USB.

Setup

Before using the code, you will need to install the Mono runtime on your RPi. Do this by typing the following at the command line on your RPi:

sudo apt-get update

sudo apt-get install mono

Using the code

AzureBotSvc

This project simply contains the configuration files, etc... needed to publish the AzureBotSvc to Azure. I did not modify this project in any way, it was fine out of the box.

AzureBotWebRole

This project is used to implement the service used to communicate between the WPF Remote Control App and the application running on the RPi listening for robot commands.

The Startup class is used by the SignalR library to get the server side up and running. It looks like this:

public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.MapSignalR();
        }
    }

The AzureBotHub class defines the communication methods used by the client and server for the robot commands and status updates.

public class AzureBotHub : Hub
{
    private ArmConnectionStatuses _armStatus = ArmConnectionStatuses.Disconnected;

    public void SendCommand(RobotCommand command)
    {
        Clients.All.SendCommand(command);
    }

    public void SendArmConnectionStatus(ArmConnectionStatuses armConnectionStatus)
    {
        _armStatus = armConnectionStatus;
        UpdateArmConnectionStatus(_armStatus);
    }

    public void RequestArmStatus()
    {
        UpdateArmConnectionStatus(_armStatus);
    }

    public void UpdateArmConnectionStatus(ArmConnectionStatuses armConnectionStatus)
    {
        Clients.All.UpdateArmConnectionStatus(armConnectionStatus);
    }
}

MonoBotControl

This is the project running on the RPi. It connects to the robotic arm via USB. It uses the LibUsbDotNet library to find, connect to and communicate with the arm. It also connects to the AzureBotSvc hosted in Azure to receive and execute the arm commands and also. publishes arm connection status updates to the service. 

The Program class handles connecting to the AzureBotSvc and the robotic arm. Connecting to the service is accomplished using:

_hubConnection = new HubConnection("http://azurebot.cloudapp.net/");
_hubProxy = _hubConnection.CreateHubProxy("AzureBotHub");
_hubProxy.On<RobotCommand>("SendCommand", rc => SendCommand(rc.ArmAction, rc.BaseRotateAction, rc.LightAction));
ServicePointManager.DefaultConnectionLimit = 10;
Console.Write("Connecting to AzureBot service...");
_hubConnection.Start().Wait(Timeout.Infinite);
Console.WriteLine("Connected");

The ConnectToArm and SendCommand methods are used for interacting with the arm:

private static bool ConnectToArm()
 {
     _sessionHandle = new MonoUsbSessionHandle();
     if (_sessionHandle.IsInvalid) throw new Exception("Invalid session handle.");

     _device = MonoUsbApi.OpenDeviceWithVidPid(_sessionHandle, VendorId, ProductId);
     _armConnectionStatus = _device == null ? ArmConnectionStatuses.Disconnected : ArmConnectionStatuses.Connected;

     Console.WriteLine("Arm connection status:{0}", _armConnectionStatus);

     return _armConnectionStatus == ArmConnectionStatuses.Connected;
 }


 private static int SendCommand(ArmActions firstByte, BaseRotateActions secondByte, LightActions thirdByte)
 {
     if (_device != null)
     {
         var cmd = new byte[3];
         cmd[0] = (byte)firstByte;
         cmd[1] = (byte)secondByte;
         cmd[2] = (byte)thirdByte;

         var pntr = Marshal.AllocHGlobal(cmd.Length);
         Marshal.Copy(cmd, 0, pntr, cmd.Length);

         return MonoUsbApi.ControlTransfer(_device, (byte)UsbRequestType.TypeVendor, 6, 0x100, 0, pntr, (short)cmd.Length, 0);
     }
     return -1;
 }

The LibUsbDotNet library has a DeviceNotifier class capable of notifying you of device connects and disconnects. The handler for those events is setup like this:

_deviceNotify = DeviceNotifier.OpenDeviceNotifier();
_deviceNotify.OnDeviceNotify += (sender, e) =>
{
    if (e.Device.IdVendor != VendorId || e.Device.IdProduct != ProductId) return;
    switch (e.EventType)
    {
        case EventType.DeviceArrival:
            Console.WriteLine("Device detected");
            ConnectToArm();
            TestArm();
            break;
        case EventType.DeviceRemoveComplete:
            _armConnectionStatus = ArmConnectionStatuses.Disconnected;
            Console.WriteLine("Device removed from the system");
            break;
    }
    Console.WriteLine("Arm connection status: {0}", _armConnectionStatus);
    _hubProxy.Invoke("SendArmConnectionStatus", _armConnectionStatus);
};

Here's a screenshot of the Mono application running on the RPi (you can see a few rounds of connecting and disconnecting the arm):

                   

RobotArmRemoteControl

This project contains the WPF application used to connect to control the robot remotely. This application need only have an internet connection so it can reach the AzureBotSvc in Azure.

The SendArmCommand property used to handle all of the button presses only supports sending one command at a time. The robot arm can handle mutiple commands, but unfortunately I didn't have time to implement some sort of thumbstick control to send multiple commands (maybe that's something you can do). I also played around with using a Xbox controller as the input mechanism, which I got working partially, but alas I ran out of time for that one as well.

public RelayCommand<object> SendArmCommand
{
    get
    {
        return _sendArmCommand ?? (_sendArmCommand = new RelayCommand<object>(
            cmd =>
            {
                var newRobotCommand = new RobotCommand
                {
                    ArmAction = ArmActions.NoAction,
                    BaseRotateAction = BaseRotateActions.NoAction,
                    LightAction = _toggleLightCommand ? LightActions.LightOn : LightActions.LightOff
                };
                if (cmd is ArmActions)
                {
                    newRobotCommand.ArmAction = (ArmActions)cmd;
                }
                else if (cmd is BaseRotateActions)
                {
                    newRobotCommand.BaseRotateAction = (BaseRotateActions)cmd;
                }
                else if (cmd is LightActions)
                {
                    newRobotCommand.LightAction = (LightActions)cmd;
                }
                _hubProxy.Invoke<RobotCommand>("SendCommand", newRobotCommand);
            }));
    }
}

Here are a couple of screenshots of the application showing the arm being offline and then online:

                                                           

                                                         

SharedTypes

Finally, this project is used to house common enumerations and the struct used to pass arm commands from the RobotArmRemoteControl project to the MonoBotControl project on the RPi.

The ArmActions, BaseRotationActions and LigthActions enumerations are used to define the bytes to send to the robotic arm to ellicit the desired action. They are defined as follows:

[Flags]
public enum ArmActions
{
    NoAction = 0x00,
    GripClose = 0x01,
    GripOpen = 0x02,
    WristBack = 0x04,
    WristForward = 0x08,
    ElbowBack = 0x10,
    ElbowForward = 0x20,
    ShoulderBack = 0x40,
    ShoulderForward = 0x80
}

[Flags]
public enum BaseRotateActions
{
    NoAction = 0x00,
    RotateRight = 0x01,
    RotateLeft = 0x02
}

[Flags]
public enum LightActions
{
    LightOff = 0x00,
    LightOn = 0x01
}

I can't take credit for reverse engineering the USB protocol for the OWI Robotic Arm Edge. I found this helpful article where the work was already done.

The RobotCommand struct is defined as follows:

public struct RobotCommand
{
    public ArmActions ArmAction;
    public BaseRotateActions BaseRotateAction;
    public LightActions LightAction;
}

Conclusion

Here's a link to a short video showing the arm in action. Not the best video, but it hopefully gives you a sense of how well it responds.

So that's it. I hope from this you have been able to see how simple it can be to set up a device so it can be controlled from anywhere. I have a few ideas to extend this project and will try to post updates as it matures. One, I'd like to upgrade the arm to one with position sensors to allow for more accurate positioning of the arm and also one that has faster servos. Two, I'd like to put together a web front end to the control application and possbily give control of the arm to the masses and maybe allow for some crowd-art or maybe host an online Jenga battle.

History

3/31/2015 - Initial revision