В данной статье я хочу рассказать о небольшом и забавном проекте выходного дня по передаче файлов через анимированные QR коды. Проект написан на Go, с использованием Gomobile и Gopherjs – последний для веб-приложения для автоматического замера скорости передачи данных. Если вам интересна идея передачи данных через визуальные коды, разработка веб-приложений не на JS или настоящая кроссплатформенность Go — велкам под кат.
Идея проекта родилась из конкретной задачи для мобильного приложения – как наиболее просто и быстро передать небольшую порцию данных (~15КБ) в другое устройство, в условиях блокировок сети. Первой мыслью было использовать Bluetooth, но это не так удобно, как кажется – относительно долгий и не всегда работающий процесс обнаружения и спаривания устройств слишком затрудняет задачу. Неплохая идея была бы использовать NFC (Near Field Communication), но до сих пор слишком много устройств, в которых поддержка NFC ограничена или отсутствует вообще. Нужно было что-то проще и доступнее.
Как насчёт QR кодов?
QR коды
QR (Quick Response) код – это самый популярный в мире вид визуальных кодов. Он позволяет кодировать до 3КБ произвольных данных и имеет различные уровни коррекции ошибок, позволяя уверенно читать даже на треть закрытый или загрязнённый код.
Но с QR кодами две проблемы:
- 3КБ недостаточно
- чем больше данных закодировано, тем выше требования к качеству картинки для сканирования
Вот так выглядит QR код 40-й версии (самая высокая плотность записи) с 1276 байтами:
Для моей задачи нужно было научиться передавать ~15KB данных, на стандартных устройствах (смартфонах/планшетах), поэтому сам собой возник вопрос – а почему бы не анимировать последовательность QR кодов и передать данные кусками?
Быстрый поиск по уже готовым реализациям навёл на несколько таких проектов – в основном проекты на хакатонах (хотя встретилась и дипломная работа) – но все были написаны на Java, Python или JavaScript, что, к сожалению, делало код практически непортируемым и неиспользуемым. Но учитывая большую популярность QR кодов и низкую техническую сложность идеи, было решено написать с нуля на Go — кросс-платформенном, читабельном и быстром языке. Обычно под кросс-платформенностью подразумевают возможность собрать бинарный код под Windows, Mac и Linux, но в моём случае тут была важна ещё и сборка под веб (gopherjs) и под мобильные системы (iOS/Android). Go даёт всё это из коробки с минимальными затратами.
Я рассматривал также альтернативные варианты визуальных кодов – такие как HCCB или JAB Code, но для них бы пришлось писать OpenCV-сканер, имплементировать с нуля кодер/декодер и это было чересчур для проекта на одни выходные. Круговые QR коды (shotcodes), и их аналоги, используемые в Facebook, Kik и Snapchat позволяют закодировать намного меньше информации, а невероятно крутой патентованный подход Apple для спаривания Apple Watch и iPhone — анимированное облако разноцветных частиц — также оптимизирован под wow-эффект, а не под максимальную пропускную способность. QR коды же интегрированы в нативные SDK камер мобильных OS, что сильно облегчает работу с ними.
TXQR
Так родился проект txqr (от Tx — transmission, и QR), реализующий библиотеку для кодирования/декодирования QR на чистом Go и протокол для передачи данных.
Основная идея в следующем – один клиент выбирает файл или данные для отправки, программа на устройстве разбивает файл на куски, кодирует каждый из них в QR фреймы и показывает их в бесконечном цикле с заданной частотой кадров пока получатель не получит все данные. Протокол сделан таким образом, что получатель может начать с любого кадра, получать QR фреймы в любом порядке – таким образом обходится проблема необходимости синхронизации частоты анимации и частоты сканирования. Получатель может быть старым устройством, мощность которого позволяет декодировать 2 кадра в секунду, а отправитель – новым смартфоном выдающим 120Гц анимацию, или наоборот, и это не будет фундаментальной проблемой для протокола.
Это достигается следующим образом – когда файл разбивается на куски (фреймы далее), к каждому фрейму в начало добавляется префикс с информацией о смещении относительно всех данных и общая длина — OFFSET/TOTAL|
(где OFFSET и TOTAL — целочисленные значения смещения и длины соответственно). Бинарные данные пока что кодируются в Base64, но это на самом деле не обязательно – QR спецификация позволяет не только кодировать данные как бинарные, но и оптимизировать различные части данных под разные кодировки (например, префикс с небольшими изменениями можно закодировать как alphanumeric, а остальное содержимое – как binary), но для простоты Base64 отлично выполнял свою функцию.
Более того, размер фреймов и частоту можно даже менять динамически, подстраиваясь под возможности получателя.
Сам протокол очень прост, и главный его минус в том, что для больших файлов (хотя, это и выходит за рамки задачи, но всё же), один пропущенный при сканировании кадр увеличит время сканирования вдвое – получателю придётся ждать полного цикла снова. В теории кодирования есть решения для таких случаев – фонтанные коды, но это я оставлю для каких-нибудь следующих свободных выходных.
Самым интересным моментом было написать мобильное приложение, которое может использовать этот протокол.
Gomobile
Если вы не слышали о gomobile, то это проект, который позволяет использовать Go библиотеки в iOS и Android проектах и делает это до не приличия простой процедурой.
Стандартный процесс таков:
- вы пишете обычный Go код
- запускаете
gomobile bind ...
- копируете получившиеся артифакт(ы) (
yourpackage.framework.
илиyourpackage.aar
) в ваш мобильный проект - импортируете
yourpackage
и работаете с ним, как с обычной библиотекой
Можете сами попробовать насколько это просто.
Поэтому я довольно быстро написал приложение на Swift, которое сканирует QR коды (благодаря вот этой замечательной статье) и декодирует их, склеивает и, когда весь файл получен, показывает в окошке предпросмотра.
Будучи новичком в Swift (хоть я и прочёл книгу по Swift 4), было немало моментов, когда я застревал на чём-то простом, пытаясь понять, как это правильно делать и, в итоге, наилучшим решением было реализовать этот функционал на Go и использовать через Gomobile. Не поймите меня не правильно, Swift во многом замечательный язык, но, как и большинство остальных языков программирования, он даёт слишком много способов сделать одно и то же, и уже имеет приличную историю обратно-несовместимых изменений. Например, мне нужно было делать простую вещь – замерять продолжительность события с миллисекундной точностью. Поиск в Google и StackOverflow приводил к массе различных, противоречивых и, зачастую, устаревших решений, ни одно из которых, в итоге не выглядело ни красивым для меня, ни корректным для компилятора. После 40 минут потраченного времени, я просто сделал ещё один метод в Go пакете, который вызывал time.Since(start) / time.Millisecond
и использовал его результат из Swift напрямую.
Я также написал консольную утилиту txqr-ascii
для быстрого тестирования приложения. Она кодирует файл и анимирует QR коды в терминале. Всё вместе это работало на удивление хорошо – я мог отправить небольшую картинку за несколько секунд, но, как только я начал тестировать различные значения частоты кадров, количества байт в каждом QR фрейме и уровень коррекции ошибок в QR кодировщике, стало понятно, что терминальное решение не сильно справляется с высокой частотой (больше 10) анимации, и что тестировать и замерять результаты вручную это гиблое дело.
TXQR Tester
Чтобы найти оптимальную комбинацию частоты кадров, размера данных в QR фрейме и уровня коррекции ошибок среди разумных пределов этих значений, мне необходимо было прогнать более 1000 тестов, вручную меняя параметры, ожидая полного цикла с телефоном в руке и записывая результаты в табличку. Конечно, это должно быть автоматизировано!
Тут и появилась идея следующего приложения — txqr-tester
. Изначально я планировал использовать x/exp/shiny — экспериментальный UI фреймворк для нативных desktop-приложений на Go, но, похоже, он заброшен. Около года назад я его пробовал, и впечатление было неплохое – для низкоуровневых вещей он подходил идеально. Но сегодня master-ветка даже не скомпилировалась. Похоже, стимулов вкладываться в развитие desktop-фреймворков – сложной и громоздкой задачи, с почти нулевым нынче спросом – уже нет, все UI решения перешли давно в веб.
В веб-программирование, как известно, языки программирования только-только начали заходить, благодаря WebAssembly, но это совсем пока детские первые шаги. Конечно, есть ещё JavaScript и надстройки, но друзья не позволяют друзьям писать приложения на JavaScript, поэтому я решил использовать недавнее своё открытие – фреймворк Vecty, который позволяет писать фронтенды на чистом Go, которые автомагически конвертируются в JavaScript с помощью очень взрослого и удивительно хорошо работающего проекта GopherJS.
Vecty и GopherJS
Я в жизни такого удовольствия не получал от разработки фронтенд интерфейсов.
Чуть позже я планирую написать ещё пару статей про свой опыт разработки фронтендов на Vecty, в том числе и WebGL приложений, но суть в том, что после нескольких проектов на React, Ангулярах и Ember, писать фронтенд на продуманном и простом языке программирования это глоток свежего воздуха! Я могу писать достаточно симпатичные фронтенды за короткое время и при этом не писать ни одной строки на JavaScript!
Для затравки, вот как вы начинаете новый проект на Vecty (никаких кодогенераторов "начального проекта", создающих тонны файлов и папок) — просто main.go:
ackage main
import (
"github.com/gopherjs/vecty"
)
func main() {
app := NewApp()
vecty.SetTitle("My App")
vecty.AddStylesheet(/* ... add your css... */)
vecty.RenderBody(app)
}
Приложение, как и любой UI компонент — это всего лишь тип: структура, которая включает тип vecty.Core
и должна реализовать интерфейс vecty.Component
(состоящий из одного метода Render()
). И это всё! Дальше вы оперируете с типами, методами, фунциями, библиотеками для работы DOM и так далее – никакой скрытой магии и новых терминов и концепций. Вот упрощённый код главной странички:
/ App is a top-level app component.
type App struct {
vecty.Core
session *Session
settings *Settings
// any other stuff you need,
// it's just a struct
}
// Render implements the vecty.Component interface.
func (a *App) Render() vecty.ComponentOrHTML {
return elem.Body(
a.header(),
elem.Div(
vecty.Markup(
vecty.Class("columns"),
),
// Left half
elem.Div(
vecty.Markup(
vecty.Class("column", "is-half"),
),
elem.Div(a.QR()), // QR display zone
),
// Right half
elem.Div(
vecty.Markup(
vecty.Class("column", "is-half"),
),
vecty.If(!a.session.Started(), elem.Div(
a.settings,
)),
vecty.If(a.session.Started(), elem.Div(
a.resultsTable,
)),
),
),
vecty.Markup(
event.KeyDown(a.KeyListener),
),
)
}
Вы, наверняка, сейчас смотрите на код и думаете – насколько же это голословная работа с DOM! Я тоже так сначала подумал, но, как только начал работать, осознал, насколько это удобно:
- Нет магии – каждый блок (Markup или HTML) это лишь переменная нужного типа, с чёткими лимитами куда что можно поставить, благодаря статической типизации.
- Нет открывающих/закрывающих тэгов, которые нужно либо не забывать менять при рефакторинге, либо использовать IDE, которая делает это за вас.
- Структура вдруг становится понятной – я никогда, например, не понимал, почему в React до 16-й версии нельзя было вернуть несколько тегов из компонента – это же "просто строка". Увидев, как это делается в Vecty, вдруг стало понятно, откуда корни росли у того ограничения в React. Всё равно не понятно, правда, почему после React 16 стало можно, но и не нужно.
В общем, как только вы попробуете такой подход работы с DOM, то его плюсы станут сильно очевидны. Минусы тоже есть, безусловно, но после минусов привычных методов, они незаметны.
Vecty называют React-подобным фреймворком, но это не совсем так. Для React есть нативная GopherJS библиотека – myitcv.io/react, но я не думаю, что это хорошая идея повторять архитектурные решения React для Go. Когда вы пишете фронтенд на Vecty, вдруг становится ясно, насколько всё на самом деле проще. Вдруг становится лишней вся эта скрытая магия и новые термины и концепции, которые каждый JavaScript фреймворк изобретает – они просто добавочная сложность, ничего более. Всё что нужно – это ясно и чётко описывать компоненты, их поведение, и связывать их между собой — типы, методы и функции, вот и всё.
Для CSS я использовал на удивление достойный фреймворк Bulma – у него очень понятное именование классов и хорошая структура, и декларативный UI код с его помощью очень читабелен.
Настоящая магия, впрочем, начинается, когда компилируешь Go код в JavaScript. Это очень пугающе звучит, но, на самом деле, вы просто вызываете gopherjs build
и менее чем через секунду, у вас готов автосгенерированный JavaScript файл, готовый чтобы включать в вашу базовую HTML страницу (обычное приложение состоит только из пустого body-тега и включения этого JS-скрипта). Когда я впервые запускал эту команду, то ожидал видеть массу сообщений, предупреждений и ошибок, но нет – она отрабатывает фантастически быстро и молча, в консоль выводит только однострочники в случае ошибок компиляции, которые сгенерированы Go компилятором, поэтому очень понятны. Но ещё круче было видеть ошибки в консоле браузера, со стектрейсами, указывающими на .go файлы и правильную строку! Это очень круто.
Тестирование параметров QR анимации
За несколько часов у меня было готово веб-приложение, которое позволяло мне быстро менять параметры для тестирования:
- FPS — частоту кадров
- QR Frame Size — сколько байт должно быть в каждом фрейме
- QR Recovery Level — уровень корректировки ошибок QR
и запускать тест автоматически.
Мобильное приложение, конечно, тоже должно было быть автоматизировано – оно должно было понимать, когда начинается следующий раунд с новыми параметрами, понимать, когда приём занимает слишком много времени и обрывать раунд, отправлять результаты приложению и так далее.
Загвоздка была в том, что веб-приложение, будучи запущенным в песочнице браузера, не может создавать новые соединения, и, если я не ошибаюсь, единственная возможность настоящего peer-to-peer соединения с браузером есть только через WebRTC (NAT мне пробивать не нужно), но это было чересчур громоздко. Веб-приложение могло быть только клиентом.
Решение было простое – веб-сервис на Go, который отдавал веб-приложение (и запускал браузер на нужный URL), так же запускал WebSocket-прокси для двух клиентов. Как только к нему присоединяются два клиента – он прозрачно отправляет сообщения из одного соединения в другое, позволяя клиентам (веб-приложению и мобильному клиенту) общаться напрямую. Они, должны быть для этого, в одной WIFI-сети, конечно же.
Оставалась проблема того, как сказать мобильному устройству, куда, собственно, подключаться, и она была решена с помощью… QR кода!
Процесс тестирования выглядит так:
- мобильное приложение ищёт QR код со стартовым маркером и ссылке на WebSocket-прокси
- как только маркер считан, приложение подключается к данному WebSocket-прокси
- веб-приложение (будучи уже подключенным к прокси) понимает, что мобильное приложение готово и показывает QR код с маркером "готов к следующему раунду?"
- мобильное приложение распознает сигнал, обнуляет декодер, и отправляет через WebSocket сообщение "угу".
- веб-приложение, получив подтверждение, генерирует новую QR анимацию и крутит её, пока не получит результаты или таймаут.
- результаты складываются в табличку рядом, которую можно тут же скачать в виде CSV
В итоге, всё что мне оставалось – просто поставить телефон на штатив, запустить приложение и дальше две программы сами делали всю грязную работу, вежливо общаясь через QR-коды и WebSocket :)
В конце я скачивал CSV файл с результатами, загонял его в RStudio и в Plotly Online Chart Maker и анализировал результаты.
Результаты
Полный цикл тестирования занимает около 4 часов (к сожалению, самая тяжелая часть процесса — генерация анимированного GIF изображения с QR фреймами, должна была запускаться в браузере, и, поскольку, результирующий код всё таки в JS, то используется только один процессор), в течении которых, нужно было следить, чтобы внезапно не погас экран или какое-то приложение не закрыло окно с веб-приложением. Тестировались следующие параметры:
- FPS — от 3 до 12
- Размер QR фрейма — от 100 до 1000 байт (с шагом в 50)
- Все 4 уровня коррекции ошибок QR (Low, Medium, High, Highest)
- Размер передаваемого файла — 13КБ рандомно сгенерированных байт
Через несколько часов я скачал CSV и стал анализировать результаты.
Картинка важнее тысячи слов, но интерактивные 3D-визуализации важнее тысячи картинок. Вот такая визуализация полученных результатов (кликабельно):
Наилучший полученный результат был 1.4 секунды, что примерно равно 9КБ/с! Этот результат был записан на частоте 11 кадров в секунду, размере фрейма 850 байт и среднем (medium) уровне коррекции ошибок. В большинстве случаев, правда, на такой скорости декодер камеры пропускал некоторые кадры, и приходилось ждать следующего повтора пропущенного фрейма, что сильно негативно сказывалось на результатах – вместо двух секунд легко могло получиться 15, или таймаут, который был выставлен в 30 секунд.
Вот графики зависимости результатов от меняемых переменных:
Время / размер фрейма
Как видно, при низких значениях количества байт в каждом фрейме, избыток кодирования слишком велик и общее время считывания, соответственно, тоже. Некий локальный минимум есть в 500-600 байт на фрейм, но значения рядом всё равно приводят к потерянным кадрам. Наилучший результат наблюдался на 900 байт, но 1000 и выше это почти гарантированная потеря кадров.
Время / FPS
Значение количества кадров в секунду, к моему удивлению, сильно большого эффекта не имело – маленькие значения слишком увеличивали время общей передачи, а большие повышали вероятность пропущенного кадра. Оптимальное значение, судя по этим тестам, находится в районе 6-7 кадров в секунду для тех устройств, на которых я тестировал.
Время / Уровень коррекции ошибок
Уровень коррекции ошибок показал чёткую взаимосвязь между временем передачи файла и уровнем избыточности, что неудивительно. Однозначный победитель тут это низкий (L) уровень коррекции – чем меньше избыточных данных, тем более читабелен для сканера QR код при том же размере данных. По факту, для этого эксперимента избыточность не нужна вообще, но стандарт такого варианта не предлагает.
Конечно же, для более объективных данных этот тест должен быть запущен сотни и тысячи раз, на разных устройствах и разных экранах, но для моего эксперимента выходного дня, это был более чем достаточный результат.
Выводы
Этот забавный проект доказал, что односторонняя передача данных через анимированные коды, безусловно, возможна, и для ситуаций, где нужно передать небольшой объем при отсутствии любых видов сетей, вполне подходит. Хотя мой максимальный результат был около 9КБ/с, в большинстве случаев реальная скорость составляла 1-2КБ/с.
Я также получил настоящее удовольствие, используя Gomobile и GopherJS с Vecty в качестве уже обыденного инструмента для решения проблем. Это очень зрелые проекты, с отличной скоростью работы, и, в большинстве случаев дающие опыт "оно просто работает".
Напоследок, я по прежнему восхищаюсь, насколько продуктивным можно быть с Go, когда чётко знаешь, что хочешь реализовать – экстремально короткий цикл "изменить"-"собрать"-"проверить" позволяет экспериментировать много и часто, простой код и отсутствие классовой иерархии в структуре программ даёт возможность легко и безболезненно их рефакторить на ходу, а фантастическая кроссплатформенность, заложенная в язык с самого начала позволяет написать код один раз и использовать его на сервере, на веб-клиенте и в нативном мобильном приложении. При этом, несмотря на более, чем достаточную производительность из коробки, по прежнему есть масса пространства для оптимизации и ускорения.
Так что если вы никогда не пробовали Gomobile или GopherJS – я рекомендую вам попробовать при следующей возможности. Это заберёт час вашего времени, но, возможно, откроет вам целый новый пласт возможностей в веб или мобильной разработке. Смело пробуйте!
Ссылки
Комментарии (84)
Yakud
21.11.2018 23:07А если снимать в замедленном режиме iPhone и увеличить FPS. Может быть получится добиться лучших результатов?
divan0 Автор
21.11.2018 23:57Ммм, интересный вопрос. Замедленный режим позволяет снимать с высоким FPS, но скорость с которой AVFoundation может распознавать коды на кадрах, по идее, константа и будет определять реальную скорость. При этом, полагаю, slow motion режим как-то особенно реализован и в нём детект лиц и кодов выключен, поэтому придётся записывать, а потом проигрывать на оптимальной скорости – что уже удлиняет и усложняет весь процесс. Но идея интересная.
evocatus
21.11.2018 23:12Проблема с UI на веб-технологиях в том, что HTML и CSS ещё хуже, чем JS. Javascript — обычный динамический язык программирования, не без недостатков, но и со своей красотой. А вот вёрстка… там такое количество исторических артефактов, несовместимостей, всяких вендорных префиксов, логических несоответствий, что JS по сравнению с этим просто образец стройности.
Попробуйте объяснить новичку как отцентрировать что-то; как сделать несколько элементов в одну строку с разным содержимым, но одинаковой высотой и пр. В итоге даже простые вещи на HTML+CSS делать на порядок сложнее, чем на нативных технологиях, а вещи сложные всё равно сможет сделать только человек, вбухавший в это дело кучу лет.
P.S. Если вы знаете ресурсы где такие вещи объясняются, эдакий tutorial/список best practices, то киньте ссылочку, пожалуйста.divan0 Автор
22.11.2018 00:07Центрировать что-то в веб UI это ужасно, да. Я лично очень жду пришествия Qt на wasm – вот уже есть даже Go-bindings: therecipe.github.io/widgets_playground
Но не могу согласиться, что костыльность HTML/CSS инвалидирует костыльность JS – мне кажется, они как раз очень гармонично смотрятся. Вот даже опыт с Vecty показывает, что с DOM/CSS на порядок проще работать, когда есть возможность легко и просто абстрагировать сложность, рефакторить и внятно описывать компоненты.
Про ресурсы по центрированию в CSS не подскажу, к сожалению, но на ответы подпишусь :Devocatus
22.11.2018 00:19А в чём заключается костыльность JS? Я не являюсь его фанатом, иногда приходится писать (как и почти всем в наше время), даже освоил Vue.js на базовом уровне и лично я не сталкивался с поведением Javascript, которое бы меня выбешивало. Может быть, потому что я давно пишу на Python, может быть, потому что полистал Дэвида Флэнагана (поэтому с NaN и == проблем не было, а те, которые были — решались простейшей отладкой через console.log), но я должен признаться, что в общем это был позитивный опыт — это мультипарадигменный язык, даёт простор для творчества и если ты хоть немного умеешь эту свободу применять, то это интересно.
А вёрстка… там иногда доходило до такого отчаяния, что хотелось всё бросить. HTML и CSS ведут себя совершенно непредсказуемо и нелогично для непосвящённого в их извращённые рецепты. И отладки никакой нет. Конечно, есть инспектор элементов, но он не слишком полезен.divan0 Автор
22.11.2018 02:46+1Ну, я не особо хочу озвучивать свою позицию про JS, чтобы не провоцировать холивары, но язык программирования это слишком важный и сложный инструмент, чтобы лепить его за пару недель и хаотически развивать. Есть такая известная цитата:
We become what we behold. We shape our tools and then our tools shape us
Инструменты, которые мы используем для решения задач, формируют то, как мы их решаем, наши дальнейшие решения, принципы и ценности, и, в итоге, всю экосистему. То, во что превратил наспех созданный язык для скриптиков, индустрию разработки софта – это сильнейший удар по борьбе с добавленной сложностью, с которой боролись пионеры computer science, такие как Дейкстра и Хоар, ещё в 70-е.
Поэтому да, моя главная претензия не столько к самому языку (что можно ждать от быстрой поделки), а к тому, что благодаря историческим недоразумениям это стало единственным языком доступным на внезапно ставшей основной платформе, и сформировало очень поломанную экосистему и целые поколения разработчиков, выращенных на поломанных принципах, сильно далеких от основного мейнстрима мира программирования. Эх, всё равно холиварно получилось.
RPG18
22.11.2018 00:22+1Я лично очень жду пришествия Qt на wasm
В 5.12 будет, если не перенесут, то в этом месяце. Проверил, вполне юзабельно.
ProVal
22.11.2018 09:17Попробуйте объяснить новичку как отцентрировать что-то; как сделать несколько элементов в одну строку с разным содержимым, но одинаковой высотой и пр. В итоге даже простые вещи на HTML+CSS делать на порядок сложнее, чем на нативных технологиях, а вещи сложные всё равно сможет сделать только человек, вбухавший в это дело кучу лет.
P.S. Если вы знаете ресурсы где такие вещи объясняются, эдакий tutorial/список best practices, то киньте ссылочку, пожалуйста.
Даже две ссылочки:
flexboxfroggy.com/#ru
cssgridgarden.com/#ruevocatus
22.11.2018 13:33Это действительно красиво и удобно, но…
caniuse.com/#feat=flexbox — 82% пользователей в России имеют поддержку flex
caniuse.com/#feat=css-grid — 76% пользователей в России имеют поддержку grid
Это слишком мало.
Есть ли для вёрстки что-то типа Babel, когда сам ты пишешь на гридах или каком-то псевдоязыке, а потом транспилируешь код в более совместимый со старыми браузерами?ProVal
22.11.2018 13:55+1То 82% погоду показывает. У флексов, особенно с префиксами, отличная поддержка, если отсечь IE (Edge можно не отсекать) и совсем древние Safari <7. Их можно использовать сейчас буквально для всего.
Гриды — да, надо с осторожностью пока. Тут зависит от конкретного сайта и его аудитории.
ArthurSupertramp
22.11.2018 10:18Вы либо застряли в нулевых и верстаете под ie6, либо просто не в курсе современных стандартов HTML и CSS. В частности, центрировать блоки можно в три строчки с помощью flex-layout. Колонки с одинаковой строкой – в две строчки с помощью того же flex'а.
Gizmich
22.11.2018 13:39Какое облегчение, всего-лишь 3 строчки что бы выставить элемент по центру, торжество технологий прям...
bill876
22.11.2018 23:41Am0ralist
23.11.2018 12:59Хм, а если у тебя родительский див не имеет заданной высоты, в него вложено два дива, в одном текст выводится в одну или две строчки, во втором — в одну. Надо чтоб второй див был прибит по высоте к низу первого?
ProVal
23.11.2018 16:56Нет ничего проще. jsfiddle.net/43huk69s/2
<div class="parent"> <div class="child">В этом блоке<br>Две строки текста</div> <div class="child">А в этом одна</div> </div> <style> .parent { border: 2px solid orange; padding: 5px; display: flex; /* Тут важна только эта одна строчка */ } .child { border: 2px solid lightgreen; margin: 5px; padding: 5px; } </style>
Если я правильно понял и речь шла об одинаковой высоте обоих блоков. А если нужно было не менять высоту второго блока, а просто сделать, чтобы он внизу располагался, то к родителю надо ещё добавитьalign-items: flex-end;
Uhehesh
21.11.2018 23:45+1А если поставить два зеркала, получится сделать клиент-сервер. :)
divan0 Автор
22.11.2018 00:09Хаха, но нет. QR коды не читаются в отражении (точнее, некоторый софт для декодера умеет распознавать отражённые QR-коды и исправлять перед считыванием, но вообще нет). Ну и протокол не готов к двустороннему общению. Но идея забавная.
Grox
22.11.2018 00:34Зачем зеркала, просто экранами друг к другу и использовать фронтальные камеры.
xenolog
23.11.2018 13:50Фронтальные камеры хороши только на топовых моделях.
А вот для, например, контроля передачи, синхронизации скорости и повторного запроса нераспознанных кадров — можно использовать камеру на сервере и вспышку на клиенте.
Inanity
22.11.2018 00:47+1Круто! Тоже когда-то хотел подобное сделать, но руки не дошли. Пару моментов:
1. Для связи телефон-телефон хорошо бы обратную связь прикрутить, тогда передача больших файлов станет более-менее реальной. Теоретически принимающий телефон может квитировать приём данных сигналя вспышкой передающему на фронтальную камеру. Вспышки и фронтальные камеры сейчас есть практически у всех.
2. Согласен, что QR код слишком избыточен. Надо свой формат метки придумать без корректирующих кодов. Контроля каждого фрейма с помощью какого-нибудь CRC будет вполне достаточно.Alexufo
22.11.2018 00:57+3ЕЩЕ ЧУТЬ чуть и так же сделаем передачу по аудио каналу и будем переизобретать протокол V.92 :-)
u010602
22.11.2018 04:47ИК-порт переизобрели вот, правда во много раз медленнее, и ресурсоемкость намного выше… Все чаще возникает ощущение «цикличности моды», это когда молодежь начинает одеваться как твоя бабушка в молодости, но с таким лицом, как будто они открыли Америку.
inkelyad
22.11.2018 08:41Так уже. «Audio MODEM» в маркете. И небольшая кучка видео на YouTube ищется.
Правда, не уверен, что из этого с обратной связью работает.
Аудио канал, кстати, выглядит несколько более осмысленным и полезным для мелких объемов данных. Для примера можно посмотреть как некие Chirp и Shuttl оплату проезда маршрутками в Индии сделали.
divan0 Автор
22.11.2018 02:01+1Вот вам чёрная магия от Timex, работает только с CRT мониторами: www.youtube.com/watch?v=2VmtPaiOBwI
Am0ralist
22.11.2018 12:421. Для связи телефон-телефон хорошо бы обратную связь прикрутить, тогда передача больших файлов станет более-менее реальной. Теоретически принимающий телефон может квитировать приём данных сигналя вспышкой передающему на фронтальную камеру. Вспышки и фронтальные камеры сейчас есть практически у всех.
Хм, вайфай и тотал коммандер с помощью QR позволяет переносить данные между компом с Windows (надо расширение найти на сайте) и телефонами на Windows Phone, Android и вроде даже Apple.
ntfs1984
22.11.2018 05:30Вай зачем такой сложный QR-код, если можно передавать мерцанием, где белый фон единица, а черный — ноль?
А можно и звуком разной частоты для единицы и нуля… ой кажется я переизобрел формат записи программ для спектрума. R tape loading error :))Darth_Malok
22.11.2018 07:26Вай зачем такой сложный QR-код, если можно передавать мерцанием, где белый фон единица, а черный — ноль?
При 60 fps скорость передачи будет 7,5 байт в секунду.
saag
22.11.2018 07:09Если протокол будет двусторонним, то можно и оплату чего-нибудь прикрутить. А если еще цветом кодировать?
shaggyone
22.11.2018 08:20Прикольная технология. Не соображу, где бы мог её применить, но как идея интересно.
p.s. почему то придумался способ «шпиёнский» канал передачи данных через систему двух смартфонов, двух зеркал и двух телескопов. Вряд ли реализуемый в реальности, но для кино про хакеров вполне.
ARad
22.11.2018 08:23Двигаем дальше — передаем анимированные изображения с помощью анимированных изображений, видео и звук с помощью QR видео и т.д.
LoadRunner
22.11.2018 09:19О, так можно же в QR-gif зашифровать QR-gif с шифрованным QR-gif!
ARad
22.11.2018 09:40Как нам повезло что берестяные грамоты этого не могли. А то написал кто то так одну шифрованную книгу — до сих пор пытаются прочесть.
LoadRunner
22.11.2018 11:13Это о какой книге речь?
m_podivilov
22.11.2018 11:34+1Полагаю, речь идёт о манускрипте Войнича.
1nd1go
22.11.2018 11:26Классно получилось. Не смотрели по какому принципу реализовано в apple watch pairing? там такой вращающийся qr-подобный шарик который надо телефоном сканировать. Вопрос именно в том, что он вращается и как это парсится
divan0 Автор
22.11.2018 11:36Там крутая технология, я пробежался по обоим патентам – если вкратце, то картинку они могут какую угодно рендерить (не обязательно этот шарик с частицами). Они анализируют усреднённое изменение в luminance (светимости), и передают данные именно через этот параметр, а вот chrominance (цветовая составляющая) там всё равно какая. Гениальность в том, что человеческий глаз гораздо более чувствителен к изменениям в цветовой составляющей, чем в светимости, и не видит тех изменений, но софт очень надёжно может из изменений светимости вытащить данные.
prs123
22.11.2018 11:39А почему необходимо использовать GIF, почему нельзя новые кадры на ходу генерировать?
divan0 Автор
22.11.2018 11:47В принципе можно, просто GIF удобней был тем, что можно гарантировать FPS – там задержка между кадрами прямо в файл записывается. При генерации каждого кадра, по идее, достаточная порция времени будет уходить на генерацию картинку, и высчитать, сколько реально осталось ждать до следующего кадра будет не сильно тривиально. Хотя стоит попробовать, конечно, всё равно.
prs123
22.11.2018 11:58Как раз таки узнать сколько времени ушло на генерацию и соответственно, сколько осталось, довольно не сложно ( как мне кажется). А почему нельзя использовать два потока — один для генерации и второй — рендера?
Если я правильно понимаю, у принимающей стороны кадры должны быть синхронизованы с передающей?divan0 Автор
22.11.2018 12:33Узнать может и не сложно, но что делать, если генерация заняла больше времени, чем задержка между кадрами? (а там почти всегда так). А горутины всё равно в скомпилированному JS будут в одном потоке работать.
принимающей стороны кадры должны быть синхронизованы с передающей?
Как раз нет — рассинхронизация частот будет влиять только на общее время передачи, но всё равно будет работать. Я там в описании идеи протокола немного объяснял этот момент — фреймы можно вообще в любом порядке принимать.
prs123
22.11.2018 12:35Ну если по потокам не развести никак — то грустно конечно.
Хотя если принимающая сторона не завязана на длину кадра, то в чем великая проблема, если новый кадр отрисуется чуть-чуть позже?
Boomburum
22.11.2018 12:25+1Вспомнился крутейший пост от tgx, "Читаем QR-код" :)
divan0 Автор
22.11.2018 12:36+1Здорово, спасибо за ссылку.
Вот ещё очень подробный разбор, только обратного процесса и с интерактивом — https://www.nayuki.io/page/creating-a-qr-code-step-by-step
Desavian
22.11.2018 12:33+1Хм… 1кб в секунду это очень много для недетектируемой передачи данных. Способов использования в голове сразу много появилось…
daserge
22.11.2018 12:33Столкнулся недавно с похожей проблемой. Ограничение на размер данных обошел просто закодировав ссылку на gist с base64 data.
divan0 Автор
22.11.2018 12:34Ага, есть вот даже такой проект: https://github.com/claudiodangelis/qr-filetransfer
Darth_Malok
22.11.2018 14:27Круто, хотя и сложно найти реальное применение.
Не думали о реализации асинхронной двухсторонней связи для подтверждения приёма очередного пакета? Вспышкой там моргать или через звук данные передавать.
То есть, передатчик показывает кадр с qr-кодом, убеждается, что приёмник его прочитал, а потом показывает следующий. С помощью обратной связи можно даже регулировать количество данных в кадре, ведь если камера хорошая, а процессор быстрый, можно и по 1276 байт данные передавать. К тому же приёмник не будет вынужден ждать весь цикл, когда один кадр пропущен.divan0 Автор
22.11.2018 18:49Ну, настолько я усложнять не хочу — почти любое решение тут сильно ограничивает круг применения. Я вот только сегодня понял, что такую анимированную гифку даже Apple-овские Shortcuts скриптинг может сделать – можно сказать «Сири, сгенерируй-ка мне QR из последнего скриншота», и скрипт возьмёт скриншот, разобъет на куски (вот тут еще не уверен, умеет ли такое), сгенерирует QR на каждый и склеит в GIF-ку (такое точно умеет). В общем, less is more.
Да и дорого слишком реализовывать протокол с подтверждениями – Шэннон не одобряет :) Я буду чуть позже фонтанные коды реализовывать, чтобы проблему с ожиданием цикла решить.
Stalker_RED
22.11.2018 16:48Если добавить цвет, можно увеличить пропускную способность во много раз. Но чем больше цветов, тем сильнее придется переживать еще и о цветопередаче.
domix32
22.11.2018 20:31Java, Python или JavaScript, что, к сожалению, делало код практически непортируемым
это какая-то шутка?
divan0 Автор
22.11.2018 23:38Нет, не шутка.
Я действительно не знаю способов использовать код на Java, Python или JS на ноутбуке, в вебе и в iOS и Android проекте. Возможно, нужно было добавить, что "непортируемым, в сравнении с Go", потому что в Go это всё однострочные команды и меньше секунды ожидания:
Windows PE
GOOS=windows go build
Linux ELF
GOOS=linux go build
MacOS X Mach64
GOOS=darwin go build
iOS Framework
gomobile bind -target=ios .
Android AAR Native Code
gomobile bind -target=android .
Web (JS)
gopherjs build
И это всё фактически из коробки (
gomobile
иgopherjs
ставятся отдельно также однострочниками).domix32
24.11.2018 01:42Java была одной из самых первых кто был портируем под различные платформы. Это один из самых портируемых языков.
Python собирается и запускается практически на любой *nix, там же виндовс и даже на андроиде работает (см. Kivy)
JS с приходом NodeJS работает едва ли не на любом чайнике.
Так что с портированием у них все хорошо. А вот удобство сборки кода под платформы — это уже другой вопрос и к кроссплатформенности отношения не имеет.divan0 Автор
24.11.2018 13:52Я не употреблял слово «кроссплатформенность».
Была конкретная задача использовать код на трёх платформах и ни один из вышеприведённых языков не позволял сделать это достаточно просто.domix32
24.11.2018 14:49Тогда что по вашему портируемость как не перенос кода с одной платформы на другую?
IvanT
22.11.2018 20:42Гениально! Как увидел сразу первая мысль "Это же белый шум из телевизора!" он же вроде как реликтовое излучение. Может это и есть разгадка тайны молчания космоса? Другие цивилизации просто передают данные посредством QR кодов высокой плотности! :)
AZaz1
23.11.2018 01:28Прочитал статью и осталось негативное впечатление — чувствуется писатель юн и горяч и допускает ошибки молодости проявляя категоричность:
— если вы не знаете инструмент — не стоит негативно о нем отзываться
— сложность в фреймворках величина постоянная и никуда не девается, а лишь перемещается из одного места в другое — порой фреймворк на котором легко написать Hello world не способен обеспечить создание enterprise приложенияdivan0 Автор
23.11.2018 13:14С горячим соглашусь )
Инструмент инструменту рознь. Мнение о том, что все инструменты одинаково хорошо, «просто нужно их получше узнать» мне чуждо, и, честно говоря, считаю его крайне вредным. Все программисты должны понимать, что выбор инструментов очень сильно будет влиять на их работу, продуктивность и даже дальнейшую судьбу. Но умение отличать плохие инструменты от хороших приходит с опытом, конечно.
Про постоянную величину сложности фреймворков – это вот сильно мимо. Интересно было бы послушать, как вы пришли к такому выводу.
pawlo16
23.11.2018 07:33Спасибо, круто всё описано, как всегда у Вас.
Некоторые вопросы вызывает vecty. Давно смотрю на неё, но пока опасаюсь использовать в боевом коде. Там написано, что проект всё ещё в стадии эксперимента и есть важные не решённые проблемы. С другой стороны его контрибьютит в том числе мэйнтейнер gopherjs, что означает, что проект вряд ли будет депрекейтнут. Как Вы полагаете, будет ли vecty + gopherjs + go полноценной заменой react + webpack + typescript. Естественно речь о проектах без лигаси и большого количества внешних js библиотек
igordata
23.11.2018 23:00Очень прикольная идея, клёвая.
Погляди на en.wikipedia.org/wiki/High_Capacity_Color_Barcode там можно на элемент до 3 бит передавать. Может выйти приемлемая скорость для каких-то применений.
gudvinr
А вы пробовали тестировать при дрожании камеры, изменениях угла, поворотах и прочих нормальных условиях?
Вряд ли ведь у двух людей будут штативы, чтобы закрепить на них телефоны, например.
P.S. За Vecty спасибо, звучит интересно, но верстка выглядит чересчур многословной. Оно, кстати, компилируется в стандартный таргет wasm из go 1.11?
divan0 Автор
Ну, такой задачи не стояло, и я, конечно, тестировал для идеальных условий, чтобы оценить вообще порядок. Но сам детект QR кода реализован непосредственно в OS (AVFoundation умеет автоматически много чего распознавать – я так полагаю, с помощью нейросетки), и при ручных тестах у меня сложилось впечатление, что легкое дрожание и изменение ракурса вообще не влияет – главное, чтобы код оставался в кадре. Вполне допускаю, что на более старых телефонах это не так хорошо – но iPhone XS всё таки по мощности сравним с Macbook Pro, так что неудивительно.
Насчёт wasm — сам сильно жду, и это в ближайших планах, уже висит PR открытый. Я так понимаю, это будет политическое решение – уход от gopherjs к wasm, поскольку поддержка обоих вариантов там не сильно получается – нужно API менять немного.
Nikobraz
QR достаточно надежно сканируются, как живущий на данный момент в Китае говорю, они тут везде. И туда до 30% избыточных данных запихать можно, для уверенности сканирования.