...

понедельник, 14 октября 2013 г.

О протоколах замолвите слово… Или давайте разрабатывать чайник вместе!

На самом деле не только о протоколах, а скорее о логике взаимодействия сложных систем. Это не BigData, конечно, но все равно есть над чем поломать голову и копья :)



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

Вот вы задумались когда-нибудь, насколько сложная логика работает, когда вы отправляете твит или постите фотку? Ее не видно, в 99.9% она скрыта от пользователя, и ее совершенно незаметно. Я ее тоже не замечал, до того момента, когда мне пришлось проектировать ее самому.


Только давайте договоримся сразу — участие в разработке это не просто высказывание «а запилите мне вот такую фичу», а решением множества маленьких вопросов из разных областей.

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

Если вам это интересно, и вы готовы осмыслить то, что мы уже сделали, и внести свой вклад в устройство — добро пожаловать. Мы начинаем!


Поехали!


Вот делаете вы чайник. Пока он управляется напрямую с телефона, как наш прототип — все просто и логично:



Есть локальная сеть, есть клиенты, есть устройство. Нет ни сервера, ни аккаунтов, ни авторизации. Красота. Покупаем чайник, приносим домой, и подключаем его к сети. А как подключаем?

Первое использование


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

Сделать это можно несколькими путями: можно кнопочками набить имя сети и пароль, можно передать морганиями экрана телефона, или писком в кодах DTMF, а можно создать известную заранее WiFi-сеть, к которой надо подключиться и настроить чайник внутри ее с помощью приложения. Посовещавшись, мы решили использовать все-таки последний вариант. Это не окончательное решение, и читатели, как будущие пользователи, могут на него повлиять — после статьи выбрать вариант в голосовании.

Но пока я буду исходить из варианта WiFi-авторизации.



При первом включении устройство создает открытую WiFi-сеть с заранее оговоренным именем(чтобы ее можно было найти), но с рандомной вставкой в конце(чтобы несколько одновременно включенных чайников не помешали друг другу), что-то вроде AVI-WifiKettle-6574.

Приложение подключается к ней, спрашивает у пользователя — создать сеть или подключиться к текущей, и имя с паролем. И в зависимости от предыдущего вопроса чайник после окончания конфигурации или создает сеть, или подключается к текущей.


Все просто и понятно. Так же довольно просто выглядит передача команды от приложения к устройству:



Приложение подключается к чайнику, передает тому команду. После проверки команды на правильность(парсер отработал без ошибок, тип устройства и версия языка соответствуют целевым), чайник говорит о том, что команду получил, и начинает ее выполнять. После выполнения команды чайник сообщает о том, что команда выполнена, и приложение может порадовать пользователя сообщением «Ваш чайник вскипел».


Усложняем задачу. Теперь у нас есть еще и сервер для удаленного управления.


Но не все так просто. Мы же хотим удаленное управление, правильно? Для этого нам придется ввести еще один элемент — сервер. Схема приобретает следующий вид:



Не очень сложно выглядит. А вот сложность логики возрастает при добавлении сервера на порядок.

Вот, например как со стороны приложения стало выглядеть первое включение:



Появилась авторизация(мы же не хотим, чтобы кто угодно мог включить наш чайник), а значит поддержка аккаунтов. При включении программа должна спросить у пользователя, желает ли он войти в уже существующий аккаунт, или создать новый. Чтобы как-то отличать устройства друг от друга, мы вводить серийный номер устройства UUID-D(Device). Его генерирует программа и передает устройству при первом подключении. В дальнейшем, из устройства пользователь никак не сможет получить UUID-D, только полностью сбросить и задать новый. В то же время, не зная серийного номера устройства, нельзя отправить ему команду. В итоге, серийный номер знает только то приложение, которое подключилось к устройству первый раз. Серийный номер хранится на сервере в аккаунте пользователя(если он его создал), так что потеря ему не грозит. Но вот новая проблема — устройством могут управлять несколько людей, которые имеют разные аккаунты. Значит, нужен метод передачи прав на устройство другим людям:



Для того, чтобы не усложнять логику работы, мы отказались от системы разделения прав, и даем равные права всем, у кого в аккаунте есть UUID-D нужного устройства. Это значит, что как только первое приложение передает права на устройства другому аккаунту, он получает полный контроль над устройством, и сможет таким же образом передавать права другим аккаунтам. Это избавляет от необходимости искать того, кто лишил девственности чайник «хозяина» чайника, если хочется дать кому-то права, но в случае, если сотрудник ушел с работы и у него в аккаунте остался неотвязанный UUID-D, он сможет вероломно кипятить офисный чайник ночью. И сделать с этим ничего нельзя, кроме как сбросить устройство и настроить его заново — при этом оно получит новый UUID-D, а записи со старым сотрутся из акаунтов и БД. Определить что можно стирать старые записи просто — при инициализации на сервер отправляется MAC-адрес устройства, и если он уже есть в БД — значит устройство инициализируется заново, и старые записи нам не нужны.


О уникальных номерах


Кстати UUID — это тоже очень интересное решение, когда я про него узнал, долго радовался. :)

Например, у вас есть сервер, на котором кто-то создает аккаунты. Или добавляет файлы. Или записи. Или еще что-то, неважно. Как вы будете нумеровать сущности на сервере? Первый ответ — по порядку. Человек загружает фотку — она называется 00001. Загружает следующую — она будет называться уже 00002, третью — 00003. И так далее. Так как нумерует фотографии сервер, проблем нет — он смотрит на предыдущую загруженную фотку, прибавляет к ее номеру единичку, и записывает. Все отлично.

Но вот ваш сервис развивается, и вам уже нужен еще один сервер. Но вот проблема — вам нужна синхронизация между серверами. И если файлы можно синхронизировать еще относительно медленно, или не синхронизировать совсем, то информацию о последнем имени файла нужно всегда иметь актуальную. Иначе, если два пользователя на разных серверах загрузят фотки с небольшим промежутком, информация может еще не обновиться на втором сервере, и файлы получает одинаковые номера. И в итоге, вместо котенка, который загрузил Петя, чтобы отправить своей девушке, обнаружится фото девушки Васи. И Пете будет очень сильно постараться, чтобы доказать получившей открытку своей девушке, что это «просто ошибка на сервере, так бывает». Да и Вася будет огорчен, найдя котенка, а не свою девушку.



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

Например, можно генерировать случайное число, достаточно большое для того, чтобы оно не совпало в обозримом будущем. А еще хорошо, для активно изменяемых данных генерировать его в зависимости от текущего времени — тогда для вероятного совпадения двух файлов мало того, что должны совпасть два рандомных числа, так они еще и должны совпасть в одну и ту же секунду! А вот вероятность этого сочетания очень мала, может быть потому котята и не превращаются в фотографии чужих девушек :(

Короче, «все уже стандартизовано до нас!». Есть уже готовый стандарт на этот случай, описываемый в RFC 4122 — Universally Unique Identifier, а попросту UUID, он выглядит вот так: 65C4B640-3452-11E3-AA6E-0800200C9A66, и всего их может быть 340000000000000000000000000000000000000 штук, или по-простому 3.4 додециллиона.

Но что-то я отвлекся на сиськи додециллионы. Вот как первое включение выглядит для чайника:



По сравнению с простой авторизацией «устройство-приложение» тут тоже добавилось много нового: устройству назначается UUID, оно пытается соединиться с сервером и отправляет туда UUID и MAC-адрес WiFi-модуля(зачем это нужно, я ужерассказывал).


Безопасность




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

Да, я уже слышу возражения из зала — а как быть с теми, кому аккаунт удобен? У человека может быть несколько устройств в аккаунте, несколько телефонов, или он часто обновляется, после чего приложение приходится ставить заново. Аккаунты де-факто есть, но пользоваться ими трудно — попробуйте запомнить, что у вам логин A811BF51-98B8-4604-9A8C-D72D8E40D846, а пароль 7960E625-912D-4625-BC9E-08E9B3047219 :)

Конечно, мы не будем так мучать пользователей, поэтому надо им дать возможность переименовать аккаунт. Поскольку пользователь не знает, что у него аккаунт уже создан, не надо разрушать его иллюзию простоты, и можно назвать пункт «создать аккаунт». Но скрываться под этим будет именно переименование:



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

Работа с командами


И теперь мы приступаем к самому интересному — отправке команд на устройство. Приложение делаем это вот так:



Следует заметить, что приложение отправляем команды теперь на сервер, а не на устройство напрямую. Еще там реализована блокировка ситуации, когда несколько приложений пытаются перебить друг друга разными командами. Скажем, Петя сидит дома и включил чайник на поддержание температуры. А девушка Пети, еще обиженная на него за открытку с чужой девушкой не хочет звонить ему и попросить подогреть чайник, а пытается сделать этого со своего телефона. К сожалению, этот жест обиды не будет воспринят как она ожидала — приложении на ее телефоне сообщит о ошибке, потому что на чайнике уже другая команда запущена на выполнение. Но в тоже время, при такой логике для любой отмены команды требуется сначала отправить команду очистки очереди, а только потом — новую команду. Это будет не очень удобно для Пети, который ошибся кнопкой, и нажал кнопку нагрева до температуры детской смеси(36 градусов), а не кнопку нагрева до 90, для чая. Он понимает что ошибся, нажимает другую кнопку — и приложение сообщает о ошибку. Ну все правильно — команда-то уже выполняется. Пришлось сделать дополнительный обработчик, который отменяет команду без подтверждения, если она до этого в этой же программе уже была запущена другая команда.

В итоге, мы пришли к логике работы, которая устраивает нас: с того телефона, с которого отправлена команда, ее можно заменить другой без лишних вопросов, а с других телефонов — нельзя. Потому что негоже мешать другому человеку кипятить чай.

Итак, приложение отправляет серверу команду, а сервер… Вот что сервер:



Проверяет права(может ли Петя включить вот этот конкретный чайник), назначает команде серийный номер, сохраняет ее в БД, и пытается отправить ее по открытому соединению на устройство. Если соединения нет, он подождет пока оно появится. После отправки, сервер принимает сообщение о успешной доставке команды, и перенаправляет его приложению. А после того, как команда завершена — записывает себе в лог, удаляет команду из БД, и отправляет уведомление о выполнении команды на чайнике с UUID-D всем, у кого этот UUID-D записан в аккаунте.

Так что девушка Пети, подходя к дому все-таки получит уведомление о том, что чайник вскипел, поймет что он грел чайник к ее приходу, и простит наконец Петю за эту долбанную открытку.


А чайнику, вечному труженику, получая команду от сервера, ничего не остается, как ее исполнить:



Конечно, под маленьким квадратиком «Выполнение команды» скрывается блок схема не меньше(а то и обширнее этой), но это совсем другая история…


Как обычно:

1)О грамматических и пунктуационных ошибках сообщать в приват

2)О ошибках в логике, предложениях, мыслях — в комментарии.

3)Устраивать срачи — разрешено.

4)Подписываемся на тех чуваков, которые делают чайник в imageна странице компании(кнопка «подписаться»)

5)Рассмотреть картинку в фуллсайзе можно тут.

6)А забрать ее исходник в формате программы OmniGraffle для Mac можно тут. Незнаю зачем, но вдруг кто-то возьмет ее за основу, мне будет приятно.


This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers. Five Filters recommends:



Комментариев нет:

Отправить комментарий