Передача данных через анимированные QR на Gomobile и GopherJS

    В данной статье я хочу рассказать о небольшом и забавном проекте выходного дня по передаче файлов через анимированные QR коды. Проект написан на Go, с использованием Gomobile и Gopherjs – последний для веб-приложения для автоматического замера скорости передачи данных. Если вам интересна идея передачи данных через визуальные коды, разработка веб-приложений не на JS или настоящая кроссплатформенность Go — велкам под кат.


    txqr demo


    Идея проекта родилась из конкретной задачи для мобильного приложения – как наиболее просто и быстро передать небольшую порцию данных (~15КБ) в другое устройство, в условиях блокировок сети. Первой мыслью было использовать Bluetooth, но это не так удобно, как кажется – относительно долгий и не всегда работающий процесс обнаружения и спаривания устройств слишком затрудняет задачу. Неплохая идея была бы использовать NFC (Near Field Communication), но до сих пор слишком много устройств, в которых поддержка NFC ограничена или отсутствует вообще. Нужно было что-то проще и доступнее.


    Как насчёт QR кодов?


    QR коды


    QR (Quick Response) код – это самый популярный в мире вид визуальных кодов. Он позволяет кодировать до 3КБ произвольных данных и имеет различные уровни коррекции ошибок, позволяя уверенно читать даже на треть закрытый или загрязнённый код.


    Но с QR кодами две проблемы:


    • 3КБ недостаточно
    • чем больше данных закодировано, тем выше требования к качеству картинки для сканирования

    Вот так выглядит QR код 40-й версии (самая высокая плотность записи) с 1276 байтами:


    qrv40


    Для моей задачи нужно было научиться передавать ~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 отлично выполнял свою функцию.


    Более того, размер фреймов и частоту можно даже менять динамически, подстраиваясь под возможности получателя.


    protocol


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


    Самым интересным моментом было написать мобильное приложение, которое может использовать этот протокол.


    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


    Я в жизни такого удовольствия не получал от разработки фронтенд интерфейсов.


    Чуть позже я планирую написать ещё пару статей про свой опыт разработки фронтендов на 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! Я тоже так сначала подумал, но, как только начал работать, осознал, насколько это удобно:


    1. Нет магии – каждый блок (Markup или HTML) это лишь переменная нужного типа, с чёткими лимитами куда что можно поставить, благодаря статической типизации.
    2. Нет открывающих/закрывающих тэгов, которые нужно либо не забывать менять при рефакторинге, либо использовать IDE, которая делает это за вас.
    3. Структура вдруг становится понятной – я никогда, например, не понимал, почему в 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

    и запускать тест автоматически.


    app


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


    Загвоздка была в том, что веб-приложение, будучи запущенным в песочнице браузера, не может создавать новые соединения, и, если я не ошибаюсь, единственная возможность настоящего peer-to-peer соединения с браузером есть только через WebRTC (NAT мне пробивать не нужно), но это было чересчур громоздко. Веб-приложение могло быть только клиентом.


    Решение было простое – веб-сервис на Go, который отдавал веб-приложение (и запускал браузер на нужный URL), так же запускал WebSocket-прокси для двух клиентов. Как только к нему присоединяются два клиента – он прозрачно отправляет сообщения из одного соединения в другое, позволяя клиентам (веб-приложению и мобильному клиенту) общаться напрямую. Они, должны быть для этого, в одной WIFI-сети, конечно же.


    Оставалась проблема того, как сказать мобильному устройству, куда, собственно, подключаться, и она была решена с помощью… QR кода!


    Процесс тестирования выглядит так:


    • мобильное приложение ищёт QR код со стартовым маркером и ссылке на WebSocket-прокси
    • как только маркер считан, приложение подключается к данному WebSocket-прокси
    • веб-приложение (будучи уже подключенным к прокси) понимает, что мобильное приложение готово и показывает QR код с маркером "готов к следующему раунду?"
    • мобильное приложение распознает сигнал, обнуляет декодер, и отправляет через WebSocket сообщение "угу".
    • веб-приложение, получив подтверждение, генерирует новую QR анимацию и крутит её, пока не получит результаты или таймаут.
    • результаты складываются в табличку рядом, которую можно тут же скачать в виде CSV

    design


    В итоге, всё что мне оставалось – просто поставить телефон на штатив, запустить приложение и дальше две программы сами делали всю грязную работу, вежливо общаясь через QR-коды и WebSocket :)


    tester demo


    В конце я скачивал 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-визуализации важнее тысячи картинок. Вот такая визуализация полученных результатов (кликабельно):


    qr_scan_results


    Наилучший полученный результат был 1.4 секунды, что примерно равно 9КБ/с! Этот результат был записан на частоте 11 кадров в секунду, размере фрейма 850 байт и среднем (medium) уровне коррекции ошибок. В большинстве случаев, правда, на такой скорости декодер камеры пропускал некоторые кадры, и приходилось ждать следующего повтора пропущенного фрейма, что сильно негативно сказывалось на результатах – вместо двух секунд легко могло получиться 15, или таймаут, который был выставлен в 30 секунд.


    Вот графики зависимости результатов от меняемых переменных:


    Время / размер фрейма


    Time vs Size


    Как видно, при низких значениях количества байт в каждом фрейме, избыток кодирования слишком велик и общее время считывания, соответственно, тоже. Некий локальный минимум есть в 500-600 байт на фрейм, но значения рядом всё равно приводят к потерянным кадрам. Наилучший результат наблюдался на 900 байт, но 1000 и выше это почти гарантированная потеря кадров.


    Время / FPS


    Time vs FPS


    Значение количества кадров в секунду, к моему удивлению, сильно большого эффекта не имело – маленькие значения слишком увеличивали время общей передачи, а большие повышали вероятность пропущенного кадра. Оптимальное значение, судя по этим тестам, находится в районе 6-7 кадров в секунду для тех устройств, на которых я тестировал.


    Время / Уровень коррекции ошибок


    Time vs Lvl


    Уровень коррекции ошибок показал чёткую взаимосвязь между временем передачи файла и уровнем избыточности, что неудивительно. Однозначный победитель тут это низкий (L) уровень коррекции – чем меньше избыточных данных, тем более читабелен для сканера QR код при том же размере данных. По факту, для этого эксперимента избыточность не нужна вообще, но стандарт такого варианта не предлагает.


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


    Выводы


    Этот забавный проект доказал, что односторонняя передача данных через анимированные коды, безусловно, возможна, и для ситуаций, где нужно передать небольшой объем при отсутствии любых видов сетей, вполне подходит. Хотя мой максимальный результат был около 9КБ/с, в большинстве случаев реальная скорость составляла 1-2КБ/с.


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


    Напоследок, я по прежнему восхищаюсь, насколько продуктивным можно быть с Go, когда чётко знаешь, что хочешь реализовать – экстремально короткий цикл "изменить"-"собрать"-"проверить" позволяет экспериментировать много и часто, простой код и отсутствие классовой иерархии в структуре программ даёт возможность легко и безболезненно их рефакторить на ходу, а фантастическая кроссплатформенность, заложенная в язык с самого начала позволяет написать код один раз и использовать его на сервере, на веб-клиенте и в нативном мобильном приложении. При этом, несмотря на более, чем достаточную производительность из коробки, по прежнему есть масса пространства для оптимизации и ускорения.


    Так что если вы никогда не пробовали Gomobile или GopherJS – я рекомендую вам попробовать при следующей возможности. Это заберёт час вашего времени, но, возможно, откроет вам целый новый пласт возможностей в веб или мобильной разработке. Смело пробуйте!


    Ссылки


    Средняя зарплата в IT

    113 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 5 253 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 92

      +2

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


      P.S. За Vecty спасибо, звучит интересно, но верстка выглядит чересчур многословной. Оно, кстати, компилируется в стандартный таргет wasm из go 1.11?

        0
        Ну, такой задачи не стояло, и я, конечно, тестировал для идеальных условий, чтобы оценить вообще порядок. Но сам детект QR кода реализован непосредственно в OS (AVFoundation умеет автоматически много чего распознавать – я так полагаю, с помощью нейросетки), и при ручных тестах у меня сложилось впечатление, что легкое дрожание и изменение ракурса вообще не влияет – главное, чтобы код оставался в кадре. Вполне допускаю, что на более старых телефонах это не так хорошо – но iPhone XS всё таки по мощности сравним с Macbook Pro, так что неудивительно.

        Насчёт wasm — сам сильно жду, и это в ближайших планах, уже висит PR открытый. Я так понимаю, это будет политическое решение – уход от gopherjs к wasm, поскольку поддержка обоих вариантов там не сильно получается – нужно API менять немного.
          +1
          QR достаточно надежно сканируются, как живущий на данный момент в Китае говорю, они тут везде. И туда до 30% избыточных данных запихать можно, для уверенности сканирования.
          0
          А если снимать в замедленном режиме iPhone и увеличить FPS. Может быть получится добиться лучших результатов?
            0
            Ммм, интересный вопрос. Замедленный режим позволяет снимать с высоким FPS, но скорость с которой AVFoundation может распознавать коды на кадрах, по идее, константа и будет определять реальную скорость. При этом, полагаю, slow motion режим как-то особенно реализован и в нём детект лиц и кодов выключен, поэтому придётся записывать, а потом проигрывать на оптимальной скорости – что уже удлиняет и усложняет весь процесс. Но идея интересная.
            –2
            Проблема с UI на веб-технологиях в том, что HTML и CSS ещё хуже, чем JS. Javascript — обычный динамический язык программирования, не без недостатков, но и со своей красотой. А вот вёрстка… там такое количество исторических артефактов, несовместимостей, всяких вендорных префиксов, логических несоответствий, что JS по сравнению с этим просто образец стройности.
            Попробуйте объяснить новичку как отцентрировать что-то; как сделать несколько элементов в одну строку с разным содержимым, но одинаковой высотой и пр. В итоге даже простые вещи на HTML+CSS делать на порядок сложнее, чем на нативных технологиях, а вещи сложные всё равно сможет сделать только человек, вбухавший в это дело кучу лет.
            P.S. Если вы знаете ресурсы где такие вещи объясняются, эдакий tutorial/список best practices, то киньте ссылочку, пожалуйста.
              0
              Центрировать что-то в веб UI это ужасно, да. Я лично очень жду пришествия Qt на wasm – вот уже есть даже Go-bindings: therecipe.github.io/widgets_playground

              Но не могу согласиться, что костыльность HTML/CSS инвалидирует костыльность JS – мне кажется, они как раз очень гармонично смотрятся. Вот даже опыт с Vecty показывает, что с DOM/CSS на порядок проще работать, когда есть возможность легко и просто абстрагировать сложность, рефакторить и внятно описывать компоненты.

              Про ресурсы по центрированию в CSS не подскажу, к сожалению, но на ответы подпишусь :D
                +1
                А в чём заключается костыльность JS? Я не являюсь его фанатом, иногда приходится писать (как и почти всем в наше время), даже освоил Vue.js на базовом уровне и лично я не сталкивался с поведением Javascript, которое бы меня выбешивало. Может быть, потому что я давно пишу на Python, может быть, потому что полистал Дэвида Флэнагана (поэтому с NaN и == проблем не было, а те, которые были — решались простейшей отладкой через console.log), но я должен признаться, что в общем это был позитивный опыт — это мультипарадигменный язык, даёт простор для творчества и если ты хоть немного умеешь эту свободу применять, то это интересно.

                А вёрстка… там иногда доходило до такого отчаяния, что хотелось всё бросить. HTML и CSS ведут себя совершенно непредсказуемо и нелогично для непосвящённого в их извращённые рецепты. И отладки никакой нет. Конечно, есть инспектор элементов, но он не слишком полезен.
                  0
                  Ну, я не особо хочу озвучивать свою позицию про JS, чтобы не провоцировать холивары, но язык программирования это слишком важный и сложный инструмент, чтобы лепить его за пару недель и хаотически развивать. Есть такая известная цитата:
                  We become what we behold. We shape our tools and then our tools shape us

                  Инструменты, которые мы используем для решения задач, формируют то, как мы их решаем, наши дальнейшие решения, принципы и ценности, и, в итоге, всю экосистему. То, во что превратил наспех созданный язык для скриптиков, индустрию разработки софта – это сильнейший удар по борьбе с добавленной сложностью, с которой боролись пионеры computer science, такие как Дейкстра и Хоар, ещё в 70-е.

                  Поэтому да, моя главная претензия не столько к самому языку (что можно ждать от быстрой поделки), а к тому, что благодаря историческим недоразумениям это стало единственным языком доступным на внезапно ставшей основной платформе, и сформировало очень поломанную экосистему и целые поколения разработчиков, выращенных на поломанных принципах, сильно далеких от основного мейнстрима мира программирования. Эх, всё равно холиварно получилось.
                  +1
                  Я лично очень жду пришествия Qt на wasm

                  В 5.12 будет, если не перенесут, то в этом месяце. Проверил, вполне юзабельно.

                  +2
                  Попробуйте объяснить новичку как отцентрировать что-то; как сделать несколько элементов в одну строку с разным содержимым, но одинаковой высотой и пр. В итоге даже простые вещи на HTML+CSS делать на порядок сложнее, чем на нативных технологиях, а вещи сложные всё равно сможет сделать только человек, вбухавший в это дело кучу лет.
                  P.S. Если вы знаете ресурсы где такие вещи объясняются, эдакий tutorial/список best practices, то киньте ссылочку, пожалуйста.

                  Даже две ссылочки:

                  flexboxfroggy.com/#ru
                  cssgridgarden.com/#ru
                    0
                    Это действительно красиво и удобно, но…
                    caniuse.com/#feat=flexbox — 82% пользователей в России имеют поддержку flex
                    caniuse.com/#feat=css-grid — 76% пользователей в России имеют поддержку grid
                    Это слишком мало.
                    Есть ли для вёрстки что-то типа Babel, когда сам ты пишешь на гридах или каком-то псевдоязыке, а потом транспилируешь код в более совместимый со старыми браузерами?
                      +1
                      То 82% погоду показывает. У флексов, особенно с префиксами, отличная поддержка, если отсечь IE (Edge можно не отсекать) и совсем древние Safari <7. Их можно использовать сейчас буквально для всего.
                      Гриды — да, надо с осторожностью пока. Тут зависит от конкретного сайта и его аудитории.
                    +4
                    Вы либо застряли в нулевых и верстаете под ie6, либо просто не в курсе современных стандартов HTML и CSS. В частности, центрировать блоки можно в три строчки с помощью flex-layout. Колонки с одинаковой строкой – в две строчки с помощью того же flex'а.
                      0
                      Какое облегчение, всего-лишь 3 строчки что бы выставить элемент по центру, торжество технологий прям...
                      0
                      Попробуйте объяснить новичку как отцентрировать что-то


                      css-tricks.com/centering-css-complete-guide
                        0
                        Хм, а если у тебя родительский див не имеет заданной высоты, в него вложено два дива, в одном текст выводится в одну или две строчки, во втором — в одну. Надо чтоб второй див был прибит по высоте к низу первого?
                          0
                          Нет ничего проще. 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;
                            0
                            Спасибо, это как-бы работает, но есть нюансы…
                            У нас тут своя атмосфера:
                            IE в режиме совместимости это игнорит, что лечится. А вот через конвертер html-pdf (wkhtmltopdf, даже последний) — не работает…
                      +1

                      А если поставить два зеркала, получится сделать клиент-сервер. :)

                        0
                        Хаха, но нет. QR коды не читаются в отражении (точнее, некоторый софт для декодера умеет распознавать отражённые QR-коды и исправлять перед считыванием, но вообще нет). Ну и протокол не готов к двустороннему общению. Но идея забавная.
                          +6
                          Зачем зеркала, просто экранами друг к другу и использовать фронтальные камеры.
                            0
                            Фронтальные камеры хороши только на топовых моделях.
                            А вот для, например, контроля передачи, синхронизации скорости и повторного запроса нераспознанных кадров — можно использовать камеру на сервере и вспышку на клиенте.
                          +2
                          Круто! Тоже когда-то хотел подобное сделать, но руки не дошли. Пару моментов:

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

                          2. Согласен, что QR код слишком избыточен. Надо свой формат метки придумать без корректирующих кодов. Контроля каждого фрейма с помощью какого-нибудь CRC будет вполне достаточно.
                            +8
                            ЕЩЕ ЧУТЬ чуть и так же сделаем передачу по аудио каналу и будем переизобретать протокол V.92 :-)
                              0
                              ИК-порт переизобрели вот, правда во много раз медленнее, и ресурсоемкость намного выше… Все чаще возникает ощущение «цикличности моды», это когда молодежь начинает одеваться как твоя бабушка в молодости, но с таким лицом, как будто они открыли Америку.
                                0
                                Так уже. «Audio MODEM» в маркете. И небольшая кучка видео на YouTube ищется.
                                Правда, не уверен, что из этого с обратной связью работает.

                                Аудио канал, кстати, выглядит несколько более осмысленным и полезным для мелких объемов данных. Для примера можно посмотреть как некие Chirp и Shuttl оплату проезда маршрутками в Индии сделали.
                                +4
                                Вот вам чёрная магия от Timex, работает только с CRT мониторами: www.youtube.com/watch?v=2VmtPaiOBwI
                                  0
                                  1. Для связи телефон-телефон хорошо бы обратную связь прикрутить, тогда передача больших файлов станет более-менее реальной. Теоретически принимающий телефон может квитировать приём данных сигналя вспышкой передающему на фронтальную камеру. Вспышки и фронтальные камеры сейчас есть практически у всех.
                                  Хм, вайфай и тотал коммандер с помощью QR позволяет переносить данные между компом с Windows (надо расширение найти на сайте) и телефонами на Windows Phone, Android и вроде даже Apple.
                                  +2

                                  Это, чёрт возьми, восхитительно! Просто слов нет, я серьёзно.

                                    0

                                    Спасибо )

                                    –3
                                    Вай зачем такой сложный QR-код, если можно передавать мерцанием, где белый фон единица, а черный — ноль?
                                    А можно и звуком разной частоты для единицы и нуля… ой кажется я переизобрел формат записи программ для спектрума. R tape loading error :))
                                      +8
                                      Вай зачем такой сложный QR-код, если можно передавать мерцанием, где белый фон единица, а черный — ноль?
                                      При 60 fps скорость передачи будет 7,5 байт в секунду.
                                        0
                                        и это без определения ошибок и уж тем более коррекции
                                      +1
                                      Если протокол будет двусторонним, то можно и оплату чего-нибудь прикрутить. А если еще цветом кодировать?
                                        +2
                                        Прикольная технология. Не соображу, где бы мог её применить, но как идея интересно.

                                        p.s. почему то придумался способ «шпиёнский» канал передачи данных через систему двух смартфонов, двух зеркал и двух телескопов. Вряд ли реализуемый в реальности, но для кино про хакеров вполне.
                                          0
                                          Двигаем дальше — передаем анимированные изображения с помощью анимированных изображений, видео и звук с помощью QR видео и т.д.
                                            +2
                                            О, так можно же в QR-gif зашифровать QR-gif с шифрованным QR-gif!
                                              0
                                              Как нам повезло что берестяные грамоты этого не могли. А то написал кто то так одну шифрованную книгу — до сих пор пытаются прочесть.
                                                0
                                                Это о какой книге речь?
                                                  +1
                                                  Полагаю, речь идёт о манускрипте Войнича.
                                                    0

                                                    Так он же просто на ацтекском написан.

                                                  0
                                                  Рукопись Войнича
                                              • НЛО прилетело и опубликовало эту надпись здесь
                                              0
                                              Классно получилось. Не смотрели по какому принципу реализовано в apple watch pairing? там такой вращающийся qr-подобный шарик который надо телефоном сканировать. Вопрос именно в том, что он вращается и как это парсится
                                                0

                                                Там крутая технология, я пробежался по обоим патентам – если вкратце, то картинку они могут какую угодно рендерить (не обязательно этот шарик с частицами). Они анализируют усреднённое изменение в luminance (светимости), и передают данные именно через этот параметр, а вот chrominance (цветовая составляющая) там всё равно какая. Гениальность в том, что человеческий глаз гораздо более чувствителен к изменениям в цветовой составляющей, чем в светимости, и не видит тех изменений, но софт очень надёжно может из изменений светимости вытащить данные.

                                                  0
                                                  офигеть. а скорость такой передачи известна?
                                                    0
                                                    Я не нашёл, но полагаю что маленькая – не под скорость, всё же, оптимизировали )
                                                0
                                                Классная реализация. Можно использовать как аналог NFC для обмена контактами, либо спаривания телефонов через bluetooth, wi-fi для передачи большого объёма информации.
                                                  0
                                                  Уже есть с обычными QR кодами…
                                                  0

                                                  А почему необходимо использовать GIF, почему нельзя новые кадры на ходу генерировать?

                                                    0

                                                    В принципе можно, просто GIF удобней был тем, что можно гарантировать FPS – там задержка между кадрами прямо в файл записывается. При генерации каждого кадра, по идее, достаточная порция времени будет уходить на генерацию картинку, и высчитать, сколько реально осталось ждать до следующего кадра будет не сильно тривиально. Хотя стоит попробовать, конечно, всё равно.

                                                      0

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

                                                        0

                                                        Узнать может и не сложно, но что делать, если генерация заняла больше времени, чем задержка между кадрами? (а там почти всегда так). А горутины всё равно в скомпилированному JS будут в одном потоке работать.


                                                        принимающей стороны кадры должны быть синхронизованы с передающей?

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

                                                          0
                                                          Ну если по потокам не развести никак — то грустно конечно.
                                                          Хотя если принимающая сторона не завязана на длину кадра, то в чем великая проблема, если новый кадр отрисуется чуть-чуть позже?
                                                            0
                                                            Так вроде есть асинхронный js. Получается GopherJS просто не умеет асинхронный го в асинхронный js?
                                                              +1
                                                              Асинхронный JS тоже только одно ядро умеет )
                                                      +1
                                                      Вспомнился крутейший пост от tgx, "Читаем QR-код" :)
                                                      +1
                                                      Хм… 1кб в секунду это очень много для недетектируемой передачи данных. Способов использования в голове сразу много появилось…
                                                        0
                                                        Столкнулся недавно с похожей проблемой. Ограничение на размер данных обошел просто закодировав ссылку на gist с base64 data.
                                                        +1
                                                        Круто, хотя и сложно найти реальное применение.

                                                        Не думали о реализации асинхронной двухсторонней связи для подтверждения приёма очередного пакета? Вспышкой там моргать или через звук данные передавать.

                                                        То есть, передатчик показывает кадр с qr-кодом, убеждается, что приёмник его прочитал, а потом показывает следующий. С помощью обратной связи можно даже регулировать количество данных в кадре, ведь если камера хорошая, а процессор быстрый, можно и по 1276 байт данные передавать. К тому же приёмник не будет вынужден ждать весь цикл, когда один кадр пропущен.
                                                          +1
                                                          Ну, настолько я усложнять не хочу — почти любое решение тут сильно ограничивает круг применения. Я вот только сегодня понял, что такую анимированную гифку даже Apple-овские Shortcuts скриптинг может сделать – можно сказать «Сири, сгенерируй-ка мне QR из последнего скриншота», и скрипт возьмёт скриншот, разобъет на куски (вот тут еще не уверен, умеет ли такое), сгенерирует QR на каждый и склеит в GIF-ку (такое точно умеет). В общем, less is more.

                                                          Да и дорого слишком реализовывать протокол с подтверждениями – Шэннон не одобряет :) Я буду чуть позже фонтанные коды реализовывать, чтобы проблему с ожиданием цикла решить.
                                                          0
                                                          Если добавить цвет, можно увеличить пропускную способность во много раз. Но чем больше цветов, тем сильнее придется переживать еще и о цветопередаче.
                                                            0
                                                            Эпилептики одобряют)
                                                              +2
                                                              Придумал вот такой способ image
                                                              можно сложить много qr кодов разных цветов)…

                                                                0
                                                                Супер идея, кстати :)
                                                                Похожий алгоритм
                                                                  0
                                                                    0

                                                                    Цветопередача у разных мониторов ± одинаковая, конечно, но качество работы камер при разных освещениях и разная цветовая температура ламп могут здорово попортить практичность.

                                                                    +1
                                                                    Интересный пост, спасибо!
                                                                      +2
                                                                      Java, Python или JavaScript, что, к сожалению, делало код практически непортируемым

                                                                      это какая-то шутка?

                                                                        0

                                                                        Нет, не шутка.


                                                                        Я действительно не знаю способов использовать код на 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 ставятся отдельно также однострочниками).

                                                                          0
                                                                          т-с-с-с, разговор не об этом… =) не начинайте
                                                                            0
                                                                            Java была одной из самых первых кто был портируем под различные платформы. Это один из самых портируемых языков.
                                                                            Python собирается и запускается практически на любой *nix, там же виндовс и даже на андроиде работает (см. Kivy)
                                                                            JS с приходом NodeJS работает едва ли не на любом чайнике.
                                                                            Так что с портированием у них все хорошо. А вот удобство сборки кода под платформы — это уже другой вопрос и к кроссплатформенности отношения не имеет.
                                                                              0
                                                                              Я не употреблял слово «кроссплатформенность».
                                                                              Была конкретная задача использовать код на трёх платформах и ни один из вышеприведённых языков не позволял сделать это достаточно просто.
                                                                                0
                                                                                Тогда что по вашему портируемость как не перенос кода с одной платформы на другую?
                                                                          0

                                                                          Гениально! Как увидел сразу первая мысль "Это же белый шум из телевизора!" он же вроде как реликтовое излучение. Может это и есть разгадка тайны молчания космоса? Другие цивилизации просто передают данные посредством QR кодов высокой плотности! :)

                                                                            0
                                                                            Можно попробовать и через вибрацию — ловить акселерометрами.
                                                                              0
                                                                              Tx — это не трансфер, а передача (transmission). Трансфер — это XFR.
                                                                                0
                                                                                Точно, спасибо!
                                                                                +2
                                                                                Прочитал статью и осталось негативное впечатление — чувствуется писатель юн и горяч и допускает ошибки молодости проявляя категоричность:
                                                                                — если вы не знаете инструмент — не стоит негативно о нем отзываться
                                                                                — сложность в фреймворках величина постоянная и никуда не девается, а лишь перемещается из одного места в другое — порой фреймворк на котором легко написать Hello world не способен обеспечить создание enterprise приложения
                                                                                  0
                                                                                  С горячим соглашусь )

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

                                                                                  Про постоянную величину сложности фреймворков – это вот сильно мимо. Интересно было бы послушать, как вы пришли к такому выводу.
                                                                                  +1
                                                                                  Спасибо, круто всё описано, как всегда у Вас.
                                                                                  Некоторые вопросы вызывает vecty. Давно смотрю на неё, но пока опасаюсь использовать в боевом коде. Там написано, что проект всё ещё в стадии эксперимента и есть важные не решённые проблемы. С другой стороны его контрибьютит в том числе мэйнтейнер gopherjs, что означает, что проект вряд ли будет депрекейтнут. Как Вы полагаете, будет ли vecty + gopherjs + go полноценной заменой react + webpack + typescript. Естественно речь о проектах без лигаси и большого количества внешних js библиотек
                                                                                    0
                                                                                    Спасибо. Да, безусловно, это пока эксперимент, и для более-менее серьезных продакшн проектов ещё сыроват. Там есть свои проблемы (связанные, опять таки, с фактом того, что в итоге всё равно JS на выходе) – например при частом обновлении виджета из горутины (прогрессбар быстрый, допустим), может понадобится вручную вызывать `runtime.Gosched()` иначе виджет не будет обновляться.
                                                                                    Так что на данном моменте как полноценную замену существующих стеков для фронтенда не могу рекомендовать.
                                                                                    0
                                                                                    Очень прикольная идея, клёвая.
                                                                                    Погляди на en.wikipedia.org/wiki/High_Capacity_Color_Barcode там можно на элемент до 3 бит передавать. Может выйти приемлемая скорость для каких-то применений.
                                                                                      0
                                                                                      Спасибо. Да, чуть выше обсуждали уже альтернативные коды. У QR кодов всё-таки главный плюс это встроенная поддержка в мобильных SDK и вообще везде. Я могу такой анимированный QR сгенерировать даже через Apple Shortcut скриптинг :) Less is more.
                                                                                      0
                                                                                      А нельзя использовать вспышку принимающего аппарата и фронтальную камеру передающего для синхронизации?
                                                                                        0
                                                                                        Первой мыслью было использовать Bluetooth, но это не так удобно, как кажется – относительно долгий и не всегда работающий процесс обнаружения и спаривания устройств слишком затрудняет задачу.

                                                                                        Спасибо за статья и реализацию, но интересно почему изначально wi-fi не рассматривали? Сейчас же существует много приложений для передачи файлов через Wi-Fi Direct, да и из коробки часто поддерживается
                                                                                          0
                                                                                          Спасибо, вот что-то подобное Wi-Fi Direct я очень давно хочу, чтобы было из коробки и стандартно для всех девайсов, но пока это далеко от истины. iOS-устройства, например Wi-Fi Direct не поддерживают, у них там своя технология.
                                                                                            0
                                                                                            Нашел приложение ShareIt от Lenovo, есть под все основные платформы, iOS в том числе, заявлена передача файлов между разными платформами по Wi-Fi, нет только возможности проверить.

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

                                                                                        Самое читаемое