Wi-Fi Gadgeteer Robot controlled by Windows Phone with image streaming

Posted by Marco Minerva

I have updagred the .NET Gadgeteer Robot I described in the post Constructing a Bluetooth controlled Robot, replacing the Bluetooth Module with Wi-Fi and adding a camera to stream images. The result is a Robot that can be controlled by a Windows Phone application, with which it is also possible to show captured images.

Let’s start seeing the new Gadgeteer application, that uses the following modules connected to a FEZ Spider Mainboard:

You can see the result in following screenshot.

Our Gadgeteer application in the Designer

Our Gadgeteer application in the Designer

The first thing to do is to configure Wi-Fi to manage connections to the device. Here is the code in Program.cs file:

private const string SSID = "YOUR_NETWORK_SSID";
private const string PASSPHRASE = "YOUR_NETWORK_PASSPHRASE";

private SocketServer movementServer;
private GT.Timer movementTimer;

void ProgramStarted()
{
    led.GreenBlueSwapped = true;
    led.BlinkRepeatedly(GT.Color.Blue);

    motorControllerL298.MoveMotor(MotorControllerL298.Motor.Motor1, 0);
    motorControllerL298.MoveMotor(MotorControllerL298.Motor.Motor2, 0);

    movementServer = new SocketServer(8080);
    movementServer.DataReceived += new DataReceivedEventHandler(server_DataReceived);

    Gadgeteer.Modules.GHIElectronics.WiFi_RS21.WiFiNetworkInfo info = new WiFi_RS21.WiFiNetworkInfo();
    info.SSID = SSID;
    info.SecMode = WiFi_RS21.SecurityMode.WPA2;
    info.networkType = WiFi_RS21.NetworkType.AccessPoint;

    wifi.NetworkUp += new GTM.Module.NetworkModule.NetworkEventHandler(wifi_NetworkUp);

    wifi.UseDHCP();
    wifi.Join(info, PASSPHRASE);

    camera.CurrentPictureResolution = Camera.PictureResolution.Resolution320x240;
    camera.PictureCaptured += new Camera.PictureCapturedEventHandler(camera_PictureCaptured);

    movementTimer = new GT.Timer(500);
    movementTimer.Tick += new GT.Timer.TickEventHandler(movementTimer_Tick);

    // Use Debug.Print to show messages in Visual Studio's "Output" window during debugging.
    Debug.Print("Program Started");
}

Before actually setting the connection, we make the led blink, to notify that the device initialization is in progress. Note that the firmware of my Multicolor LED has a bug that swaps green and blue color, so I need to set the GreenBluetSwapped property to true in order to show the correct color.

Our Robot will be controlled with commands sent through a TCP connection. To handle this task, we create an instance of the SocketServer class. This is the same class I have discussed in the past months in this blog.  It allows direct connections and avoids the overhead of HTTP request. It creates a thread that waits for an incoming connection on port 8080 and listens for commands. After the server is started, as shown later in this article, the DataReceived event is raised everytime a message is received.

Then, we configure the Wi-Fi module. We create a WiFiNetworkInfo object in which we specify the SSID, the security mode and the network type. You can find more details about this configuration in the module documentation. Then, we call the UseDHCP method and we join the network.

The next step is to configure the camera module. We set the resolution to 320×240 and register for the PictureCaptured, that is raised when a picture is taken.

Finally, we initialize a timer object that is used to control the motors movement. The behavior of this timer is the same we described in the article Constructing a Bluetooth controlled Robot.

When the network is available, the NetworkUp event is raised, in which we complete the initialization of the system:

private void wifi_NetworkUp(GTM.Module.NetworkModule sender, GTM.Module.NetworkModule.NetworkState state)
{
    oledDisplay.SimpleGraphics.Clear();
    oledDisplay.SimpleGraphics.DisplayText(wifi.NetworkSettings.IPAddress,
        Resources.GetFont(Resources.FontResources.NinaB), GT.Color.Yellow, 15, 50);

    var getImageEvent = WebServer.SetupWebEvent("getimage");
    getImageEvent.WebEventReceived +=
                            new WebEvent.ReceivedWebEventHandler(getImageEvent_WebEventReceived);

    movementServer.Start();
    movementTimer.Start();
    WebServer.StartLocalServer(wifi.NetworkSettings.IPAddress, 8081);

    camera.TakePicture();
    led.TurnGreen();
}

We show the IP Address on the OLED Display, so that we can use this value in our applications. Then, we create a Web event that responds on the /getimage path and retrieves the last taken picture. Note that, instead of this approach, we could create a second server that waits for image request. However, in this way, we can show the image in a normal Web Browser too, not only in our application.

After that, we start the TCP server, the movement timer, and the Web Server, then we take the first picture and turn the led green, notifying that the Robot is ready to receive commands.

When the picture is available, in the PictureCaptured event, we save it in a local variable, so that we can send it in the WebEventReceived event handler:

private GT.Picture currentPicture;

private void camera_PictureCaptured(Camera sender, GT.Picture picture)
{
    currentPicture = picture;
    Debug.Print("New image captured");
}

private void getImageEvent_WebEventReceived(string path, WebServer.HttpMethod method,
                                                                 Responder responder)
{
    if (currentPicture != null)
        responder.Respond(currentPicture);

    // Takes a new picture.
    camera.TakePicture();
}

Everytime the client requests an image, the device application returns it and takes a new picture, so that it is available for the next request. If we make periodically requests for images, in this way we can realize a simple image streaming mechanism.

Finally, the most important part of the application: the code that actually controls the motors. As said before, commands are received using the SocketServer class, while the code that determines direction and speed of motors is a slightly modified version of the one used in the Bluetooth controlled Robot:

private enum Status
{
    Idle, MoveForward, MoveBackward, TurnRight, TurnLeft, Rotate
}

private Status currentStatus;
private Status newStatus;

private enum RoverDirection
{
    None, Forward, Backward
}

private RoverDirection currentRoverDirection;
private RoverDirection newRoverDirection;

private void server_DataReceived(object sender, DataReceivedEventArgs e)
{
    string command = SocketServer.BytesToString(e.Data);
    this.HandleCommand(command);
}

private void HandleCommand(string data)
{
    Debug.Print("Command: " + data);

    foreach (char command in data)
    {
        switch (command.ToString().ToUpper())
        {
            case "F":
                newStatus = Status.MoveForward;
                newRoverDirection = RoverDirection.Forward;
                break;

            case "B":
                newStatus = Status.MoveBackward;
                newRoverDirection = RoverDirection.Backward;
                break;

            case "S":
                newStatus = Status.Idle;
                break;

            case "T":
                newStatus = Status.Rotate;
                break;

            case "R":
                newStatus = Status.TurnRight;
                break;

            case "L":
                newStatus = Status.TurnLeft;
                break;

            default:
                break;
        }
    }
}

In the server_DataReceived event handler we use the SocketServer.BytesToString method to get the string representation of the received bytes, then we pass it to the HandleCommand method. This routine cycles through all the characters in the string, because, as we will see later, it is possibile to send multiple commands to the Robot at one time. For details about the meaning of the variables that are used in this piece of code, you can refer to the Bluetooth controlled Robot article.

Finally, in the timer Tick event, we write the code to move the Rover, using the newStatus and newRoverDirection variables we have set in the DataReceveid event:

void movementTimer_Tick(GT.Timer timer)
{
    if (currentStatus != newStatus || currentRoverDirection != newRoverDirection)
    {
        timer.Stop();

        int speed = 50;
        int currentDirection1 = 0;
        int currentDirection2 = 0;

        switch (newStatus)
        {
            case Status.Idle:
                currentDirection1 = currentDirection2 = 0;
                break;

            case Status.MoveForward:
                currentDirection1 = currentDirection2 = 1;
                break;

            case Status.MoveBackward:
                currentDirection1 = currentDirection2 = -1;
                break;

            case Status.TurnRight:
                speed = 100;
                currentDirection1 = 0;
                if (newRoverDirection == RoverDirection.Forward)
                    currentDirection2 = 1;
                else if (newRoverDirection == RoverDirection.Backward)
                    currentDirection2 = -1;
                break;

            case Status.TurnLeft:
                speed = 100;
                currentDirection2 = 0;
                if (newRoverDirection == RoverDirection.Forward)
                    currentDirection1 = 1;
                else if (newRoverDirection == RoverDirection.Backward)
                    currentDirection1 = -1;
                break;

            case Status.Rotate:
                speed = 100;
                currentDirection1 = 1;
                currentDirection2 = -1;
                break;

            default:
                break;
        }

        motorControllerL298.MoveMotor(MotorControllerL298.Motor.Motor1, speed * currentDirection1);
        motorControllerL298.MoveMotor(MotorControllerL298.Motor.Motor2, speed * currentDirection2);

        currentStatus = newStatus;
        currentRoverDirection = newRoverDirection;

        timer.Start();
    }
}

Again, you can refer to the analogous method described in the Bluetooth controlled Robot article for implementation details.

Now that the software part is complete, we can assemble the hardware. Starting from our old Rover, we obtain this new version:

Our Robot with Wi-Fi and camera

Our Robot with Wi-Fi and camera

And now it’s the turn of the Windows Phone Application that controls the Rover via Wi-Fi and shows images that are captured by the camera. Let’s write the following XAML in the MainPage.xaml file:

<phone:PhoneApplicationPage
    x:Class="WiFiRoverControl.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="768"
    xmlns:local="clr-namespace:WiFiRoverControl"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait" Orientation="Portrait"
    shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded">

    <phone:PhoneApplicationPage.Resources>
        <BitmapImage x:Key="Arrow" UriSource="Images/arrow.png"></BitmapImage>
        <BitmapImage x:Key="ArrowPressed" UriSource="Images/arrow_pressed.png"></BitmapImage>
        <Style TargetType="local:ImageButton">
            <Setter Property="Image" Value="{StaticResource Arrow}"></Setter>
            <Setter Property="PressedImage" Value="{StaticResource ArrowPressed}"></Setter>
            <Setter Property="Height" Value="96"></Setter>
            <Setter Property="Width" Value="96"></Setter>
            <Setter Property="RenderTransformOrigin" Value="0.5,0.5"></Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="local:ImageButton">
                        <Grid>
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal"/>
                                    <VisualState x:Name="MouseOver"/>
                                    <VisualState x:Name="Pressed">
                                        <Storyboard>
                                            <ObjectAnimationUsingKeyFrames
                                                    Storyboard.TargetProperty="(UIElement.Visibility)"
                                                    Storyboard.TargetName="PressedImage">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Visible</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                            <ObjectAnimationUsingKeyFrames
                                                    Storyboard.TargetProperty="(UIElement.Visibility)"
                                                    Storyboard.TargetName="NormalImage">
                                                <DiscreteObjectKeyFrame KeyTime="0">
                                                    <DiscreteObjectKeyFrame.Value>
                                                        <Visibility>Collapsed</Visibility>
                                                    </DiscreteObjectKeyFrame.Value>
                                                </DiscreteObjectKeyFrame>
                                            </ObjectAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Disabled"/>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Image x:Name="NormalImage" Source="{TemplateBinding Image}"/>
                            <Image x:Name="PressedImage" Source="{TemplateBinding PressedImage}"
                                                                          Visibility="Collapsed"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </phone:PhoneApplicationPage.Resources>

    <!--LayoutRoot is the root grid where all page content is placed-->
    <Grid x:Name="LayoutRoot" Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <!--TitlePanel contains the name of the application and page title-->
        <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
            <StackPanel Orientation="Horizontal">
                <TextBlock x:Name="ApplicationTitle" Text="WI-FI ROVER"
                       Style="{StaticResource PhoneTextNormalStyle}"/>
                <TextBlock x:Name="connectionStatus" Text="Not connected"></TextBlock>
            </StackPanel>
                <HyperlinkButton Content="marco.minerva@gmail.com" Margin="0,-5,0,-15"
                                 HorizontalAlignment="Left"></HyperlinkButton>
            <TextBlock x:Name="PageTitle" Text="remote control" Margin="9,-7,0,0"
                       Style="{StaticResource PhoneTextTitle1Style}"/>
        </StackPanel>

        <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="Auto"></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>

            <local:ImageButton Grid.Row="0" Grid.Column="0" Click="MoveRover_Click"
                               Tag="FL">
                <local:ImageButton.RenderTransform>
                    <RotateTransform Angle="-135"  />
                </local:ImageButton.RenderTransform>
            </local:ImageButton>
            <local:ImageButton Grid.Row="0" Grid.Column="1" Click="MoveRover_Click"
                               Tag="F">
                <local:ImageButton.RenderTransform>
                    <RotateTransform Angle="-90"  />
                </local:ImageButton.RenderTransform>
            </local:ImageButton>
            <local:ImageButton Grid.Row="0" Grid.Column="2" Click="MoveRover_Click"
                               Tag="FR">
                <local:ImageButton.RenderTransform>
                    <RotateTransform Angle="-45"  />
                </local:ImageButton.RenderTransform>
            </local:ImageButton>
            <Border BorderThickness="1" BorderBrush="White" Grid.Row="1"
                    Grid.ColumnSpan="3" Width="416" Height="312" Margin="0,3,0,0">
                <Image Width="416" Height="312" x:Name="webcam" RenderTransformOrigin="0.5,0.5">
                    <Image.RenderTransform>
                        <ScaleTransform ScaleY="-1"/>
                    </Image.RenderTransform>
                </Image>
            </Border>
            <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Grid.Row="2"
                        Grid.ColumnSpan="3" Margin="0,0,0,10">
                <Button Content="Rotate" Padding="30,5,30,5" Margin="0, 0, 35, 0"
                        Click="MoveRover_Click" Tag="T"></Button>
                <Button Content="Stop" Padding="30,5,30,5"
                        Click="MoveRover_Click" Tag="S"></Button>
            </StackPanel>
            <local:ImageButton Grid.Row="3" Grid.Column="0" Click="MoveRover_Click"
                               Tag="BL">
                <local:ImageButton.RenderTransform>
                    <RotateTransform Angle="135"  />
                </local:ImageButton.RenderTransform>
            </local:ImageButton>
            <local:ImageButton Grid.Row="3" Grid.Column="1" Click="MoveRover_Click"
                               Tag="B">
                <local:ImageButton.RenderTransform>
                    <RotateTransform Angle="90"  />
                </local:ImageButton.RenderTransform>
            </local:ImageButton>
            <local:ImageButton Grid.Row="3" Grid.Column="2" Click="MoveRover_Click"
                               Tag="BR">
                <local:ImageButton.RenderTransform>
                    <RotateTransform Angle="45"  />
                </local:ImageButton.RenderTransform>
            </local:ImageButton>
        </Grid>
    </Grid>

</phone:PhoneApplicationPage>

Note that a custom control, ImageButton, is used. It is a control that adds to the standard button two properties: Image and PressedImage. Using the template defined in the XAML, these images are shown, respectively, when the botton is in its normal state and when it is pressed with the finger. Image buttons are used to show the arrows with which we can send commands to the Gadgeteer device. You can find the control and all the images in the ZIP file attached to this article.

Take also a look at the declaration of the Image controls that will contains the pictures received from the device:

<Border BorderThickness="1" BorderBrush="White" Grid.Row="1"
        Grid.ColumnSpan="3" Width="416" Height="312" Margin="0,3,0,0">
    <Image Width="416" Height="312" x:Name="webcam" RenderTransformOrigin="0.5,0.5">
        <Image.RenderTransform>
            <ScaleTransform ScaleY="-1"/>
        </Image.RenderTransform>
    </Image>
</Border>

In particular, the image is horizontally flipped using a ScaleTransform. We’ll discuss this detail later in this article, when we’ll talk about image streaming.

Note that probably you don’t see the image buttons in the Designer. However, when you run the application, they will appear in the right position, as you can see in the following screenshot:

The UI of the Windows Phone Application

The UI of the Windows Phone Application

To handle socket connection, we use the SocketClient class that is available on MSDN. It simplifies the use of the socket object with Windows Phone and its asynchronous programming model. You can find it in the ZIP file that is available at the end of this post.

private const string ROVER_IP = "192.168.1.104";
private const int ROVER_MOVEMENT_PORT = 8080;
private const int ROVER_CAMERA_PORT = 8081;

private SocketClient movementSocket;

private DispatcherTimer tmrPingRover;
private DispatcherTimer tmrGetImage;

// Constructor
public MainPage()
{
    InitializeComponent();
}

private void PhoneApplicationPage_Loaded(object sender, RoutedEventArgs e)
{
    connectionStatus.Text = "Connecting to... " + ROVER_IP;

    BackgroundWorker bwConnection = new BackgroundWorker();
    bwConnection.DoWork += new DoWorkEventHandler(bwConnection_DoWork);
    bwConnection.RunWorkerCompleted +=
                                 new RunWorkerCompletedEventHandler(bwConnection_RunWorkerCompleted);
    bwConnection.RunWorkerAsync();

    // Creates the timer that periodically pings the Rover, to keep the connection alive.
    tmrPingRover = new DispatcherTimer();
    tmrPingRover.Interval = TimeSpan.FromSeconds(10);
    tmrPingRover.Tick += new EventHandler(tmrPingRover_Tick);

    // Creates the timer that gets image streaming.
    tmrGetImage = new DispatcherTimer();
    tmrGetImage.Interval = TimeSpan.FromSeconds(0.5);
    tmrGetImage.Tick += new EventHandler(tmrGetImage_Tick);
}

private void bwConnection_DoWork(object sender, DoWorkEventArgs e)
{
    string response = null;

    // First, connects the socket used to send commands to the Rover.
    movementSocket = new SocketClient();
    movementSocket.Connect(ROVER_IP, ROVER_MOVEMENT_PORT, out response);

    e.Result = response;
}

private void bwConnection_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error == null)
    {
        string response = e.Result as string;
        connectionStatus.Text = response;

        if (response == SocketError.Success.ToString())
        {
            // Starts the timer that periodically pings the Rover, to keep the connection open,
            // and the one that request image streaming.
            tmrPingRover.Start();
            tmrGetImage.Start();
        }
    }
    else
    {
        connectionStatus.Text = e.Error.Message;
    }
}

In the Loaded event, we create a BackgroundWorker that is responsible of making the connection to Gadgeteer. In its DoWork event handler, we instantiate the movementSocket object and then we connect to the IP address and port number specifies in the relative constants (note that, for the sake of simplicity, the IP address of Gadgeteer Robot is written directly in code). If the connection is successful, in the RunWorkerCompleted event handler we start the tmrPingRover and tmrGetImage timers, that we have instantiated in the Loaded event: they are used, respectively, to send a keep-alive message to the Rover, in order to keep the connection open, and to request images from the device.

Sending command to Rover is straightforward, thanks to the SocketClient class we’re using:

private void tmrPingRover_Tick(object sender, EventArgs e)
{
    // Sends the ping command to the Rover.
    movementSocket.Send("P");
}

public void MoveRover_Click(object sender, RoutedEventArgs e)
{
    string command = (sender as Button).Tag as string;
    string response = movementSocket.Send(command);
    connectionStatus.Text = response;
}

When the Tick event of tmrPingRover event raises, we use the Send method of SocketClient to send a keep-alive command to Gadgeteer. In particular, we send a character that doesn’t correspond to any known commands, so Gadgeteer simply discards it, but it is sufficient to keep the connection channel open.

The other event handler, MoveRover_Click, is raised when an ImageButton (i.e. an arrow) is clicked. We get the Tag proprerty of the sender, that contains the commands that need to be sent to Gadgeteer. For example, for the forward command, the Tag property values F, while for backward-right it values BR. We send these commands using the Send method of SocketClient. This is all is needed to make the Rover move.

Getting and showing image streaming, instead, require a bit more work. First, we use RestSharp, a library that simplifies the process of making requests via HTTP and processing responses in an asynchronous way. But then, we have a problem: Windows Phone doesn’t support BMP format, that is used by .NET Gadgeteer camera module. So, we need a way to load images in this format and convert them in a format that Windows Phone can handle (i.e., in PNG or JPEG). For this task, we’ll use ImageTools, that is available on http://imagetools.codeplex.com.

But there is another problem: if we try to read images that come from .NET Gadgeteer camera module with ImageTools, the height property is always negative. So, the read operation causes an exception. To solve this issue, I have downloaded the source code of ImageTools and applied a little hack to the library, in order to take the positive value of height. Doing so, the image is read correctly, but it appears flipped. This is the reason for which I have applied a ScaleTransform tranformation on image object in XAML, so that it is shown in the right direction. Of course, a better approach is to modify the code that reads the bitmap, but for the moment this solution is acceptable. As always, you can find the modified version of ImageTools library in the ZIP file that comes with this article.

In conclusion, here is the code thet retrieves the image from device and shows it in the tmrGetImage_Tick event:

private void tmrGetImage_Tick(object sender, EventArgs e)
{
    tmrGetImage.Stop();

    // Makes the request for the image.
    string uri = string.Format("http://{0}:{1}", ROVER_IP, ROVER_CAMERA_PORT);
    RestClient client = new RestClient(uri);
    RestRequest request = new RestRequest("getimage", Method.GET);
    client.ExecuteAsync(request, response =>
    {
        if (response.StatusCode == HttpStatusCode.OK)
        {
            // response.RawBytes contains the bytes images.
            var data = response.RawBytes;
            ExtendedImage image = new ExtendedImage();
            image.LoadingCompleted += (s, args) =>
            {
                Dispatcher.BeginInvoke(() =>
                {
                    webcam.Source = image.ToBitmap();
                    connectionStatus.Text = response.StatusCode.ToString();
                    tmrGetImage.Start();
                });
            };
            image.LoadingFailed += (s, args) =>
            {
                Dispatcher.BeginInvoke(() =>
                {
                    connectionStatus.Text = (args.ExceptionObject as Exception).Message;
                    tmrGetImage.Start();
                });
            };

            // Invoking the SetSource method actually loads the image.
            MemoryStream ms = new MemoryStream(data);
            image.SetSource(ms);
        }
        else
        {
            // Something goes wrong.
            connectionStatus.Text = response.StatusCode.ToString();
            tmrGetImage.Start();
        }
    });
}

We make an HTTP request for the /getimage resource. When the ExecuteAsync method completes, if everything is OK, we get the response bytes and we create an object of type ExtendedImage (that is part of ImageTools). Then, we create handlers for LoadingCompleted and LoadingFailed events, because image is loaded asynchronously. In particular, in the LoadingCompleted event we use the ToBitmap method to get a bitmap that can be shown in our application. Finally, to actually load the image, we invoke the SetSource method on ExtendedImage object.

For this method to work, we need to instruct ImageTools to use the appropriate decoder for the image. This is done by adding the following line in the MainPage constructor:

public MainPage()
{
    // ...
    ImageTools.IO.Decoders.AddDecoder<BmpDecoder>();
}

Note that there are some delays between the captured picture on the device and the one shown by the Windows Phone application, but for our prototype this is acceptable.

The last thing to do is to send the command to stop the Rover when we close the application. We can override the OnNavigatingFrom method:

protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
    // The page are about to close, so we send the stop command to Rover.
    movementSocket.Send("S");

    tmrPingRover.Stop();
    tmrGetImage.Stop();

    base.OnNavigatingFrom(e);
}

And that’s all. Now we can turn on our .NET Gadgeteer Robot, for example using the same power source we used in the Bluetooth controlled Robot article, and start the Windows Phone application. After a few seconds, the first image will be shown in the center of the UI. Pressing an arrow, we’ll send a command to the device, so that the Robot will move accordingly:

Both the Gadgeteer and the Windows Phone application are available for download.

WiFiRover.zip

, , , ,

  1. #1 by Ryan on July 10, 2012 - 1:55 PM

    Well done! Thanks for sharing. I’ll try this out myself and let you know how it goes.

  2. #2 by Barney on December 30, 2012 - 9:54 PM

    I have read so many content on the topic of the blogger lovers but this article is genuinely a nice post, keep it up.

  3. #3 by tagza.com on January 27, 2013 - 2:07 AM

    Hello there I am so glad I found your site, I really found you
    by error, while I was browsing on Yahoo for something else, Regardless
    I am here now and would just like to say cheers for a incredible post and a all round
    interesting blog (I also love the theme/design), I don’t have time to read through it all at the minute but I have book-marked it and also added your RSS feeds, so when I have time I will be back to read a great deal more, Please do keep up the great job.

  4. #4 by Kevin on February 12, 2013 - 12:31 AM

    Is it possible to create an Ad-hoc network between the .Net Gadgeteer WiFi module and Windows Phone? For example, could I take the robot out in the forest where no wireless connections exist of any kind and end up controlling it from the phone? I have need to create a connection between Windows Phone and hardware through WiFi without the assistance of a wireless connection from an ISP. If a direct connection can be established I would be grateful to be pointed to some resources.

  5. #6 by Plamen Dimitrov on August 22, 2013 - 1:34 AM

    Wonderful Tutorial :). Thank you very much for sharing? I would like to ask you just one thing. What did you use to create the Windows Phone app? Is it possible to use a Windows Phone Emulator that comes with the Windows Phone Developer Tools CTP?

    • #7 by Michael Dodaro on August 22, 2013 - 8:50 AM

      Thanks, Plamen, and yes we used the Windows Phone emulator for this application. Marco Minerva wrote the WP code.

  6. #8 by Alex Onoshko on November 1, 2013 - 10:11 AM

    Thanks for sharing this. I am trying to create a replica of this rover and I would like to ask you something. How can I find the number of the rover movement port of my rover? I saw in your documentation that you are using movement port 8080 for your rover. Can you tell me where you found this number?

    • #9 by Marco Minerva on November 1, 2013 - 11:16 AM

      The port number 8080 is passed to the constructor of SocketServer class that is instantiated in the ProgramStarted method.

  7. #10 by salvador on July 1, 2015 - 5:54 PM

    Hi, i will like to know, how i can change the ip so i can insert it manually using a textbox,

  1. Using voice commands to control a servo « Integral Design
  2. What are people doing with .NET Gadgeteer? | MSDN Blogs
  3. Blog J.Schweiss | Aproaching MVVM on WP7
  4. La classe immersive l’école 2.0 par Microsoft | Planète Robots

Leave a reply to tagza.com Cancel reply