Статьи

Управление телефоном Android с помощью волны пальца

Мое определение веселья в эти дни включает в себя возиться с различными технологиями и посмотреть, как я могу отыграть их друг против друга. Как сумасшедший дирижер, дико махающий руками, чтобы заставить оркестр выступать вместе, я печатаю, как сумасшедший, пока идея в моей голове наконец не сработает.

В этой статье я покажу вам, как я создал контроллер вызовов Android, который отключает звук звонка при входящем звонке. Мы будем использовать следующие технологии:

Вышеупомянутая технология составляет три важных части к загадке:

  • Node server — у нас будет запущен Node-сервер, который является мостом между устройством Android и очень простой веб-страницей. Он будет отслеживать, если мы хотим, чтобы телефон замолчал или нет.
  • on {X} — Когда мы получим входящий звонок, мы попросим телефон опросить наш сервер, чтобы узнать, не было ли запроса на молчание. Если это так, он отключит звук и отправит сообщение о занятости.
  • Веб-страница с контроллером Leap Motion — Когда мы открыли нашу веб-страницу и обведем пальцем Leap Motion, мы отправляем нашему серверу запрос на молчание.

Что такое контроллер скачкообразного движения?

Контроллер Leap Motion Controller — это интригующее устройство ввода, которое может определять движения рук и пальцев. Это революционная технология, которая просто ждет, когда какой-нибудь невероятный разработчик придет и поработает с чистой магией. Мы будем использовать API LeapJS от команды Leap Motion для управления приложениями JavaScript.

на {X}

Одним из ключевых преимуществ, которые я обнаружил в JavaScript, является простота использования языка для сопряжения совершенно не связанных между собой устройств. В этой демонстрации мы будем связывать Leap Motion с {X} . on {X} — это приложение для Android с JavaScript API, которое позволяет вам контролировать и / или отвечать на события на вашем Android-устройстве. Если вы не использовали ранее {X}, я рассмотрел основы этого в своей статье «Управление веб-страницами с помощью JavaScript и {X}» . Проверьте это для быстрого изложения.

Сервер Node.js

Файл index.js содержит весь код приложения Node на стороне сервера. Весь код нашей веб-страницы будет находиться в общей папке. Это структура каталогов, к которой мы стремимся:

Структура файла проекта

В вашем файле package.json (показано ниже) не так уж много, чтобы объявить об этом. Мы будем использовать его для объявления зависимостей для нашего сервера. Основной зависимостью для нашего сервера в этом случае является Express.

Поля engines удобно включать при развертывании приложения на таких хостинговых сервисах, как Heroku. Они укажут, какую версию Node и npm вы ожидаете использовать.

 { "name": "LeapCallController", "version": "0.0.1", "dependencies": { "express": "3.1.x" }, "engines": { "node": "0.10.x", "npm": "1.2.x" } } 

Чтобы запустить наше приложение на Heroku, нам нужен Procfile , содержимое которого показано ниже.

 web: node index.js 

Код сервера

Узловой сервер является мостом между нашей веб-страницей с контроллером Leap Motion и нашим устройством Android. Код сервера показан ниже.

 var http = require('http'), express = require('express'), app = express(), server = require('http').createServer(app), port = process.env.PORT || 5000, call = {}; call.sound = true; app.use(express.bodyParser()); app.get('/', function(request, response) { response.sendfile('public/index.html'); }); app.post('/shouldibesilent', function(request, response) { console.log('That phone wants to know if it should be silent...', request); response.json({callSound: call.sound}); }); app.post('/call', function(request, response) { console.log('Something is setting the call to ' + request.body.action); switch (request.body.action) { case 'mute': call.sound = false; break; case 'reset': call.sound = true; break; } response.json({success: true, actionReceived: request.body.action}); }); app.get(/^(.+)$/, function(req, res) { res.sendfile('public/' + req.params[0]); }); server.listen(port, function() { console.log('Listening on ' + port); }); 

Мы создаем объект с именем call для хранения информации о состоянии вызова. call.sound — это логическое значение, которое указывает, есть ли у нас запрос на отключение звука (отключение звука телефона). В нашей демонстрации мы будем использовать только call.sound , но я поместил его в объект, так что расширение функциональности приложения в будущем будет простым.

Общение с Android

Следующий маршрут будет использоваться, чтобы сообщить нашему устройству Android значение call.sound . Я использовал ответ JSON, поскольку обнаружил, что он лучше всего работает с {X} Ajax-запросами. Во время отладки я нашел очень удобным регистрировать эти запросы на сервере с помощью console.log() .

 app.post('/shouldibesilent', function(request, response) { console.log('That phone wants to know if it should be silent...', request); response.json({callSound: call.sound}); }); 

Взаимодействие с Leap Motion

Маршрут POST в /call отвечает за обработку запросов, чтобы телефон принял меры. Мы отправим запрос на отключение звука телефона и, таким образом, установим для call.sound значение false . Код, ответственный за обработку этого, показан ниже.

 app.post('/call', function(request, response) { switch (request.body.action) { case 'mute': call.sound = false; break; case 'reset': call.sound = true; break; } response.json({success: true, actionReceived: request.body.action}); }); 

Код JavaScript на стороне клиента

Страница index.html в нашем public каталоге в основном скучная и неинтересная для всех, кто посещает ее в данный момент. Вы можете расширить его, чтобы отобразить панель информации о входящих вызовах или визуализацию, когда вы делаете различные жесты, чтобы дать пользователю немного обратной связи о том, успешно ли они делают жест. Мы не будем углубляться в это в этой демонстрации.

В нашей демонстрации сегодня мы сосредоточимся на JavaScript, который обеспечивает ввод Leap Motion. Я включил jQuery исключительно для использования функциональности Ajax, однако вы можете сделать это в обычном JavaScript с тем же эффектом.

 <script src="jquery-1.7.2.min.js"></script> 

Я также включил Underscore в LeapJS, так как я обнаружил, что некоторые версии LeapJS API требуют этого:

 <script src="js/underscore.min.js"></script> <script src="js/leap.min.js"></script> 

Код JavaScript внешнего интерфейса показан ниже. Чтобы этот учебник был простым, JavaScript был встроен в HTML.

 var controller = new Leap.Controller({enableGestures: true}), callMuteRequestMade = false; controller.loop(function(frame) { var gesture = frame.gestures[0], type = gesture ? gesture.type : ''; if (type == 'circle') { console.log('Circle'); if (!callMuteRequestMade) { // Only ask it to mute once! callMuteRequestMade = true; $.ajax({ url: '/call', type: 'POST', data: { action: 'mute' } }); } } }); controller.on('ready', function() { console.log('ready'); }); controller.on('connect', function() { console.log('connect'); }); controller.on('disconnect', function() { console.log('disconnect'); }); controller.on('focus', function() { console.log('focus'); }); controller.on('blur', function() { console.log('blur'); }); controller.on('deviceConnected', function() { console.log('deviceConnected'); }); controller.on('deviceDisconnected', function() { console.log('deviceDisconnected'); }); 

Следующая строка настраивает наш контроллер Leap Motion и включает жесты, чтобы мы могли обнаружить кружение пальца. Вы также можете использовать жесты для обнаружения ударов пальцами. Мы будем использовать переменную controller для взаимодействия с Leap Motion с этого момента:

 var controller = new Leap.Controller({enableGestures: true}) 

Различные функции controller.on() в конце JavaScript доступны для отладки. Каждый из них дает нам знать, когда меняется состояние нашего устройства Leap Motion.

Нас в основном интересует функция controller.loop() которая запускается неоднократно в каждом кадре, который обнаруживает Leap Motion. Согласно документации API, это примерно шестьдесят раз в секунду. Имейте это в виду, если вы делаете что-то слишком ресурсоемкое, так как это будет выполняться часто!

В нашем коде каждый кадр в controller.loop() проверяет любые жесты, которые поднял Leap Motion. frame.gestures содержит массив данных для каждой руки. frame.gestures[0] означает, что мы берем только первую руку. gesture.type сообщит нам, какой жест был выбран:

 var gesture = frame.gestures[0], type = gesture ? gesture.type : ''; 

Если жест является кругом, мы видим, был ли уже сделан запрос на отключение телефона. Как только это было сделано, мы устанавливаем callMuteRequestMade в true . Таким образом, мы не отправляем сотни таких изображений для каждого кадра с помощью кружащего пальца. Код, который выполняет это, показан ниже.

 if (type == 'circle') { console.log('Circle'); if (!callMuteRequestMade) { // Only ask it to mute once! callMuteRequestMade = true; ... 

Наконец, если это первый раз, когда был сделан запрос на отключение телефона, мы делаем запрос Ajax POST к маршруту /call мы настроили на нашем сервере.

Код {X}

Наш сервер готов к перехвату вызовов с нашей веб-страницы и нашего устройства Android. У нас также есть готовая веб-страница для отправки звонков, чтобы отключить звук телефона. Осталось настроить последний элемент — наше устройство Android. Нам нужно создать правило для {X} и загрузить его на телефон.

Для устройства Android мы сосредоточимся на двух обработчиках событий в API {X}: device.telephony.on('incomingCall') и device.telephony.on('idle') . Первое срабатывает каждый раз, когда {X} обнаруживает входящий вызов на вашем устройстве. Второе срабатывает всякий раз, когда функции телефонии устройства перестали работать (например, телефон перестал звонить, мы не совершаем никаких исходящих вызовов и т. Д.).

Полный код {X} показан ниже.

 var originalCallVolume, originalRingerMode, currentPhoneNumber, textMessageRequested = false; device.telephony.on('incomingCall', function(signal) { originalCallVolume = device.audio.ringerVolume, originalRingerMode = device.audio.ringerMode; currentPhoneNumber = signal.phoneNumber; device.scheduler.setTimer({ name: 'checkingForInCallInputs', time: 0, interval: 5 * 1000, exact: false }, function() { checkIfPhoneShouldBeSilent(); }); }); device.telephony.on('idle', function() { device.scheduler.removeTimer('checkingForInCallInputs'); returnToPhoneDefaults(); }); function checkIfPhoneShouldBeSilent() { device.ajax({ url: 'http://yourappurlhere.com/shouldibesilent', type: 'POST', dataType: 'json', data: '{"call":"incoming"}', headers: {'Content-Type': 'application/json'} }, function onSuccess(body, textStatus, response) { var JSONResponse = JSON.parse(body); console.info('successfully received http response!'); console.info(JSON.stringify(JSONResponse)); if (JSONResponse.callSound === false) { device.audio.ringerVolume = 0; if (!textMessageRequested) { textMessageRequested = true; device.messaging.sendSms({ to: currentPhoneNumber, body: 'Sorry! In the middle of a technological breakthrough. I\'ll call you back!' }, function(err) { console.log(err || 'sms was sent successfully'); }); } } }, function onError(textStatus, response) { var error = {}; error.message = textStatus; error.statusCode = response.status; console.error('error: ',error); }); } function returnToPhoneDefaults() { device.audio.ringerVolume = originalCallVolume; device.audio.ringerMode = originalRingerMode; textMessageRequested = false; device.ajax({ url: 'http://yourappurlhere.com/call', type: 'POST', dataType: 'json', data: '{"action":"reset"}', headers: {'Content-Type': 'application/json'} }, function onSuccess(body, textStatus, response) { var JSONResponse = JSON.parse(body); console.info('Successfully got a response after asking to reset the call state'); console.info(JSON.stringify(JSONResponse)); }, function onError(textStatus, response) { var error = {}; error.message = textStatus; error.statusCode = response.status; console.error('error: ',error); }); } 

Обнаружение входящих вызовов

Всякий раз, когда мы получаем входящий звонок, мы сохраняем текущую громкость звонка телефона и режим звонка. Таким образом, мы можем установить их обратно в эти настройки после того, как звонок закончил звонить, чтобы будущие звонки все еще звонили. Мы также храним номер телефона вызывающего абонента, чтобы мы могли отправлять ему SMS-сообщения после отключения звука:

 device.telephony.on('incomingCall', function(signal) { originalCallVolume = device.audio.ringerVolume, originalRingerMode = device.audio.ringerMode; currentPhoneNumber = signal.phoneNumber; ... 

Затем мы запускаем device.scheduler.setTimer() , который очень похож на встроенную функцию setTimeout() в JavaScript. Здесь мы проверяем, должен ли телефон молчать каждые пять секунд. Другие поля делают следующее:

  • name : мы устанавливаем это в "checkingForInCallInputs" чтобы у нас было имя, к которому мы будем обращаться при последующем удалении таймера.
  • время : время в миллисекундах с 1 января 1970 года ( время Unix начинается с этой даты), с которого вы хотите запустить таймер. Мы установили его на ноль, так как мы рассчитываем на время с этого момента.
  • интервал : сколько миллисекундных интервалов мы хотим, чтобы таймер вызывал нашу функцию. Я установил его один раз каждые 5 секунд.
  • точный : установка этого значения в false включает тип оптимизации мощности для повторяющихся таймеров. Я не уверен, в какой степени это заметно, но подумал, что не помешает установить этот.

Функция device.scheduler.setTimer() с этими полями выглядит так:

 device.scheduler.setTimer({ name: 'checkingForInCallInputs', time: 0, interval: 5 * 1000, exact: false }, function() { checkIfPhoneShouldBeSilent(); }); 

Функция checkIfPhoneShouldBeSilent() довольно длинная, но это ваш типичный Ajax-запрос. Он отправляет запросы POST по http://yourappurlhere.com/shouldibesilent с помощью простой строки JSON, позволяющей нашему серверу узнать, что происходит входящий вызов. Вам нужно изменить URL на URL вашего собственного сервера.

Убедитесь, что для dataType и headers задано значение JSON, поэтому {X} отправляет запрос в правильном формате:

 dataType: 'json' headers: {'Content-Type': 'application/json'} 

Когда мы успешно получили ответ от сервера, мы анализируем данные, используя JSON.parse(body) :

 onSuccess(body, textStatus, response) { var JSONResponse = JSON.parse(body); 

Затем мы проверяем, говорит ли сервер JSON, что он хочет отключить звук телефона. Если это так, мы используем device.audio.ringerVolume API {X} для установки громкости звонка на 0:

 if (JSONResponse.callSound === false) { device.audio.ringerVolume = 0; 

Мы не хотим быть слишком грубыми и полностью игнорировать этого человека, поэтому при желании мы можем отправить ему SMS с device.messaging.sendSms функции device.messaging.sendSms в API on {X}. Напомним, что мы сохранили их номер телефона в переменной currentPhoneNumber . Мы также гарантируем, что отправим только одно SMS, установив для textMessageRequested значение true :

 if (!textMessageRequested) { textMessageRequested = true; device.messaging.sendSms({ to: currentPhoneNumber, body: 'Sorry! In the middle of a technological breakthrough. I\'ll call you back!' }, function(err) { console.log(err || 'sms was sent successfully'); }); } 

Обнаружение, когда телефон снова бездействует

Когда телефон снова бездействует, мы удаляем таймер checkingForInCallInputs InCallInputs:

 device.telephony.on('idle', function() { device.scheduler.removeTimer('checkingForInCallInputs'); 

Затем мы запускаем returnToPhoneDefaults() чтобы установить громкость вызова, режим звонка и textMessageRequested обратно к их исходным значениям:

 function returnToPhoneDefaults() { device.audio.ringerVolume = originalCallVolume; device.audio.ringerMode = originalRingerMode; textMessageRequested = false; 

Мы также перезагружаем наш сервер, чтобы он больше не говорил нашему телефону, чтобы он молчал при следующем вызове, сделав еще один запрос POST для /call с действием сброса. В настоящее время мы не используем ответ об успехе ни для чего, кроме отладки:

 device.ajax({ url: 'http://yourappurlhere.com/call', type: 'POST', dataType: 'json', data: '{"action":"reset"}', headers: {'Content-Type': 'application/json'} }, function onSuccess(body, textStatus, response) { var JSONResponse = JSON.parse(body); console.info('Successfully got a response after asking to reset the call state'); console.info(JSON.stringify(JSONResponse)); }, function onError(textStatus, response) { var error = {}; error.message = textStatus; error.statusCode = response.status; console.error('error: ',error); }); 

Отладка с помощью {X}

Если на каком-либо этапе вы хотите проверить, сработало ли что-то, есть два метода для отладки вашего кода {X}:

  • console.info(JSON.stringify(JSONResponse)); ваши ответы в console.info(JSON.stringify(JSONResponse)); JSONconsole.info(JSON.stringify(JSONResponse)); , Вы можете увидеть их, перейдя на страницу правил в {X}, щелкнув правило и выбрав вкладку журналов.
  • Создайте уведомления на своем телефоне, чтобы показать, когда что-то device.notifications.createNotification("No longer in a call, I'll stop asking.").show();device.notifications.createNotification("No longer in a call, I'll stop asking.").show();

Окончательный результат

Когда ваш сервер работает в Интернете, веб-страница запущена в браузере, а телефон подключен к Интернету, теперь вы сможете отключить входящие звонки. В качестве теста нашего кода я заставил мою любимую девушку позвонить на мой телефон так:

Входящий звонок на телефон

Когда мой телефон звонит, я приглушаю входящий звонок, обводя свой палец в воздухе следующим образом:

Обведите мой палец над Leap Motion, чтобы отключить звук входящего вызова.

Это приводит к отключению телефона и отправке SMS моей девушке, сообщающей ей, что я пока не могу ответить на звонок:

SMS-ответ получен

Вывод

Есть много интересных вещей, которые можно сделать, подключив JavaScript API различных устройств. С каждым новым устройством, которое выпускает JavaScript API, возможности становятся все более захватывающим будущим взаимосвязанности! Выйдите, интегрируйте несколько API и создайте что-то новое, чего раньше не было. Если кто-то прервет ваш фокус телефонным звонком, просто обведите пальцем и вернитесь к работе.

Весь код для этого урока доступен на GitHub . Читателю рекомендуется снять его и поэкспериментировать с ним. Например, вы можете настроить код включения {X}, чтобы отключить звук телефона, или добавить поддержку различных жестов и действий. Вы также можете включить Socket.IO для повышения производительности по сравнению со стандартными Ajax-запросами, показанными здесь.