Pull to refresh

Моя попытка сделать Wi-Fi-флешку и что из этого получилось (а что нет)

Reading time11 min
Views58K

Wi-Fi-флешка? WTF?

Что такое Wi-Fi-флешка? Это флешка, которая опознается, как флешка, пахнет, как флешка, крякает, как флешка, но на самом деле никакая она не флешка, она просто эмулирует файловую систему, а файлы берет по Wi-Fi с сервера.

Но зачем?

Да, на самом деле, и не зачем. Просто как-то пришла в голову идея о чем-то подобном и стало интересно, реально ли это вообще? И немного подумав, я понял, что вроде все необходимое для реализации проекта у меня есть, и я таки решился попробовать. Кому-то нравится в свободное время играть в игры, качать героя, стрелять из танков, самолетов, кому-то смотреть фильмы или сериалы, кому-то читать художественную литературу, кому-то решать головоломки, проходить квесты, выбираться из комнат. А мне вот захотелось пройти свой квест, чисто For Fun. Да и к тому же, даже если результат проекта мне сейчас не нужен, это в любом случае будет хороший опыт, который может пригодиться в будущем. Но теоретическое назначение этому мне виделось следующее.

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

Для этого нам нужно

  • ESP32-S2 (так как она сразу умеет в USB OTG).

  • USB разъем (так как эта плата не умеет в USB через разъем для прошивки, хотя вроде есть версии с двумя USB Type-C, где один как раз 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 Leonardo, например, может). Ну ладно, не может и не может, вопрос решался довольно просто, GPIO20 на плате это USB_DATA+, GPIO19 это USB_DATA-.

В результате получилось так:

Питание пока не подключено, только линии данных, так как при отладке она будет питаться через Micro-USB, но если делать законченное устройство, то нужно землю к земле, 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 Мбит/с. Хм, надо оптимизировать что-то еще...

А ведь все начиналось так хорошо...

Дальше, что бы я не делал, скорость, увы, не росла. На форумах у людей получалось поднимать скорость Wi-Fi платы перекомпилируя SDK с другими параметрами. Я тоже пробовал так делать, но мне это дало процентов 5–10 прироста и все. "Что я делаю не так?" пролетало у меня в голове, "они ведь пишут, что им удалось поднять скорость чуть ли не в 5 раз! Почему у меня не получается?". И тут мне на выручку пришло одно полезное правило:

Когда уже больше ну ничего не помогает, прочти уже наконец целиком текст сообщений, а не только те его куски которые тебе интересны

И вот я прочел и все понял: у них изначально была скорость в 5 раз ниже моей, а после всех ускорений, стала примерно такая же, как у меня сейчас и когда они жаловались на медленный Wi-Fi, они жаловались не на 300 КБ/с, как зажравшийся я, а на 50-60 КБ/с. И выходит, что 300-350 КБ/с это и есть предел скорости на ESP32. Круче только на бенчмарках у производителей платы, но похоже, что там специфические тесты. И это весьма грустно, так как довольно прилично портит функционал устройства. Я конечно же не ожидал 54 мегабита по воздуху от ESP, но рассчитывал хотя бы на 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 (но только из-за не стандартизированных сокетов C++ и возможно еще пары функций, вроде AnsiToOem, честно говоря было просто лень искать кроссплатформенные альтернативы и не хотелось подключать сторонние библиотеки, но думаю портировать код на другие ОС не будет большой проблемой).

Флешка большого объема
Флешка большого объема

Оптимизаторы оптимизировали-оптимизировали...

Еще интересный момент, связанный с инициализацией флешки. Windows подключает флешку тем дольше, чем больше она по объему, так как хочет полностью прочитать всю таблицу секторов, а для 200+ ГБ она занимает более 60 МБ (а скорость помним, ~300 КБ/с) и это прям не быстро. Совсем. Флешка такого объема подключается несколько минут. А вот Android, при подключении к нему флешки, очень скромен и запрашивает данные только при необходимости, поэтому к нему флешка подключается мгновенно. Зато при запуске видео, Android начинает читать кучу всего, и самого запуска видео я на нём так и не дождался, устал ждать).

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

Для этого, помимо количества секторов на диске, мой сервер стал отдавать плате немного больше полезной информации. И вот, когда код был написан, и я его запустил, я ожидал, как при подключении к USB флешка в ту же секунду определится. Ну ладно, может через пару секунд. Но увы тут меня ждал второй облом, скорость увеличилась раза в 2, но не более. Я уже стал грешить, что написал медленный код генерации кусочков таблицы FAT на ESP32, но нет, мой код был быстр, как гордый лев, удирающий от разъяренного носорога, чего нельзя сказать про работу USB на плате. И тут я вспомнил... Ну конечно, там же USB 1.1 и 10 мегабит скорости! Но с другой стороны, должно же быть 1.25 МБ/с... В идеальных условиях и идеальном мире, а в реальном те 800 КБ/с, что у меня получились — это похоже был максимум, на что я мог рассчитывать, так что не так уж и страшен этот медленный Wi-Fi, все равно порог скорости не так уж далеко, не 350 КБ/с так 800 КБ/с. Ладно... Я спокоен.

Вишенкой на торте я хотел записать видео, как я запускаю с получившейся "флешки" фильм на телевизоре, и уже было собрался это делать, так как я помню что у одного из моих старых телеков точно был USB разъем, но увы, расходимся пацаны, кина не будет, не для нас USBишенька цвела:

Ничего лишнего, только сервис
Ничего лишнего, только сервис

Немного идей

Еще поразмыслив, я решил, что идеальная флешка могла бы работать не в связке с каким-то специальным сервером, а более универсально, например, подключаясь по FTP или WebDAV сама эмулировала бы FAT32, а данные получала бы прямо с этих серверов. Это было бы просто прелестно, будь у ESPшки нормальная скорость Wi-Fi и USB. Но, увы, нет ручек — нет конфеток. Но идея прекрасна, возможно когда-нибудь...

Настройка флешки

Изначально все настройки (Wi-Fi, сервер) этой "флешки" были прямо в прошивке, но я подумал, что так уж совсем негоже. По хорошему надо сделать подключение к ней с телефона, как к Wi-Fi точке доступа и настраивать все через Web. Но тогда надо еще сделать какую-то кнопку, чтобы сбрасывать настройки, иначе как потом перевести её снова в точку доступа? И если бы я делал законченное устройство, я бы так и сделал, а еще придумал бы для этого всего корпус, но мне это устройство не сильно нужно, поэтому и с настройкой я так заморачиваться не хотел и остановился на компромиссе: все будет настраиваться командами, через любой из COM портов (либо через тот, что используется для прошивки, либо через тот, что идет бонусом вместе с USB устройством). Это, конечно, еще не человек, но уже и не обезьяна, и не нужно пересобирать прошивку для изменения настроек сервера или Wi-Fi.

Команды для настройки флешки
Команды для настройки флешки

Итого

На этом, в общем-то, проект можно считать завершенным. У меня еще была идея сделать псевдо-меню на файлах флешки и таким принципом работы:

  1. Мы подключаем флешку к ТВ и она показывает нам фейковый список видео файлов (это будет, например, список роликов с ютуба).

  2. На ТВ мы выбираем файл и запускаем его.

  3. Сервер видит к какому файлу идет обращение и может скачать этот файл с ютуба, но он не может сразу подменить содержимое у запрошенного файла. У фейкового файла был фейковый размер и он будет отличаться от реального, а менять эти данные на уже подключенной флешке мы не можем. Но мы можем послать ей команду на перезагрузку, она отвалится, подключится снова и на ней будет уже другой список файлов. Так мы и будем делать навигацию по меню с проваливанием внутрь пунктов и возвращением обратно, через отвал флешки. Можно даже сделать прогресс бар скачивания файла, когда флешка будет периодически отваливаться и там будет файл с процентами загрузки в имени файла, а потом уже скачанный файл, который можно запускать и файл "назад", который возвращает к списку. Клевое извращение, правда?))

Звучит дико и забавно, но я все таки решил этого не делать) Слишком уж большое извращение над USB, да и к тому же, я в курсе, что существуют дешевые HDMI-устройства на Android, которые превращают любой старый ТВ в смарт. У меня даже валялся такой где-то.

Ну а пока оставляю тут исходный код того, что получилось, надеюсь кому-то было интересно и кому-то будет полезно:

https://github.com/CodeName33/Wi-Fi-Flash-Drive

Спасибо за внимание)

Tags:
Hubs:
Total votes 203: ↑203 and ↓0+203
Comments100

Articles