Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
WiFi-флешка? WTF?
Что такое WiFi-флешка? Это флешка, которая опознается, как флешка, пахнет, как флешка, крякает, как флешка, но на самом деле никакая она не флешка, она просто эмулирует файловую систему, а файлы берет по WiFi с сервера.
Но зачем?
Да, на самом деле, и не зачем. Просто как-то пришла в голову идея о чем-то подобном и стало интересно, реально ли это вообще? И немного подумав, я понял, что вроде все необходимое для реализации проекта у меня есть, и я таки решился попробовать. Кому-то нравится в свободное время играть в игры, качать героя, стрелять из танков, самолетов, кому-то смотреть фильмы или сериалы, кому-то читать художественную литературу, кому-то решать головоломки, проходить квесты, выбираться из комнат. А мне вот захотелось пройти этот квест, чисто For Fun. Да и к тому же, даже если результат проекта мне сейчас нужен, это в любом случае будет хороший опыт, который может пригодится будущем. Но теоретическое назначение этому мне виделось такое:
Есть старые устройства, которые умеют читать флешки (телевизоры, музыкальные центы) но при этом еще не доросли до сети. Чтобы постоянно не перезаписывать для них флешку с файлами, можно сделать такое вот беспроводное автоматическое обновление. Ну это только один из вариантов использования, на самом деле, мне кажется много чего можно сделать из этой технологии, поэтому вперед!
Для этого нам нужно
ESP32-S2 (т.к. она сразу умеет в USB OTG)
USB разъем (т.к. эта плата не умеет в USB через разъем для прошивки, хотя вроде есть версии в двумя USB TypeC, где один как раз OTG, но у меня такой нет)
Упрямство, отвага и слабоумие (т.к. без этого никуда)
Свободное время
Ну, что ж, начнем...
Для программирования плат Arduino и ESP я использую Arduino, но не саму студию (она ужасна), а только её SDK. А для написания кода мне гораздо больше нравится плагин Visual Micro для Visual Studio. Но проще (и бесплатнее) это делать через Arduino IDE. Как всем этим пользоваться я тут расписывать не буду, иначе эта статья растянется на километры, поэтому предположим, что вы это все знаете и умеете. Брынь! (взмах волшебной палочкой). Все. Теперь вы знаете и умеете.
Первое что я сделал - это взял демо скетч USBMSC, который идет в комплекте к SDK для ESP32, использующий tinyUSB:
https://github.com/espressif/arduino-esp32/blob/master/libraries/USB/examples/USBMSC/USBMSC.ino
В нем видно, что эмуляция флешки - это очень просто. Для начала я просто собрал его, чтобы проверить. Тогда я еще не знал, что tinyUSB в ESP32-S2 не может работать по встроенному USB для прошивки (Arduino Leonaro, например, может). Ну ладно, не может и не может, вопрос решался довольно просто, GPIO20 на плате это USB_DATA+, GPIO19 это USB_DATA-
В результате получилось так:
Питание пока не подключено, только линии данных, т.к. при отладке она будет питаться через microUSB, но если делать законченное устройство то нужно землю к земле, 5 вольт к 5 вольтам. После этого скетч заработал, и у меня тут же в системе появился диск какого-то микроскопического объема с файлом README + еще один COM порт. Это меня порадовало, пришло время менять этот код на нужный мне. Я разделил этот процесс на несколько этапов.
Работа с готовым образом флешки
Сперва я решил начать с простого, взять готовый образ флешки с файлами и его передавать. Это казалось довольно легко, ведь в примере все сделано на довольно нехитрых функциях:
static int32_t onRead(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
static int32_t onWrite(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
Метод onWrite мне вообще не нужен, т.к. я пока не планирую возможность записи на такую флешку, вернем в нем bufsize, типа мы все записали. А вот метод onRead как раз нужен, и вроде все просто, данные спрашивают у меня, я спрашиваю у сервера, получаю результат, отдаю его спрашивающему.
Но начал я с того, чтобы создать образ флешки. Нужен именно чистый образ, без лишних данных и без дополнительных заголовков файловых контейнеров. Для Windows есть программы для создания подобного, вроде Win32DiskImager, но я просто написал такой код на C и создал образ с реальной флешки:
FILE* f = fopen("\\\\.\\PhysicalDrive8", "rb");
FILE* fd = fopen("V:\\WFLASH.RAW", "wb");
if (f == NULL)
{
std::cout << "Erro opening drive\n";
return 1;
}
__int64 size = 0;
const int BUFFER_SIZE = 512;
char buffer[BUFFER_SIZE];
while (fread(buffer, 1, BUFFER_SIZE, f) == BUFFER_SIZE)
{
fwrite(buffer, 1, BUFFER_SIZE, fd);
size += BUFFER_SIZE;
if ((size % 4194304) == 0)
{
std::cout << "\r Copied: " << size;
}
}
PhysicalDriveНОМЕР_ДИСКА - который можно посмотреть в оснастке управление компьютером - управление дисками.
Далее я поднял простой HTTP сервер (у меня был ламповый Apache) и накидал скрипт на PHP:
<?php
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
header("Content-Type: application/octet-stream");
if (!isset($_GET["pos"]))
{
$_GET["pos"] = 0;
}
if (!isset($_GET["length"]))
{
$_GET["length"] = 512;
}
file_put_contents("./log.txt", "POS=".$_GET["pos"].", LENGTH=".$_GET["length"].PHP_EOL, FILE_APPEND);
$file = fopen("v:/WFLASH.RAW", "r");
fseek($file, $_GET["pos"]);
echo fread($file, $_GET["length"]);
fclose($file);
?>
Скрипт просто получает позицию и размер данных, которые ему нужно прочитать, читает и отдает. Здесь все легко. Повозиться пришлось на стороне платы. Она никак не хотела подключаться и что-то получать. Потом до меня дошло, что в методе onRead не надо этого делать, он вызывается как-то по прерыванию, и в нем не все работает, в нем можно поспать (типа delay(1) в цикле) в ожидании нужного результата, а во время его сна уже работает основная функция цикла программы loop() и в ней как раз можно и подключаться и получать данные. После N минут (часов, дней...) отладки все наконец-таки заработало и я увидел образ флешки, как новый диск у себя в компьютере. Не скажу, что работало это быстро, скажу, что работало это очень медленно. Вначале я думал на медленный USB HOST на ESP32-S2, но потом понял, что медленно идет именно получение данных. Поэтому перешел ко второму этапу - вещать образ флешки не через HTTP, где надо еще формировать и парсить заголовки, и не PHP скрипта, который при каждом запросе по новой открывает файл, а написать на C простой сервер на сокетах с постоянным соединением, это явно будет быстрее.
Сказано - сделано. Сервер был написан, и даже дал определенный прирост производительности, но небольшой. Тогда я решил сделать буфер данных на плате. Например запрашивают у меня один сектор 512 байт, но ведь наверняка, как только я его отдам - у меня тут же запросят следующий, идущий за ним. А мне надо по каждому сектору спрашивать данные и получать ответ. Но я сделаю умнее, я сразу запрошу 32К данных и потом буду супер-турбо-мега-быстро отдавать данные уже из памяти. До тех пор пока не запросят что-то за пределами буфера. При случайном чтении станет, конечно же, еще медленнее, но чаще всего чтение идет последовательное, поэтому делаю кэш.
Снова сказано - снова сделано. Стало ещё чуть быстрее, но все равно не то, 65К данных приходят примерно за 200мс, 32К за 100, это где-то 2-2,5Мегабита. Хм, надо оптимизировать что-то еще...
А ведь все начиналось так хорошо...
Дальше, что бы я не делал, скорость, увы, не росла. На форумах у людей получалось поднимать скорость WiFi платы перекомпилируя SDK с другими параметрами. Я тоже пробовал так делать, но мне это дало процентов 5-10 прироста и все. "Что я делаю не так?" пролетало у меня в голове, "они ведь пишут, что им удалось поднять скорость чуть ли не в 5 раз! Почему у меня не получается?". И тут мне на выручку пришло одно полезное правило:
Когда уже больше ну ничего не помогает, прочти уже наконец целиком текст сообщений, а только те его куски которые тебе интересны
И вот я прочел и все понял: у них изначально была скорость в 5 раз ниже моей, а после всех ускорений, стала примерно такая же, как у меня сейчас и когда они жаловались на медленный ВайФай, они жаловались не на 300кб/с, как зажравшийся я, а на 50-60кб/с. И выходит, что 300-350к/сек это и есть предел скорости на ESP32. Круче только на бенчмарках у производителей платы, но похоже, что там специфические тесты. И это весьма грустно, так как довольно прилично портит функционал устройства. Я конечно же не ожидал 54 мегабита по воздуху от этой платы, но рассчитывал хотя бы на 20мегабит. Ну хотя бы 10? Ну пожалуйста... Ну и ладно. В целом, все работает и видео с битрейтом до 2 мегабит играются без тормозов (ни о каком HD конечно же речь не идет, 2 мегабита - это довольно низкий битрейт даже для 720p, но для SD вполне). Можно пока оставить этот момент как есть, все равно с ним уже ничего не сделаешь, и продолжить двигаться дальше, туда где можно что-то еще улучшить.
— Друзья! У нас две проблемы. Минобороны и пуговица. Пуговицу мы найти можем? Чисто теоретически? Можем. А с Минобороны... ничего. Вывод: ищем пуговицу. (День радио)
Ищем пуговицу
Итак мой сервер уже умеет передавать данные с готового образа, но если бы я видел завершение проекта таким, но скорее всего даже не начал бы его. Потому, что в этом нет никакого смысла. Если для того, чтобы добавить файл для передачи его нужно копировать в образ - то тогда зачем все это, если можно точно также скопировать его на обычную флешку и вставить в устройство. Да и к тому же, у меня на диске лежит, например, фильмов на 200Гб, это получается нужно либо делать образ флешки еще на 200Гб, либо постоянно вручную копировать их в образ меньшего объема. Нет, это все полная фигня. Я видел это совсем по другому. Я хочу, чтобы я просто указал серверу "расшаренную" папку, а он сам её просканирует и создаст у себя в мозгах виртуальный образ, но физически не будет его записывать на диск или в память, а просто понимая в каком диапазоне секторов у него находятся какие файлы и когда эти сектора запрашивают, он просто будет открывать настоящие файлы и брать данные оттуда. Таким образом он создаст образ не создавая его) И это будет быстро и не потребует много памяти даже для 200+Гб образов.
Чтобы это реализовать, нужно понимать какую файловую систему эмулировать. FAT16 имеет ограничения на размер раздела в 2ГБ, что сразу нет. NTFS слишком сложный и наврятли его понимают старые устройства, как и exFAT. Остается FAT32 с его единственным важным ограничением - максимальная длина одного файла 4Гб, остальное все неплохо. Снаружи. Тогда я еще не знал, что там внутри...
Там внутри такое... Там внутри такое! Там внутри такое!! Я не буду говорить, что там внутри никому, кроме своего психотерапевта, а т.к. его у меня нет, то вообще никому. Но эта информация открыта, её можно легко найти в сети, если есть такое желание. Особенно доставляет то, как там сделана поддержка длинных имен файлов, это настолько ублюдство, что даже великолепно. Но важно понимать, что перед людьми, придумавшими это, стояла важная задача - сохранить совместимость с устройствами и программами, которые знать не знали ни о каких длинных именах файлов и важно было, чтобы потом из-за этого изменения не потерялись важные файлы на тысячах компьютеров по всему миру, поэтому вариантов у разработчиков было не много. Впрочем после N минут (часов, дней...) отладки я научился читать образы FAT32 и вытаскивать оттуда файлы и папки, а после Nх3 минут (часов, дней...) я научился их создавать такими, чтобы их могли читать другие. Ну а дальше, спустя еще пару веков, сервер научился и эмулировать FAT32 больших размеров, храня у себя в памяти только структуру FAT, а сами данные файлов беря из их настоящего расположения. Я попробовал проэмулировать папку с фильмами, получилось 229ГБ данных, файлы размером более 4Гб отсекаются сервером еще на этапе сканирования папок, иначе размер образа был бы еще больше. Жаль что большая часть этих фильмов не способна воспроизводиться без жутких тормозов, но старые 700Мб рипы играются очень даже бодро)
На сегодняшний день сервер работает только под Windows (но только из-за не стандартизированных сокетов с++ и возможно еще пары функций, вроде AnsiToOem, честно говоря было просто лень искать кроссплатформенные альтернативы и не хотелось подключать сторонние библиотеки, но думаю портировать код на другие ОС не будет большой проблемой).
Оптимизаторы оптимизировали-оптимизировали...
Еще интересный момент связанный с инициализацией флешки. Windows подключает флешку тем дольше, чем больше она по объему, т.к. хочет полностью прочитать всю таблицу секторов, а для 200+ГБ она занимает более 60Мб (а скорость помним, ~300 Кб сек) и это прям не быстро. Совсем. Флешка такого объема подключается несколько минут. А вот андроид, при подключении к нему флешки, очень скромен и запрашивает данные только при необходимости, поэтому к нему она подключается мгновенно, зато при запуске видео он начинает читать кучу всего и самого запуска видео я на андроиде так и не дождаться, устал ждать). Но все таки решил немного ускорить загрузку данных для Windows. И раз он читает всю таблицу FAT, я могу отдать ему её быстрее. И добиться этого можно, если сделать плату чуть умнее, не просто как прокси, которая гоняет через себя данные, а ещё совсем немного в них разбирается. А также особенность того, что я эмулирую идеальный FAT. Это FAT в котором нет фрагментации, FAT в котором данные каждого файлика идут строго друг за другом, без пропусков и без перемешивания секторов, мой FAT - это FAT в котором волшебные пони кушают волшебную радугу, голосуйте за мой FAT! Поэтому в моей таблице секторов в разделе с данными самих файлов всего два варианта записей: информация о том, где следующий сектор файла (а это всегда на единицу больше текущего) или информация о том, что это последний сектор файла. Поэтому чтобы построить её, нужно всего лишь иметь массив с конечным (или начальным) сектором каждого файла отсортированный по возрастанию. Поэтому помимо количества секторов на диске, мой сервер стал отдавать плате немного больше полезной информации. И вот, когда код был написан и я его запустил, я ожидал, как при подключении к USB флешка в ту же секунду определится. Ну ладно, может через пару секунд. Но увы тут меня ждал второй облом, скорость увеличилась раза в 2, но не более. Я уже стал грешить, что написал медленный код генерации кусочков таблицы FAT на ESP32, но нет, мой код был быстр, как гордый лев, удирающий от разъяренного носорога, чего нельзя сказать про работу USB на плате. И тут я вспомнил... Ну конечно, там же USB 1.1 и 10 мегабит скорости! Но с другой стороны, это же должно быть 1.25Мб/сек... А это в идеальных условиях и идеальном мире, а в реальном те 800кб/сек, что у меня получились - это похоже был максимум, на что я мог рассчитывать, так что не так уж и страшен этот медленный WiFi, все равно порог скорости не так уж далеко, не 350к так 800к. Ладно... Я спокоен.
Вишенкой на торте я хотел записать видео, как я запускаю с этой флешки фильм на телевизоре, и уже было собрался это делать, т.к. я помню что у одного из моих старых телеков точно был USB разъем, но увы, расходимся пацаны, кина не будет, не для нас USBишенька цвела:
Немного идей
Еще поразмыслив, я решил, что идеальная флешка могла бы работать не в связке с каким-то специальным сервером, а более универсально, например, подключаясь по FTP или WebDav сама эмулировала бы FAT32, а данные получала бы прямо с этих серверов. Это было бы просто прелестно, будь у ESPшки нормальная скорость WiFi и USB. Но, увы, нет ручек - нет конфеток. Но идея прекрасна, возможно когда-нибудь...
Настройка флешки
Изначально все настройки (wifi, сервер) этой "флешки" были прямо в прошивке, но я подумал, что так уж совсем негоже. По хорошему надо сделать подключение к ней с телефона, как к WiFi точке доступа и настраивать все это через Web. Но тогда надо еще сделать какую-то кнопку, чтобы сбрасывать настройки, иначе как потом перевести её снова в точку доступа? И если бы я делал законченное устройство, я бы так и сделал, а еще придумал бы для этого всего корпус, но мне это устройство не сильно нужно, поэтому и с настройкой я так заморачиваться не хотел и остановился на компромиссе: все будет настраиваться командами, через любой из COM портов (либо через тот, что используется для прошивки, либо через тот, что идет бонусом вместе с USB устройством). Это, конечно, еще не человек, но уже и не обезьяна, и не нужно пересобирать прошивку для изменения настроек сервера или WiFi.
Итого
На этом, в общем-то, проект можно считать завершенным. У меня еще была идея сделать псевдо-меню на файлах флешки и таким принципом работы:
Мы подключаем флешку к ТВ и она показывает нам фейковый список видео файлов (это будет, например, список роликов с ютуба)
На ТВ мы выбираем файл и запускаем его
Сервер видит к какому файлу идет обращение и может скачать этот файл с ютуба, но он не может сразу подменить содержимое у запрошенного файла. У фекового файла был фейковый размер и он будет отличаться от реального, а менять эти данные на уже подключенной флешке мы не можем. Но мы можем послать ей команду на перезагрузку, она отвалится, подключится снова и на ней будет уже другой список файлов. Так мы и будем делать навигацию по меню с проваливанием внутрь пунктов и возвращением обратно, через отвал флешки. Можно даже сделать прогресс бар скачивания файла, когда флешка будет периодически отваливаться и там будет файл с процентами загрузки в имени файла, а потом уже скачанный файл, который можно запускать и файл "назад", который возвращает к списку. Клевое извращение, правда?))
Звучит дико и забавно, но я все таки решил этого не делать) Это уже слишком большое извращение над USB, да и к тому же, я в курсе, что существуют дешевые HDMI андроид девайсы, который превращают любой старый ТВ в смарт. У меня даже валялся такой где-то.
Ну а пока оставляю тут исходный код того, что получилось, надеюсь кому-то было интересно и кому-то будет полезно:
https://github.com/CodeName33/Wi-Fi-Flash-Drive
Спасибо за внимание)