Мы столкнулись с интересной статьей от разработчика приложений под Windows Phone и решили поделиться ею с вами.
Когда я был маленьким, родители подарили мне деревянную игру-лабиринт. Она мне очень нравилась. Не думаю, что меня когда-либо волновал сам лабиринт, но механизм наклона был интригующим и очень простым.
Позже я вспомнил об этой игре и решил найти эту игру онлайн и свой старый лабиринт в кладовке.
Сейчас существует масса схожих игр, использующих акселерометр телефона, но их графика оставляет желать лучшего. Вот почему я сделал фотореалистичный вариант ;-)

Цель была сделать лабиринт, соединяющий в себе олдскульный и современный варианты игры. Так что я использовал телефон для управления наклоном физического лабиринта через Bluetooth с Netduino. Лабиринт — это простая модель, распечатанная на 3D принтере и наклоняемая двумя сервоприводами. Бонусом от добавления выключателя в конце послужила обратная связь с телефоном, дающая знать о завершения игры.
• 2 довольно сильных сервопривода. Я использовал Turnigy TGY-9018MG Metal Gear Servo из HobbyKing
• Модуль Bluetooth
• Netduino
• Небольшой квадратный лабиринт
• Резистор на 10 кОм
• Шарик из проводящего материала
Прежде чем вдаваться в технические подробности, стоит разобраться в наклоняющем механизме. Описание ниже может поставить в тупик, поскольку его сложно объяснить. Так что если почувствуете, что запутались, смотрите на картинки чуть ниже, станет понятнее.
Наклон в двух направлениях реализуют по аналогии с деревянной игрой. Один сервопривод прикрепляется к внутренней части игрушки, затем второй сервопривод крепится к внешнему корпусу. Стоит заметить, что в деревянном лабиринте целых три коробки помещены одна в другую, как это можно увидеть на первой картинке поста. Я пользовался той же теорией, но несколько ее упростил. Нижняя часть обеих осей не будет действительно горизонтальной, а значит, лабиринт по сути будет вращаться вокруг условного внутреннего стержня.
Так что мы просто имеем два сервопривода, расположенных под углом 90 градусов относительно друг друга. Первый будет вращать второй. А второй, в свою очередь, будет вращать подставку, прикрепленную к самому лабиринту. Ниже две картинки, иллюстрирующие вышеописанное.


И, чтобы стало еще понятнее, вот как это выглядит в действии:
Чтобы соединить приводы я просто туго стянул их резинкой. Нижний сервопривод я приклеил к старому креплению от привода и закрепил на плоском основании.
С моделью лабиринта пришлось немного помучиться, но, так или иначе, модель в конце поста вполне рабочая, её при желании можно распечатать на собственном принтере. Я распечатал его в размере 9 на 9 см, т.к. это самый большой размер, который может распечатать мой Makerbot Thing-O-Matic.

Чтобы создать модель, я первым делом отправился на www.mazegenerator.net и создал там лабиринт 9х9. Затем импортировал результат в SketchUp, отрисовал линии и нарастил стенки. Стенки я сделал высокими настолько, чтобы шарик не вываливался из лабиринта, но и не застревал.
Электронная часть не так уж сложна.

Примечание: общие провода расположены так, чтобы быть более заметными.
В данном случае он присоединяется также, как и в моих предыдущих проектах. Код точно такой же, так что подробности можно почитать здесь:
blog.roguecode.co.za/Post/ControllingaNetduinooverBluetoothwithWP8
blog.roguecode.co.za/Post/MoreNetduino%2bWP8%2bBluetoothfun-3Dreconstruction
blog.roguecode.co.za/Post/Netduino%2bSonar%2bWP8%2bBluetooth-Controllingsoundwithyourmind
Я начал этот раздел с прямого подключения сервоприводов к разъёму питания Netduino. Понимаю, что вообще-то это не рекомендуется делать, поскольку это приводит к большим затратам энергии, но для запуска концепта это должно быть приемлемо.
Кроме того, у меня возникли другие проблемы, кроме потребляемой мощности – как только стартовали сервоприводы, происходило отключение модуля Bluetooth. Это вполне объяснимо шумом/помехами, но я не сталкивался с тем, чтобы это влияло на какой-либо другой компонент так явно.
Так что я подсоединил их к батарейному блоку на 4.8V. Важно помнить, что необходимо соединить заземление батареи и Netduino. Сигнальные провода идут к контактам ШИМ.
Чтобы сделать контакты конечной точки лабиринта, я отрезал две металлические полоски и поместил одну горизонтально в конечной точке, а вторую – вертикально, с небольшим зазором относительно первой. Когда шарик достигает конца лабиринта, он замыкает контакт между двумя площадками и активирует выключатель.

После добавления кода Bluetooth (по ссылкам выше) остальное становится довольно просто.
Для начала мы устанавливаем оба сервопривода, Bluetooth и выключатель на контактной площадке конца лабиринта. Чтобы разобраться, как и почему работают выключатели на Netduino, сходите по ссылке. Затем выравниваем сервоприводы, чтобы лабиринт был расположен горизонтально.
В идеальном мире, в котором они держатся не на резинках, оба значения будут 90 градусов. Класс приводов взят здесь.
Код в обработчике событий заставит выключатель срабатывать, когда шарик замкнет контакты. Но, поскольку мы не хотим, чтобы это срабатывало раз за разом, мы убедимся, что текущая игра запущена (с помощью isRunning bool).
В заключительной части кода Netduino обрабатывает сообщения от телефона.
Когда игрок нажимает кнопку GO на телефоне, по каналу Bluetooth посылается команда «start». Так что мы устанавливаем bool в значение true, чтобы показать, что игра началась.
Если сообщение – не сообщение о старте, то мы знаем, что это значения акселерометра. Как будет понятно из телефонной части кода, мы отправляем эти данные как значения по осям X, Y. Код разделяет их, затем конвертирует в значения int и устанавливает сервоприводы. Как упоминалось ранее, я добавляю небольшое смещение, поскольку мои приводы не до конца откалиброваны по уровню.
Телефонный код совершенно прост. Вот базовые функции, которые он выполняет:
— Послать «start», когда нажата кнопка GO
— Отобразить таймер
— Отправить значения акселерометра по осям X и Y
— Отобразить окончательное время игры при получении «done»
И вот код для каждой из них:
Послать «start», когда нажата кнопка GO и отобразить таймер
Следует отметить, что использовать DispatcherTimer с такой высокой частотой, вероятно, не самая лучшая идея. И он не должен использоваться кроме случаев, когда это действительно необходимо.
Отправить значения акселерометра по осям X и Y
Те, кто использовал MathHelper.Clamp в XNA должны узнать функционал. Он просто останавливает значение от превышения или снижения вне установленного лимита. Код внутри Dispatcher делает несколько визуальных действий, чтобы показать угол наклона в UI.
Показ финального времени после получения “done”.
Вот и все, по сути. Смотрите исходники для примеров BT и UI.
В zip-файле содержатся решение для Netduino, решение для WP8 и STL-модель для распечатывания.
Поделитесь вашими проектами, в которых использовался WP8!
Когда я был маленьким, родители подарили мне деревянную игру-лабиринт. Она мне очень нравилась. Не думаю, что меня когда-либо волновал сам лабиринт, но механизм наклона был интригующим и очень простым.
Позже я вспомнил об этой игре и решил найти эту игру онлайн и свой старый лабиринт в кладовке.
Сейчас существует масса схожих игр, использующих акселерометр телефона, но их графика оставляет желать лучшего. Вот почему я сделал фотореалистичный вариант ;-)

Цель была сделать лабиринт, соединяющий в себе олдскульный и современный варианты игры. Так что я использовал телефон для управления наклоном физического лабиринта через Bluetooth с Netduino. Лабиринт — это простая модель, распечатанная на 3D принтере и наклоняемая двумя сервоприводами. Бонусом от добавления выключателя в конце послужила обратная связь с телефоном, дающая знать о завершения игры.
Что нам понадобится:
• 2 довольно сильных сервопривода. Я использовал Turnigy TGY-9018MG Metal Gear Servo из HobbyKing
• Модуль Bluetooth
• Netduino
• Небольшой квадратный лабиринт
• Резистор на 10 кОм
• Шарик из проводящего материала
Механизм:
Прежде чем вдаваться в технические подробности, стоит разобраться в наклоняющем механизме. Описание ниже может поставить в тупик, поскольку его сложно объяснить. Так что если почувствуете, что запутались, смотрите на картинки чуть ниже, станет понятнее.
Наклон в двух направлениях реализуют по аналогии с деревянной игрой. Один сервопривод прикрепляется к внутренней части игрушки, затем второй сервопривод крепится к внешнему корпусу. Стоит заметить, что в деревянном лабиринте целых три коробки помещены одна в другую, как это можно увидеть на первой картинке поста. Я пользовался той же теорией, но несколько ее упростил. Нижняя часть обеих осей не будет действительно горизонтальной, а значит, лабиринт по сути будет вращаться вокруг условного внутреннего стержня.
Так что мы просто имеем два сервопривода, расположенных под углом 90 градусов относительно друг друга. Первый будет вращать второй. А второй, в свою очередь, будет вращать подставку, прикрепленную к самому лабиринту. Ниже две картинки, иллюстрирующие вышеописанное.


И, чтобы стало еще понятнее, вот как это выглядит в действии:
Чтобы соединить приводы я просто туго стянул их резинкой. Нижний сервопривод я приклеил к старому креплению от привода и закрепил на плоском основании.
Лабиринт:
С моделью лабиринта пришлось немного помучиться, но, так или иначе, модель в конце поста вполне рабочая, её при желании можно распечатать на собственном принтере. Я распечатал его в размере 9 на 9 см, т.к. это самый большой размер, который может распечатать мой Makerbot Thing-O-Matic.

Чтобы создать модель, я первым делом отправился на www.mazegenerator.net и создал там лабиринт 9х9. Затем импортировал результат в SketchUp, отрисовал линии и нарастил стенки. Стенки я сделал высокими настолько, чтобы шарик не вываливался из лабиринта, но и не застревал.
Netduino:
Электронная часть не так уж сложна.

Примечание: общие провода расположены так, чтобы быть более заметными.
Модуль Bluetooth:
В данном случае он присоединяется также, как и в моих предыдущих проектах. Код точно такой же, так что подробности можно почитать здесь:
blog.roguecode.co.za/Post/ControllingaNetduinooverBluetoothwithWP8
blog.roguecode.co.za/Post/MoreNetduino%2bWP8%2bBluetoothfun-3Dreconstruction
blog.roguecode.co.za/Post/Netduino%2bSonar%2bWP8%2bBluetooth-Controllingsoundwithyourmind
Приводы:
Я начал этот раздел с прямого подключения сервоприводов к разъёму питания Netduino. Понимаю, что вообще-то это не рекомендуется делать, поскольку это приводит к большим затратам энергии, но для запуска концепта это должно быть приемлемо.
Кроме того, у меня возникли другие проблемы, кроме потребляемой мощности – как только стартовали сервоприводы, происходило отключение модуля Bluetooth. Это вполне объяснимо шумом/помехами, но я не сталкивался с тем, чтобы это влияло на какой-либо другой компонент так явно.
Так что я подсоединил их к батарейному блоку на 4.8V. Важно помнить, что необходимо соединить заземление батареи и Netduino. Сигнальные провода идут к контактам ШИМ.
Конечная точка лабиринта:
Чтобы сделать контакты конечной точки лабиринта, я отрезал две металлические полоски и поместил одну горизонтально в конечной точке, а вторую – вертикально, с небольшим зазором относительно первой. Когда шарик достигает конца лабиринта, он замыкает контакт между двумя площадками и активирует выключатель.

Код:
После добавления кода Bluetooth (по ссылкам выше) остальное становится довольно просто.
static SerialPort bt; static string buffer = ""; static Servo servoX; static Servo servoY; static InterruptPort endStopPort; static bool isRunning = false; public static void Main() { servoX = new Servo(Pins.GPIO<em>PIN</em>D5); servoY = new Servo(Pins.GPIO<em>PIN</em>D9); bt = new SerialPort(SerialPorts.COM1, 9600, Parity.None, 8, StopBits.One); bt.Open(); endStopPort = new InterruptPort(Pins.GPIO<em>PIN</em>D10, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeLevelHigh); endStopPort.OnInterrupt += new NativeEventHandler(endStopPort<em>OnInterrupt); bt.DataReceived += new SerialDataReceivedEventHandler(bt</em>DataReceived); servoX.Degree = 90; servoY.Degree = 105; while (true) { //do some other stuff here Thread.Sleep(1000); } } static void endStopPort_OnInterrupt(uint data1, uint data2, DateTime time) { if (isRunning) { isRunning = false; byte[] bytes = Encoding.UTF8.GetBytes("done|"); bt.Write(bytes, 0, bytes.Length); } Thread.Sleep(1); endStopPort.ClearInterrupt(); }
Для начала мы устанавливаем оба сервопривода, Bluetooth и выключатель на контактной площадке конца лабиринта. Чтобы разобраться, как и почему работают выключатели на Netduino, сходите по ссылке. Затем выравниваем сервоприводы, чтобы лабиринт был расположен горизонтально.
В идеальном мире, в котором они держатся не на резинках, оба значения будут 90 градусов. Класс приводов взят здесь.
Код в обработчике событий заставит выключатель срабатывать, когда шарик замкнет контакты. Но, поскольку мы не хотим, чтобы это срабатывало раз за разом, мы убедимся, что текущая игра запущена (с помощью isRunning bool).
В заключительной части кода Netduino обрабатывает сообщения от телефона.
private static void DoSomething(string buffer) { if (buffer == "start") { isRunning = true; } else { string[] split = buffer.Split(new char[] { ',' }); if (split.Length == 2) { int x = int.Parse(split[0]); int y = int.Parse(split[1]); servoX.Degree = x + 3; servoY.Degree = y + 12; Debug.Print(x + " " + y); } } }
Когда игрок нажимает кнопку GO на телефоне, по каналу Bluetooth посылается команда «start». Так что мы устанавливаем bool в значение true, чтобы показать, что игра началась.
Если сообщение – не сообщение о старте, то мы знаем, что это значения акселерометра. Как будет понятно из телефонной части кода, мы отправляем эти данные как значения по осям X, Y. Код разделяет их, затем конвертирует в значения int и устанавливает сервоприводы. Как упоминалось ранее, я добавляю небольшое смещение, поскольку мои приводы не до конца откалиброваны по уровню.
WP8:
Телефонный код совершенно прост. Вот базовые функции, которые он выполняет:
— Послать «start», когда нажата кнопка GO
— Отобразить таймер
— Отправить значения акселерометра по осям X и Y
— Отобразить окончательное время игры при получении «done»
И вот код для каждой из них:
Послать «start», когда нажата кнопка GO и отобразить таймер
private void goBtn<em>Click</em>1(object sender, RoutedEventArgs e) { Write("start"); secTxt.Text = ""; msText.Text = ""; <em>startedDT = DateTime.Now; TimeSpan timeTaken; _timer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 0, 51) }; _timer.Tick += (s, ev) => { timeTaken = DateTime.Now.Subtract(</em>startedDT); secTxt.Text = timeTaken.Seconds.ToString(); msText.Text = timeTaken.Milliseconds.ToString(); }; _timer.Start(); goBtn.Visibility = System.Windows.Visibility.Collapsed; stopBtn.Visibility = System.Windows.Visibility.Visible; timerDisplayContainer.Visibility = System.Windows.Visibility.Visible; }
Следует отметить, что использовать DispatcherTimer с такой высокой частотой, вероятно, не самая лучшая идея. И он не должен использоваться кроме случаев, когда это действительно необходимо.
Отправить значения акселерометра по осям X и Y
void <em>acc</em>ReadingChanged(Accelerometer sender, AccelerometerReadingChangedEventArgs args) { Write(Convert(args.Reading.AccelerationX) + "," + Convert(-args.Reading.AccelerationY) + "|"); Dispatcher.BeginInvoke(() => { xRight.Opacity = args.Reading.AccelerationX * 2; xLeft.Opacity = -args.Reading.AccelerationX * 2; yTop.Opacity = args.Reading.AccelerationY * 2; yBottom.Opacity = -args.Reading.AccelerationY * 2; }); } private string Convert(double val) { return ((int)((Clamp((val * 2d), -1, 1) * 10) + 90)).ToString(); //first double it so full range is -45deg to 45deg //clamp the above value to the max of -1 and 1 //then multiple by the max angle we want the servos to goto //then add 90 because servos go from 0 to 180, not -90 to 90 } private double Clamp(double value, double min, double max) { return value < min ? min : value > max ? max : value; }
Те, кто использовал MathHelper.Clamp в XNA должны узнать функционал. Он просто останавливает значение от превышения или снижения вне установленного лимита. Код внутри Dispatcher делает несколько визуальных действий, чтобы показать угол наклона в UI.
Показ финального времени после получения “done”.
private void DoSomethingWithReceivedString(string <em>receivedBuffer) { if (</em>receivedBuffer == "done") { <em>timer.Stop(); stopBtn.Visibility = System.Windows.Visibility.Collapsed; goBtn.Visibility = System.Windows.Visibility.Visible; TimeSpan timeTaken = DateTime.Now.Subtract(</em>startedDT); MessageBox.Show(string.Format("You took {0}:{1}", timeTaken.Seconds, timeTaken.Milliseconds), "Done!", MessageBoxButton.OK); } }
Вот и все, по сути. Смотрите исходники для примеров BT и UI.
Загрузки:
В zip-файле содержатся решение для Netduino, решение для WP8 и STL-модель для распечатывания.
Поделитесь вашими проектами, в которых использовался WP8!
