Controlling the Servo using a Windows Phone application

Posted by Marco Minerva

In the post Using a Servo in a .NET Gadgeteer Camera Device we saw how to create a .NET Gadgeteer application that uses a Servo to change the direction of the Camera, takes a photo and uploads it to a Web Service. Now, we’ll realize a Windows Phone application to remotely control the camera and show the pictures.

Let’s create a standard Windows Phone Application, named CameraControl, targeting OS version 7.1 (Mango). We use too Silverlight for Windows Phone Toolkit, that can be downloaded from CodePlex site. The user interface is very simple and consists of an area where the last taken photo is shown and a slider with which we can control the direction of the camera:

WP7

The UI of the Windows Phone Application

This is the main portion of the XAML of the application (you can refer to the download available at the end of the post to see the entire markup):

</pre>
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"></RowDefinition>
        <RowDefinition Height="Auto"></RowDefinition>
        <RowDefinition Height="Auto" MaxHeight="100"></RowDefinition>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Vertical" Grid.Row="0">
        <Image Stretch="Uniform" Source="{Binding Path=Image, Converter={StaticResource ImageByteConverter}}"></Image>
        <TextBlock Text="{Binding Path=LastUpdate, StringFormat='last update: {0:G}'}" Margin="5" HorizontalAlignment="Center"></TextBlock>
    </StackPanel>
    <toolkit:PerformanceProgressBar Grid.Row="1" IsIndeterminate="{Binding Path=IsBusy}" />
    <StackPanel Orientation="Vertical" Grid.Row="2">
        <StackPanel Orientation="Horizontal">
        <TextBlock Text="angle of the camera: " Grid.Column="0" VerticalAlignment="Center" Margin="5"></TextBlock>
            <HyperlinkButton Content="tap here to center" Command="{Binding Path=CenterCameraCommand}" />
        </StackPanel>
        <Slider Grid.Column="1" Minimum="1" Maximum="100" Background="Transparent" Margin="5" Value="{Binding Path=Percent, Mode=TwoWay}" />
        <Button Content="set camera" HorizontalAlignment="Center" Grid.Column="2" Command="{Binding Path=SetServoCommand}"></Button>
    </StackPanel>
</Grid>

The application is developed using the MVVM pattern. We have only one ViewModel, called MainViewModel. It uses the REST Web Service already described in the post Using a Servo in a .NET Gadgeteer Camera Device: in particular, the method to set the state of the servo and the one to get the last taken picture. Note that, to set the servo state, we need to send to the service  a number that is a percentage of the delta between the top and bottom PWM settings. It’s an unsigned integer that indicates a percentage of the range: 1% – 100%. Communications are realized using RestSharp, a library that simplifies the process of making requests and processing REST responses in an asynchronous way.

Here is the code:

public class MainViewModel : ObservableObject
{
    private const string BASE_ADDRESS = "http://integral-data.com/ServoCameraService";
    private DispatcherTimer tmrRefresh;

    private bool isBusy;
    public bool IsBusy
    {
        get
        {
            return isBusy;
        }
        set
        {
            if (UpdateProperty(value, ref isBusy))
            {
                OnPropertyChanged("IsBusy");
                RefreshCommand.UpdateCanExecute();
            }
        }
    }

    private byte[] image;
    public byte[] Image
    {
        get { return image; }
        set
        {
            if (UpdateProperty(value, ref image))
                OnPropertyChanged("Image");
        }
    }

    private DateTime? lastUpdate;
    public DateTime? LastUpdate
    {
        get { return lastUpdate; }
        set
        {
            if (UpdateProperty(value, ref lastUpdate))
                OnPropertyChanged("LastUpdate");
        }
    }

    private int percent;
    public int Percent
    {
        get { return percent; }
        set
        {
            if (UpdateProperty(value, ref percent))
            {
                OnPropertyChanged("Percent");
                CenterCameraCommand.UpdateCanExecute();
            }
        }
    }

    public DelegateCommand RefreshCommand { get; set; }
    public DelegateCommand SetServoCommand { get; set; }
    public DelegateCommand CenterCameraCommand { get; set; }

    public MainViewModel()
    {
        percent = 50;
        SetServoCommand = new DelegateCommand(SetServo, o => { return true; });
        CenterCameraCommand = new DelegateCommand(CenterCamera, o => { return percent != 50; });

        RefreshCommand = new DelegateCommand(Refresh, o => { return !isBusy; });
        RefreshCommand.Execute(null);

        // Initialize the timer that periodically retrieves the last taken photo.
        tmrRefresh = new DispatcherTimer();
        tmrRefresh.Interval = TimeSpan.FromSeconds(5);
        tmrRefresh.Tick += (s, e) =>
        {
            RefreshCommand.Execute(null);
        };
        tmrRefresh.Start();
    }

    private void CenterCamera(object parameter)
    {
        Percent = 50;
        SetServoCommand.Execute(null);
    }

    private void SetServo(object parameter)
    {
        IsBusy = true;

        // Send the request to change the direction of the camera.
        RestSharp.RestClient client = new RestSharp.RestClient(BASE_ADDRESS);
        RestSharp.RestRequest request = new RestSharp.RestRequest("/" + percent.ToString(), RestSharp.Method.POST);
        client.ExecuteAsync(request, r =>
        {
            if (r.StatusCode != HttpStatusCode.OK)
                ServiceLocator.ShowAlert("Error", "Unable to set the direction of the camera.");

            IsBusy = false;
        });
    }

    private void Refresh(object parameter)
    {
        if (isBusy)
            return;

        //Retrieve the last taken photo.
        IsBusy = true;

        RestSharp.RestClient client = new RestSharp.RestClient(BASE_ADDRESS);
        RestSharp.RestRequest request = new RestSharp.RestRequest("/", RestSharp.Method.GET);
        client.ExecuteAsync(request, r =>
        {
            if (r.StatusCode == HttpStatusCode.OK)
                Image = r.RawBytes;
            else
                ServiceLocator.ShowAlert("Error", "Unable to retrieve the last taken photo.");

            LastUpdate = DateTime.Now;
            IsBusy = false;
        });
    }
}

Note that this code uses some base class that you find in the example attached at the end of the post.

In the constructor, we initialize three commands:

  • SetServoCommand is used to send to the service the new direction of the servo, and then change the direction of the camera. When it is executed, the metodo SetServo is run. This in turn creates a new RestClient (a class that is part of RestSharp) pointing to BASE_ADDRESS and a RestRequest with a POST message that contains the percentage value as part of the resource. This percentage is bound to the value of the slider. Finally, the ExecuteAsync method of RestClientsends the request to the service and waits for the response. In this case we do not need to work with the response object, so we check only if the request succeeds.
  • CenterCameraCommand sets the percent value to 50 and calls the SetServoCommand, in order to center the remote camera.
  • RefreshCommand creates a GET request that returns the last taken photo from the service. If there is not problems, the RawBytes property of the RestResponse object (r.RawBytes in the example above)  will contain the bytes of the returned photo. They are assigned to the Image property of the ViewModel, when it happens, a custom value converter (referenced in the markup of the page) converts them to a valid BitmapImage that can be show in the interface.
public class ImageByteConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null)
            return null;

        var buffer = (byte[])value;
        if (buffer.Length == 0)
            return null;

        BitmapImage bmp = new BitmapImage();
        bmp.SetSource(new MemoryStream(buffer));

        return bmp;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The ViewModel class contains also a timer that periodically retrieves the last taken photo by invoking the RefreshCommand every 5 seconds. When each command executes, we set the IsBusy property to true, in order to show in the UI a progress indicator that is bound to this property: in this way, we notify the user that a background operation is running.

WP7-2

This is a screenshot of the application, run by the author, Marco Minerva in Pisa, Italy, taking a picture with the servo-oriented camera of Mike Dodaro in Redmond, Washington USA.

The complete application is available for download.

CameraControl.zip

Advertisements

, ,

  1. #1 by Hans on April 4, 2012 - 7:22 AM

    Nicely done!

    One question if I may ask?
    Can’t you use a usb or Bluetooth connection with the camera and servo, why did you choose for a connection via the internet?
    Or can the Windows phone 7 only connect to outside sources true the internet?

    I ask this mainly becouse I am trying to find a way to connect a temperature sensor to my windows phone 7.5 in a easy way. But I can’t seem to find a way without an internet connection.

    Greetings,

    Hans

    • #2 by Marco Minerva on April 4, 2012 - 7:30 AM

      Hi!

      At this momento, there is no API for Bluetooth in Windows Phone, so the only way to make a connection is via the Internet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: