- PVSM.RU - https://www.pvsm.ru -
Или как мы зажгли лампочку со смартфона через облачную службу на глазах изумленных студентов НГУ.
Полное техническое описание решения мы приводим внизу, а начнем с лирическо-философского пролога.
Практически все наши сотрудники получили высшее образование и очень многие именно в Новосибирском государственном университете. Кто-то буквально недавно, кто-то 10 – 20 лет назад, и все сталкивались с выбором будущей профессии. На последних курсах студентами мы выбирали кафедру на которой проходили специализацию и защищали дипломы. И была такая замечательная традиция как Дни открытых дверей в институтах, лабораториях и компаниях, где сотрудники рассказывали, чем они занимаются, какие темы сейчас стоят перед наукой и технологиями, и как можно в этом поучаствовать.
Что самое интересное в Днях открытых дверей для студента? Это – ходить, задавать вопросы, смотреть на реальных людей, которые занимаются настоящим делом, которое кому-то нужно.
По словам одного из наших сотрудников, для него, как для будущего специалиста, который никогда не видел практически применения своих знаний в рамках Дней открытых дверей было очень интересно увидеть вживую тренажер стыковки для станции Мир, систему перехвата телефонных звонков, систему управления ТЭЦ, виртуальную студию, услышать и поговорить с людьми все это уже реализовавшими. И именно такие встречи помогли решить, где будет написан диплом и в какой из областей хотелось бы развиваться в профессиональном плане.
Сегодня Дни открытых дверей сменили Дни карьеры. Но нам хотелось сохранить дух этого мероприятия в своей презентации.
Мы поставили цель – сделать все согласно старым добрым и проверенным традициям с одной стороны, а с другой – показать студентам, чтобы есть фундаментальные принципы и мир достаточно сложная вещь, но это и хорошо. Это вызов и это возможность реализовать себя в профессиональном ИТ-обществе.
Именно с такими мыслями мы начали подготовку к Дням карьеры в НГУ – ведущей кузницы кадров среди Новосибирских вузов для любой уважающей себя ИТ-компании Новосибирска и многих других городов и стран.
Поэтому мы устроили стендовую сессию и мастер-класс, на который пришли руководители проектов и ведущие разработчики, участвующие в интересных боевых проектах для наших ведущих клиентов. Говоря просто – мы не ограничились девушками из HR, а выставили действующий «ИТ-спецназ» с шикарной темой.
Мы решили провязать несколько модных ИТ-тем и опыт из реального проекта:
И придумали управлять лампочками со смартфона. Да, вот так просто и наглядно показать, как за полтора часа можно написать код и получить работающее комплексное решение.
Но давайте по порядку.
Если говорить в общем про интернет вещей, то мы бы выделили 2 направления, в разработке решений.
Конечно в будущие решения класса IoT будут не выбирать одно из двух направлений, а сочетать их. Однако 2-й вариант, кажется пока несколько сложным для проведения мастер-класса, т.е. написания его в online-режиме за полтора часа.
А вот что касается первого, то на основе опыта в разработке таких решений мы вполне смогли его продемонстрировать. Несколько упростив один из наших проектов. Мы решили построить следующее.
Ребята из .NET взяли на себя программирование микроконтроллера, парни из Java – облачную службу на Amazon, а наши «мобильные» коллеги – Android-приложение.
В презентации для студентов это звучало так:
Для демонстрации на стендовой сессии мы решили сделать отдельный девайс. Мы не поленились и сделали его классным в стимпанковском стиле – состарили деревянную коробку и нашли винтажные лампы.
Пара выходных и вечеров и решение было готово, и стильный девайс собран и работает.
Пара рабочих дней сверху и у нас готовы крутые дизайны плакатов, буклетов, стенда и футболок.
Как люди ответственные мы решили отрепетировать наш мастер-класс. Подготовили презентацию, собрали всех технических специалистов и устроили прогон. Программа мастер-класса была такая: последовательно показываем, как программируем контроллер, пишем службу, пишем прилож и в завершении каждой части зажигаем лампочку.
В течение трех прогонов мы научились укладываться в отведенный тайминг, отшлифовали код и текст и подготовили пару каверзных вопросов для студентов.
С утра мы одни из первых появились в НГУ, развернули стенд, убрали унылый стандартный стол и поставили нашу фирменную тумбу и на нее прям в центре выставили наш девайс. Он пользовался громадным успехом. На него приходили смотреть и фотографировать все – студенты, преподаватели, фотографы, конкуренты и даже охранники. Наши буклеты и девайс сработали прекрасно – мы собрали полную аудиторию на следующий день!
И вот настал день мастер-класса. Мы приехали в НГУ и тут мы поняли, что как не готовься, а в таких мероприятиях без затыков не бывает. Полная аудитория студентов, а у нас не подключется Mac к проектору, не пускает некоторые девайсы в универовский Wi-Fi и даже Амазоновские облака пытались встать в позу и не задеплоить наши 5 раз проверенные сервисы. Но не зря мы выставили дрим-тим и все было порешано «на лету» не выпадая из общего тайминга и структуры презентации и разработки ПО.
Мы дали общую вводную про нашу компанию, интернет-вещей и как мы это будем делать. Затем по шагам прошли все стадии программирования комплексного решения, раздавая сувениры и брендированные кружки наиболее активным, умным и хитрым студентам. Хитрым, потому что некоторые из них попробовали нас хакнуть, и подход был довольно неплох, за что были награждены отдельно :).
Да, это сильно напоминало хрестоматийное киношное провальное свидание, на котором все идет не так, но финал, как и положено по жанру, был счастливым. Программное обеспечение создано, сервисы развернуты в облаке, мобильное приложение встало на телефоны и лампочка зажглась в строго отведенное на это время. Студенты разглядели нашу «душу» ;) и сказали нам «да», оставшись после завершения презентации обсуждать с нашими ребятами проекты, решения, технологии.
А теперь к самому интересному для хабрачитателей – к коду.
Итак, напоминаем, что наше решение должно состоять из 3-х компонентов.
Сердцем нашего устройства является плата NodeMCU на базе контроллера ESP8266.
Из всего списка возможностей этой платы, нас интересуют поддержка беспроводных сетей Wi-Fi и GPIO — вводы/выводы общего назначения. Также, несмотря на то, что для этой платы нету ОС в привычном ее понимании, различные варианты прошивок поддерживают выполнение программ на языках C/C++, Lua, JavaScript и MicroPython.
Мы остановились на прошивке SMART.JS, программы для которой пишутся на языке JavaScript. Из возможностей этой прошивки будем использовать только http-клиент.
Нас интересует вывод номер 5 (GPIO5). Это цифровой вывод. Это означает, что на выходе у него может быть логический “0” или логическая “1”. При логическом 0 реле будет выключено, при логической 1 – реле будет включено, и лампочка будет гореть.
Пререквизиты:
1. SMART.JS документация: docs.cesanta.com/smartjs/latest [1]
2. SMART.JS прошивка: github.com/cesanta/smart.js [2]
3. FNC (утилита для загрузки прошивок и программ на JavaScript): github.com/cesanta/fnc [3]
4. Virtual COM port drivers: www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx [4]
5. Тестовый сервис: Тестовый сервис, который на GET запрос выдает JSON в формате:
{
“resource_name”: true/false
…
}
Итак, к делу.
Для начала прошиваем на нашу плату прошивку SMART.JS с помощью программы FNC. После этого у нас устройство начинает работать в режиме точки доступа и появляется сеть SMARTJS_???? (например, SMARTJS_FA352), пароль можно подсмотреть в консоли FNC.
Подключаемся к точке доступа и открываем адрес 192.168.4.1. У нас открывается конфигурационная страница, на которой мы вводим SSID и пароль для нашей сети. Сохраняем изменения, устройство перегружается, и мы готовы писать наше приложение.
Берем ваш любимый текстовый редактор и создаем файл app.js. Для начала выведем какое-нибудь приветственное сообщение в нашу консоль и определим вывод, к которому подключено реле, и имя ресурса, ассоциированного с лампочкой:
console.log('device started.');
var pin = 5;
var resource = 'light01';
Инициализируем наш вывод и установим на выходе логический 0:
GPIO.setmode(pin, 0, 0);
GPIO.write(pin, false);
Установим callback-функцию на изменения состояния подключения:
Wifi.changed(changedFunc);
Функция принимает параметром числовой статус подключения. Пока просто выведем его на консоль вместе с текстовым представлением этого статуса:
function changedFunc(state) {
console.log('Wifi state: ', state, Wifi.status());
}
Запустив приложение мы видим, что статусу “got ip” соответствует код 2. Теперь давайте при успешном подключении к сети отправим запрос к сервису:
function changedFunc(state) {
console.log('Wifi state: ', state, Wifi.status());
if (state == 2) {
mainFunc();
}
}
function mainFunc() {
Http.request({
hostname: 'ngurestexample.us-east-1.elasticbeanstalk.com',
port: 80,
path: '/',
method: 'GET'
}, function (response){
console.log(response.body);
})
.end()
.setTimeout(5000, function(){
console.log('timeout error');
});
}
Нам приходит ответ в виде строки: {“light01”: false}. Преобразуем его в JS объект и устанавливаем состояние на нашем выводе в соответствии с тем, что получили из сервиса:
var states = JSON.parse(response.body);
GPIO.write(pin, !!states[resource]);
Ну, и чтобы наше устройство периодически опрашивало сервис, поставим повторный вызов mainFunc через setTimeout в функцию обработки ответа сервиса и в функцию обработки таймаута запроса:
setTimeout(mainFunc, 1000);
Теперь наше устройство готово к работе.
Elastic Beanstalk – это один из полезных сервисов AWS для быстрого развертывания и масштабирования веб-приложений. Его использование освобождает нас от необходимости самостоятельно создавать и настраивать окружение. Сервис на выбор предоставляет несколько заранее сконфигурированных. Все, что от нас потребуется – это выбрать подходящее окружение и загрузить собранное приложение, используя интуитивно понятный UI. Остальное сделает сервис Beanstalk. На выходе мы получим URL, по которому приложение будет доступно по HTTP.
Выберем окружение Tomcat (сервлет контейнер) и следующую конфигурацию:
Эти же действия в картинках:
Кроме этого Beanstalk предоставляет возможность:
Перейдем к созданию нашего веб-приложения.
Цель — реализовать небольшое и простое приложение которое бы могло хранить состояние лампочек (off/on) и изменять это состояние. Клиенты абсолютно разные (микроконтроллер и мобильное приложение), поэтому разумно использовать архитектуру REST.
Как итог наше приложение будет предоставлять следующее стандартное REST API:
Писать код мы будем на Java, собирать результаты нашей работы с помощью Maven’а, а для написания кода используем библиотеку resteasy.
С помощью Maven подключим необходимые зависимости
[ github.com/EBTRussia/nsucareerdays2016/edit/master/cloud/sample-web-app-rest [5]]easy/pom.xml ]:
resteasy-jaxrs – для работы с jaxrs
resteasy-servlet-initializer – для интеграции с томкатом
resteasy-jackson2-provider – для работы с json
Почему resteasy. В мире Java существует много инструментов, которые позволяют создавать rest-приложения (Jersey, Spring, Spark, и т.д.). Мы просто выбрали один из них, который к слову входит в стандартную поставку сервера приложений WildFly.
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
public class LightAppController {
private ConcurrentMap<String, Boolean> resource = new ConcurrentHashMap<>();
@GET
public Map getAll() {
return resource;
}
@GET
@Path("/{resource}")
public Boolean get(@PathParam("resource") String r) {
return resource.get(r);
}
@PUT
@Path("/{resource}")
public Boolean put(@PathParam("resource") String r, Boolean status) {
if (resource.containsKey(r)) {
resource.put(r, status);
return status;
}
throw new WebApplicationException(Response.Status.NOT_FOUND);
}
@POST
@Path("/{resource}")
public Boolean post(@PathParam("resource") String r, Boolean status) {
resource.put(r, status);
return status;
}
@DELETE
@Path("/{resource}")
public void delete(@PathParam("resource") String r) {
resource.remove(r);
}
}
GET [7], PUT [8],
POST [9], DELETE [10] – Анотации из JAX-RS, которые показывают какими http методами обращаться к нашиму api
Path [11]("/{resource}") &
@PathParam(«resource») показывают по какому URL обращаться к api и какую часть URL мы хотим обрабатывать как параметр в логике приложения. В нашем случае, resource — имя/id для лампочки.
Для хранения состояния наших ламп мы используем ConcurrentHashMap [ docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html [12] ], потокобезопасный словарь ключей и значений.
Теперь превратим наш обычный класс в настоящий rest-сервис. Для этого создадим конфигурационный класс BaseApplication, который унаследуем от javax.ws.rs.core.Application. Внутри метода getSingletons() перечислим все классы, которые будут REST сервисами.
@ApplicationPath("")
public class BaseApplication extends Application {
@Override
public Set<Object> getSingletons() {
HashSet<Object> objects = new HashSet<>();
objects.add(new LightAppController());
return objects;
}
}
Собираем приложение. Идём в рутовую директорию нашего приложения и выполняем команду:
mvn clean install
После идём в созданную maven’ом папку target и забераем *.war файл, деплоим его на томкат с помощью сервиса BeansTalk.
Просто нажмём кнопку и всё должно взлететь:
Проверяем что всё работает:
Шаг 0. Подготовительный
Для создания Android-приложений используется система автоматической сборки Gradle. В его скрипте build.gradle мы подключим несколько зависимостей, которые упростят нам и написание кода, и жизнь:
dependencies {
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.squareup.retrofit2:retrofit:2.0.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
}
Библиотека Butterknife поможет нам проще настраивать графический интерфейс, а Retrofit идеально подходит в качестве HTTP клиента, если вы планируете взаимодействовать с REST-сервисом.
Шаг 1. Графический интерфейс
На пустой экран (activity) добавим компонент Switch, который как никто другой хорошо подходит для управления лампочкой, почти как настоящий выключатель:
<Switch
android:id="@+id/light_switch"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Лампочка"/>
Мы дали нашему компоненту уникальное имя light_switch и написали текст, который будет виден пользователю.
Теперь добавим переключатель в код нашей activity:
public class MainActivity extends AppCompatActivity {
@Bind(R.id.light_switch) SwitchCompat lightSwitch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
@OnClick(R.id.light_switch)
void onLightSwitchClicked(Switch lightSwitch) {
boolean checked = lightSwitch.isChecked();
Toast.makeText(this, "Switch checked = " + checked, Toast.LENGTH_SHORT).show();
}
}
Мы воспользовались двумя аннотациями из библиотеки ButterKnife: Bind [13] и OnClick [14]. Первая связывает наш переключатель, объявленный в xml-разметке с тем переключателем, что объявлен в коде. Второй устанавливает метод onLightSwitchClicked() в качестве обработчика клика по нашему переключателю.
Шаг 2. HTTP-клиент
Создать http-клиент совсем не сложно, если прибегнуть к помощи библиотеки retrofit. Нужно лишь создать интерфейс и описать в нём все запросы к серверу в качестве его методов, после чего скормить этот интерфейс в retrofit, который самостоятельно создаст подходящую реализацию нашего интерфейса.
public interface WebApi {
@GET("/")
Call<Map<String, Boolean>> list();
@POST("/{resource}")
Call<Boolean> switchBulb(@Path("resource") String resource, @Body Boolean enabled);
}
В нашем интерфейсе всего 2 метода:
webApi = new Retrofit.Builder()
.baseUrl(getString(R.string.api_url))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(WebApi.class);
Здесь мы указываем библиотеке retrofit свой базовый url, сообщаем, что запросы и ответы будут в формате JSON и указываем только что написанный интерфейс, после чего HTTP-клиент уже готов к работе.
Шаг 3. Дружим UI и HTTP-клиент
Во-первых, нужно получить и отобразить актуальное состояние лампочки. Сделаем GET-запрос и поищем в вернувшемся словаре лампочку с именем “light01”:
webApi.list().enqueue(new Callback<Map<String, Boolean>>() {
@Override
public void onResponse(Call<Map<String, Boolean>> call, Response<Map<String, Boolean>> response) {
Boolean light01Enabled = response.body().get("light01");
lightSwitch.setChecked(Boolean.TRUE == light01Enabled);
}
@Override
public void onFailure(Call<Map<String, Boolean>> call, Throwable t) {
}
});
Во-вторых, при переключении лампочки на клиенте нужно уведомлять об этом сервер. Мы уже написали обработчик клика по переключателю на предыдущем шаге, теперь добавим в него выполнение http-запроса:
@OnClick(R.id.light_switch)
void onLightSwitchClicked(Switch lightSwitch){
boolean checked = lightSwitch.isChecked();
webApi.switchBulb("light01", checked).enqueue(new Callback<Boolean>() {
@Override
public void onResponse(Call<Boolean> call, Response<Boolean> response) {
Toast.makeText(MainActivity.this, "Light01 changed", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<Boolean> call, Throwable t) {
}
});
}
И-и-и, лампочка гори!
С полным кодом приложения для устройства можно ознакомиться здесь: github.com/EBTRussia/nsucareerdays2016/blob/master/hw/app.js [15]
Мастер-классом и этим примером мы хотели показать – классные и интересные вещи можно делать за короткое время и минимумом усилий. Нужна идея. Дерзайте! Именно из простых и нужных вещей рождаются целые отрасли, такие как интернет вещей.
И приходите к нам работать :). У нас действительно уникальный коллектив и очень интересные проекты.
До встречи!
Автор: EastBanc Technologies
Источник [16]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/117440
Ссылки в тексте:
[1] docs.cesanta.com/smartjs/latest: https://docs.cesanta.com/smartjs/latest/
[2] github.com/cesanta/smart.js: https://github.com/cesanta/smart.js
[3] github.com/cesanta/fnc: https://github.com/cesanta/fnc
[4] www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx: https://www.silabs.com/products/mcu/Pages/USBtoUARTBridgeVCPDrivers.aspx
[5] github.com/EBTRussia/nsucareerdays2016/edit/master/cloud/sample-web-app-rest: https://github.com/EBTRussia/nsucareerdays2016/edit/master/cloud/sample-web-app-rest
[6] github.com/EBTRussia/nsucareerdays2016/blob/master/cloud/sample-web-app-resteasy/src/main/java/ru/ebt/LightAppController.java: https://github.com/EBTRussia/nsucareerdays2016/blob/master/cloud/sample-web-app-resteasy/src/main/java/ru/ebt/LightAppController.java
[7] GET: https://habrahabr.ru/users/get/
[8] PUT: https://habrahabr.ru/users/put/
[9] POST: https://habrahabr.ru/users/post/
[10] DELETE: https://habrahabr.ru/users/delete/
[11] Path: https://habrahabr.ru/users/path/
[12] docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html
[13] Bind: https://habrahabr.ru/users/bind/
[14] OnClick: https://habrahabr.ru/users/onclick/
[15] github.com/EBTRussia/nsucareerdays2016/blob/master/hw/app.js: https://github.com/EBTRussia/nsucareerdays2016/blob/master/hw/app.js
[16] Источник: https://habrahabr.ru/post/281238/
Нажмите здесь для печати.