Программируем роботов на Windows 8. Управляем Sphero с планшета используя акселерометр и датчик столкновений

в 8:56, , рубрики: Sphero, visual studio 2013, windows, Windows 8, Блог компании Microsoft, робототехника, метки: , , ,

image

Продолжаем исследовать возможности Sphero и его SDK.
На повестке дня управление роботом с помощью акселерометра планшета и распознавание столкновения с препятствием.

Немного теории

В прошлой статье мы уже рассматривали кто такой Sphero и как начать управлять им программно на Windows 8.1 используя Visual Studio 2013.
Теперь мы займемся более сложными вещами. Научимся «рулить» роботом наклоняя планшет в разные стороны и определять столкновение с препятствием. Для этого мы будем использовать акселерометр моего планшета и датчик столкновения робота с внешним миром.

Акселерометр

Операционная система Windows 8.1 и объектная модель WinRT поддерживают работу со следующими сенсорами, если, конечно, они есть у устройства:

  • Акселерометр;
  • Компас;
  • Гирометр;
  • Датчик освещения;
  • Инклинометр;
  • Геолокация.

Акселерометр – это модуль, измеряющий движение в трех направлениях. Этим модулем обустроены многие современные устройства. Чтобы получить доступ к данным акселерометра, необходимо обратиться к объекту Accelerometer, который предоставляет доступ к объекту AccelerometerReading со следующими свойствами:

  • AccelerationX: отображает ускорение вдоль оси Х;
  • AccelerationY: отображает ускорение вдоль оси Y;
  • AccelerationZ: отображает ускорение вдоль оси Z;
  • Timestamp: определяет время снятия данных.

Мы подпишемся на событие изменения показаний акселерометра и будем передвигать робота, ускоряя, замедляя или разворачивая его в зависимости от показаний.

image

Подписаться на событие изменения показаний акселерометра можно так.

Подписка на событие акселерометра
protected override void OnNavigatedTo(NavigationEventArgs e)
{
…
Accelerometer _accelerometer = Accelerometer.GetDefault();  
_accelerometer.ReadingChanged += new TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>(ReadingChanged);
…
}
async private void ReadingChanged(object sender, AccelerometerReadingChangedEventArgs e)
{
…
}

Перемещение

Предположим, мы получили данные акселерометра с планшета. Теперь надо их использовать для перемещения Sphero в пространстве.
В Sphero SDK для Windows 8.1 есть метод Roll, котoрый занимается перемещением Sphero и принимает два параметра:

  • Heading – угол перемещения.
    Рассчитывается как:
    image
    Принимает значения в диапазоне от 0 до 359.
  • Speed – скорость перемещения.
    0 – не двигается.
    255 – максимальная скорость.

По умолчанию, Sphero начинает свою работу с показателей x = 0, y = 0, heading = 0, speed = 0.

Детектор столкновения

Sphero поддерживает такую забавную вещь, как датчик столкновения (Collision Detection). Он работает весьма примитивно, на разнице координат.

В Sphero SDK для Windows 8.1 уже есть готовое событие CollisionDetectedEvent, на которое вы можете подписаться и отлавливать столкновения с объектами реального мира.

Описание события
private void OnRobotConnected(object sender, Robot robot)
{
…
robot.CollisionControl.StartDetectionForWallCollisions();
robot.CollisionControl.CollisionDetectedEvent += OnCollisionDetectedEvent; 
…
}

Событие будет срабатывать, если вы предварительно не забудете включить датчик столкновений:

robot.CollisionControl.StartDetectionForWallCollisions();

Когда датчик вам перестанет быть нужен – выключите его:

robot.CollisionControl.StopDetection();

Некоторые полезные функции Sphero SDK

Для работы с роботом, нам так же понадобятся следующие методы Sphero SDK для Windows 8.1:

SetBackLED(intensity) – включить подсветку задней части робота, чтобы можно было понимать куда он поедет. Значения:
1.0f – включает на полную мощность.
0.0f — отключает.

SetHeading(heading) – повернуть робота. Принимает значения от 0 до 359.

SetRGBLED(red, green, blue) – установить цвет шара.

Roll(heading, speed) – задать направление движения и скорость движения:
heading – угол перемещения. Принимает значения в диапазоне от 0 до 359.
speed – скорость перемещения. 0 – не двигается. 255 – максимальная скорость.

Sleep() – усыпить робота.

Paзработка

Задача

Передвигать робота используя акселерометр. При столкновении с препятствием – красить робота красным.

Инструменты

  • Планшет Microsoft Surface 2 Pro
  • OS Windows 8.1
  • Среду разработки: Visual Studio 2013
  • Язык: C#
  • Sphero SDK для Windows 8.1

Процесс

1. Открываем Visual Studio 2013 и создаём проект File / New / Project / Blank App.

image

2. Подключаем сборку RobotKit.dll из Sphero SDK для Windows 8.1

image

3. Добавляем поддержку Bluetooth RFCOMM для приложения. Открываем Package.appxmanifest в режиме правки кода и добавляем в секцию Capabilities следующий код.

Поддержка Bluetooth RFCOMM для приложения

<wb:DeviceCapability Name="bluetooth.rfcomm"> <wb:Device Id="any"> <wb:Function Type="serviceId:00001101-0000-1000-8000-00805F9B34FB" /> </wb:Device> </wb:DeviceCapability>

4. На основной экран приложения кладем:

  • переключатель состояния робота (подключенне подключен);
  • текстовое поле для отображения состояния;
  • кнопки Start и Stop для запуска и остановки игры.

Откроем файл MainPage.xaml и добавим туда всё это.

Основной экран приложения

<Page
    x:Class="SpheroRace.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:SpheroRace"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"   >
    <Grid Margin="100" >        
        <Grid.RowDefinitions >
            <RowDefinition Height="60"/>
            <RowDefinition Height="60"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBlock x:Name="SpheroName" Grid.Row="0" Grid.Column="0" Text="No Sphero Connected" FontSize="24"/>
        <ToggleSwitch x:Name="ConnectionToggle" Grid.Row="1" Grid.Column="0" Toggled="ConnectionToggle_Toggled" />
        <Button Name="StartBtn" Click="StartBtn_Click" Grid.Row="2" Grid.Column="0" Content="Start" VerticalAlignment="Bottom" HorizontalAlignment="Left" Padding="100" ></Button>
        <Button Name="StopBtn" Click="StopBtn_Click" Grid.Row="2" Grid.Column="1" Content="Stop" VerticalAlignment="Bottom" HorizontalAlignment="Right" Padding="100" IsEnabled="False" ></Button>
    </Grid>
</Page>

5. Перейдем в MainPage.xaml.cs и опишем логику игры. Для общения со Sphero используем объектную модель, предоставленную SDK.

Логика игры

public sealed partial class MainPage : Page
    {
        private Sphero m_robot = null;
        private long   m_lastTimeMs;
        private double m_currentX = 0;
        private double m_currentY = 0;

        private bool m_isStarted = false;

        private const string c_noSpheroConnected = "No Sphero Connected";
        private const string c_connectingToSphero = "Connecting to {0}";
        private const string c_spheroConnected = "Connected to {0}";

        public MainPage()
        {
            this.InitializeComponent();
        }
         
        //Начало работы. Переход на основной экран
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            SetupRobotConnection();
            Application app = Application.Current;
            app.Suspending += OnSuspending;
            
            Accelerometer _accelerometer = Accelerometer.GetDefault();    
            _accelerometer.ReadingChanged += new TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>(ReadingChanged);

        }

        //Завершение работы. Переход с основного экрана
        protected override void OnNavigatedFrom(NavigationEventArgs e)
        {
            base.OnNavigatedFrom(e);

            ShutdownRobotConnection();

            Application app = Application.Current;
            app.Suspending -= OnSuspending;

            Accelerometer _accelerometer = Accelerometer.GetDefault();
            _accelerometer.ReadingChanged -= new TypedEventHandler<Accelerometer, AccelerometerReadingChangedEventArgs>(ReadingChanged);
        }

        //handle the application entering the background
        private void OnSuspending(object sender, SuspendingEventArgs args)
        {
            ShutdownRobotConnection();
        }

        //Ищем робота и подключаемся к нему
        private void SetupRobotConnection()
        {
            SpheroName.Text = c_noSpheroConnected;

            RobotProvider provider = RobotProvider.GetSharedProvider();
            provider.DiscoveredRobotEvent += OnRobotDiscovered;
            provider.NoRobotsEvent += OnNoRobotsEvent;
            provider.ConnectedRobotEvent += OnRobotConnected;
            provider.FindRobots();
        }            

        //Завершаем работу с роботом
        private void ShutdownRobotConnection()
        {
            if (m_robot != null && m_robot.ConnectionState == ConnectionState.Connected)
            {
                m_robot.SensorControl.StopAll();
                m_robot.CollisionControl.StopDetection();
                m_robot.Sleep();                
                m_robot.Disconnect();

                ConnectionToggle.OffContent = "Disconnected";
                SpheroName.Text = c_noSpheroConnected;
                SetRedColor();
    
                RobotProvider provider = RobotProvider.GetSharedProvider();
                provider.DiscoveredRobotEvent -= OnRobotDiscovered;
                provider.NoRobotsEvent -= OnNoRobotsEvent;
                provider.ConnectedRobotEvent -= OnRobotConnected;
            }
        }
       
        //Робот найден!
        private void OnRobotDiscovered(object sender, Robot robot)
        {          
            if (m_robot == null)
            {                
                RobotProvider provider = RobotProvider.GetSharedProvider();
                provider.ConnectRobot(robot);
                ConnectionToggle.OnContent = "Connecting...";
                m_robot = (Sphero)robot;

                SpheroName.Text = string.Format(c_connectingToSphero, robot.BluetoothName);
            }
        }

        //Робот не найден :(
        private void OnNoRobotsEvent(object sender, EventArgs e)
        {
            MessageDialog dialog = new MessageDialog(c_noSpheroConnected);
            dialog.DefaultCommandIndex = 0;
            dialog.CancelCommandIndex = 1;
            dialog.ShowAsync();
        }
    
        //Робот готов исполнять команды
        private void OnRobotConnected(object sender, Robot robot)
        {
            ConnectionToggle.IsOn = true;
            ConnectionToggle.OnContent = "Connected";

            SpheroName.Text = string.Format(c_spheroConnected, robot.BluetoothName);

            if (m_robot != null && m_robot.ConnectionState == ConnectionState.Connected)
            {
                m_robot.CollisionControl.StartDetectionForWallCollisions();
                m_robot.CollisionControl.CollisionDetectedEvent += OnCollisionDetectedEvent;

                SetRedColor();
            }
        }

        //Событие изменения показаний акселерометра
        async private void ReadingChanged(object sender, AccelerometerReadingChangedEventArgs e)
        {
            if (!m_isStarted)
                return;

            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                Windows.Devices.Sensors.AccelerometerReading reading = e.Reading;
                SendRollCommand(reading.AccelerationX, reading.AccelerationY, reading.AccelerationZ);
            });
        }

        //Событие столкновения с препятствием
        private void OnCollisionDetectedEvent(object sender, RobotKit.CollisionData data)
        {
            SetRedColor();
            m_robot.Roll(0, 0);
        }
        
        //Кнопка для отключения робота
        private void ConnectionToggle_Toggled(object sender, RoutedEventArgs e)
        {
            ConnectionToggle.OnContent = "Connecting...";
            if (ConnectionToggle.IsOn)
            {
                if (m_robot == null || m_robot.ConnectionState != ConnectionState.Connected)
                {
                    SetupRobotConnection();
                }
            }
            else
            {
                ShutdownRobotConnection();
            }
        }

        //Запустить робота
        private void StartBtn_Click(object sender, RoutedEventArgs e)
        {
            m_isStarted = true;
            StartBtn.IsEnabled = false;
            StopBtn.IsEnabled = true; 
            
            SetGreenColor();
            m_robot.Roll(0, 0);           
        }

        //Остановить робота
        private void StopBtn_Click(object sender, RoutedEventArgs e)
        {
            m_isStarted = false;
            StartBtn.IsEnabled = true;
            StopBtn.IsEnabled = false;

            SetRedColor();
            m_robot.Roll(0, 0);
        }

        //Начальное состояние робота. Он зеленый и неподвижно стоит
        private void SetGreenColor()
        {
            m_robot.SetHeading(0);
            m_robot.SetBackLED(1.0f);
            m_robot.SetRGBLED(0, 255, 0);
        }

        //Состояние остановки или столкновения в препятствием
        private void SetRedColor()
        {
            m_robot.SetHeading(0);
            m_robot.SetBackLED(1.0f);
            m_robot.SetRGBLED(255, 0, 0);
        }

        //Логика движения робота при изменении показаний акселерометра
        private async void SendRollCommand(double newX, double newY, double newZ)
        {
            float x = (float)newX;
            float y = (float)newY;
            float z = (float)newZ;

            float speed = Math.Abs(z);
            speed = (speed == 0) ? 0 : (float)Math.Sqrt(speed);
            if (speed > 1f)
                speed = 0.01f;

            int heading = Convert.ToInt32(Math.PI / 2.0 - Math.Atan2((double)y - m_currentY, (double)x - m_currentX));

            long milliseconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
            if ((milliseconds - m_lastTimeMs) > 1000)
            {
                SetGreenColor();
                m_robot.Roll(heading, speed);

                m_lastTimeMs = milliseconds;
                m_currentX = x;
                m_currentY = y;
            }
        }
       
}

6. Подключаемся к Sphero по Bluetooth

image

7. Запускаем приложение.

image

8. Нажимаем на кнопку Start и проверяем игру на деле.

image

Результат

У нас получилась простая игрушка. Её можно применять, устраивая многопользовательские гонки роботов, или для игры с домашним животным.

Результат работы можно посмотреть на видео.

Исходники доступны тут: github.com/MissUFO/SpheroRace

Полезные ссылки

Программируем роботов на Windows 8. Магический шар Sphero
Официальный сайт Sphero
Центр разработчика Sphero
Sphero SDK для Windows 8.1
Пример использования Sphero SDK для Windows 8.1
Пример использования акселерометра в Windows 8.1
Канал разработчиков Sphero на youtube
Скачать Visual Studio 2013
Подробнее о Visual Studio Online
Зарегистрироваться на Visual Studio Online

Автор: MissUFO

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js