С caret notation разобрались, теперь допишем оставшийся n-1 символ. Последовательности, начинающиеся с
Посмотрим теперь на конкретный пример: вывод
Мы видим символы "A", "B", "C" (с кодами 0x41, 0x42 и 0x43, соответственно), потом опять "A", а после — перевод строки (код 0x0a).
Теперь попробуем перейти в пустой каталог, создать в нём подкаталог с именем
Скучный, "чёрно-белый" ls просто напечатал уже привычные четыре буквы и перевод строки, а вот в "цветном" варианте размахнулся аж на 21 байт, где не сразу и заметишь наш знакомый маркер "41 42 43 41" и перевод строки "0a". Оставшиеся 16 байт — это последовательности Select Graphic Rendition: сначала двухбайтный префикс
*
*
*
*
*
Такую же последовательность байт (с таким же эффектом) можно самостоятельно выдать в терминал командой
\e[ (то есть двух байтов: ESC и всамделишного [), являются частным случаем Fe escape sequences. После префикса из двух байтов ESC и [ в общем случае идёт сначала ноль и более "байтов-параметров", потом ноль и более "промежуточных байтов", а следом — ровно один "финальный байт". И "параметры" и "промежуточные" байты принадлежат алфавитам из 16 символов: байты-параметры имеют коды 0x30-0x3F (цифры 0-9 и ещё шесть "спецсимволов", следующих за ними в таблице: :;<=>?), но обычно представляют собой последовательность десятичных чисел, разделённых точкой с запятой, а промежуточные байты — 0x20-0x2F (!"#$%&'()*+,-./ — сплошные "спецсимволы"). Ассортимент финальных байтов гораздо богаче: этот байт должен иметь код из диапазона 0x40-0x7E, что включает в себя все заглавные и строчные буквы, плюс 11 "спецсимволов" (а вот непечатный символ DEL с кодом 0x7F не включает — упражнение на внимательность).Посмотрим теперь на конкретный пример: вывод
ls. Чтобы увидеть, какие байты печатаются в терминал, перенаправим вывод команды на вход утилиты hd, которая выдаст три колонки: смещение от начала файла (или, как у нас, потока байтов), строчку из 16 байтов в шестнадцатеричном виде и те же байты, но текстом (заменяя непечатные символы на точку). Для начала поймём, как будет выглядеть обычный текст без "украшений":$ echo ABCA | hd
00000000 41 42 43 41 0a |ABCA.|
00000005
Мы видим символы "A", "B", "C" (с кодами 0x41, 0x42 и 0x43, соответственно), потом опять "A", а после — перевод строки (код 0x0a).
Теперь попробуем перейти в пустой каталог, создать в нём подкаталог с именем
ABCA и "увидеть" его при помощи ls:$ ls --color=never | hd
00000000 41 42 43 41 0a |ABCA.|
00000005
$ ls --color=always | hd
00000000 1b 5b 30 6d 1b 5b 30 31 3b 33 34 6d 41 42 43 41 |.[0m.[01;34mABCA|
00000010 1b 5b 30 6d 0a |.[0m.|
00000015
Скучный, "чёрно-белый" ls просто напечатал уже привычные четыре буквы и перевод строки, а вот в "цветном" варианте размахнулся аж на 21 байт, где не сразу и заметишь наш знакомый маркер "41 42 43 41" и перевод строки "0a". Оставшиеся 16 байт — это последовательности Select Graphic Rendition: сначала двухбайтный префикс
\e[, потом последовательность чисел-команд через точку с запятой и завершающая буква m:*
\e[0m (4 байта, команда 0) — сбросить все атрибуты (reset or normal)*
\e[01;34m (8 байтов, команды 1 и 34) — поярче (bold or increased intensity) и синим текстом (blue foreground)*
ABCA (4 байта) — собственно, само имя каталога*
\e[0m (4 байта, команда 0) — прибираем рабочее место, опять сброс*
\n (1 байт) — перевод строкиТакую же последовательность байт (с таким же эффектом) можно самостоятельно выдать в терминал командой
printf '\e[0m\e[01;34mABCA\e[0m\n'
Мелочь, а удобно. Иногда нужно зайти по SSH на удалённую систему и скачать там что-нибудь из интернета. Обычно в таких случаях я просто переходил в каталог, куда нужно скачать файл, командовал идти, куда послали обрабатывать перенаправления в HTTP и
Однажды скачиваемый файл оказался не просто
и curl сам разберётся со счётчиком, и скачает все 20 файлов один за другим. Кавычки нужны не для curl (он их как раз не увидит), а чтобы bash (думаю, ровно то же применимо и к zsh) даже не пытался сам раскрывать шаблоны с квадратными скобками, а просто достал содержимое из одиночных кавычек, и передал как ровно один аргумент запускаемой программе. Кроме приведённого выше примера у curl есть ещё несколько аналогичных вариантов раскрытия одного урла в последовательность — они удобно указаны прямо в начале man-страницы.
wget https://example.org/path/to/file.tar.gz и радовался. Правда, иногда вместо wget был только curl — тогда радовался я чуть меньше, потому что нужно было вспомнить минимальный набор опций: что-то вроде -L, чтобы -O, чтобы просто сохранить файл под ожидаемым именем file.tar.gz.Однажды скачиваемый файл оказался не просто
file.tar.gz, а file-1.tar.gz, file-2.tar.gz, ..., file-20.tar.gz (а там ведь и весь остальной URL https://example.org/path/... к каждому приписан) — получается очень длинная командная строка, и запросто можно запутаться и что-нибудь пропустить. Тут-то обнаружилась оборотная сторона курловых хитростей: можно просто написать (обратите внимание на одиночные кавычки)curl -L -O 'https://example.org/path/to/file-[1-20].tar.gz'
и curl сам разберётся со счётчиком, и скачает все 20 файлов один за другим. Кавычки нужны не для curl (он их как раз не увидит), а чтобы bash (думаю, ровно то же применимо и к zsh) даже не пытался сам раскрывать шаблоны с квадратными скобками, а просто достал содержимое из одиночных кавычек, и передал как ровно один аргумент запускаемой программе. Кроме приведённого выше примера у curl есть ещё несколько аналогичных вариантов раскрытия одного урла в последовательность — они удобно указаны прямо в начале man-страницы.
А вот ещё одна байка на стыке двух предыдущих тем, но совсем свежая: о том, как я решил сохранить локально подборку iso-образов с исходниками Debian (суммарно около 90 гигов). И в тот момент мне показалось логичным (наверное, это нифига не логично, но тогда показалось), что торренты торрентами, пусть файлы не валяются без дела, а раздаются, но вот скачать не столь популярные образы будет эффективнее по HTTP с географически близкого зеркала, вот прям curl-ом, ага, такой вот принудительный web seed.
Поскольку любым способом качать пришлось бы долго, запустил команду наподобие такой
с помощью стандартной утилиты
И вот я запустил загрузку, потом периодически смотрел прогресс и видел там отчёты вроде
Вывод прямо, как "в прямом эфире", только последняя строчка (текущий загружаемый файл) не обновляется каждую секунду, ведь это всего лишь напечатанный в терминал файл, каким он был на момент печати. Ещё удивлялся, какой curl умный: проверил, что дескрипторы stdout/stderr указывают на обычный файл (с помощью isatty что ли?..) и стал аккуратно позиционироваться в
Секрет фокуса оказался в том, что фокус мне никто не показывал и даже не собирался: файл на полсотни отображаемых строк весил мегабайт с лишним. Оказывается, curl просто печатал форматированный вывод в выходной поток, не смотря ни на что, прямо со всеми "вернись к началу строки и напиши заново" каждую секунду. Когда же я в очередной раз выполнял
Поскольку любым способом качать пришлось бы долго, запустил команду наподобие такой
nohup curl --limit-rate 3M -L -O 'https://...-source-DVD-[1-20].iso' &
с помощью стандартной утилиты
nohup обезопасив curl от завершения вместе с командной оболочкой при отключении от ssh. Важно, что при этом утилита nohup перенаправляет вывод программы в файл nohup.out в текущем каталоге.И вот я запустил загрузку, потом периодически смотрел прогресс и видел там отчёты вроде
# cat nohup.out
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4476M 100 4476M 0 0 3075k 0 0:24:50 0:24:50 --:--:-- 3078k
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 4481M 100 4481M 0 0 3075k 0 0:24:51 0:24:51 --:--:-- 3073k
...
Вывод прямо, как "в прямом эфире", только последняя строчка (текущий загружаемый файл) не обновляется каждую секунду, ведь это всего лишь напечатанный в терминал файл, каким он был на момент печати. Ещё удивлялся, какой curl умный: проверил, что дескрипторы stdout/stderr указывают на обычный файл (с помощью isatty что ли?..) и стал аккуратно позиционироваться в
nohup.out, обновлять строчку текущей загрузки, никакой тебе мешанины, как у некоторых, когда на каждую десятую процента выводится новая строка...Секрет фокуса оказался в том, что фокус мне никто не показывал и даже не собирался: файл на полсотни отображаемых строк весил мегабайт с лишним. Оказывается, curl просто печатал форматированный вывод в выходной поток, не смотря ни на что, прямо со всеми "вернись к началу строки и напиши заново" каждую секунду. Когда же я в очередной раз выполнял
cat nohup.out, мегабайт текста вперемешку с escape-последовательностями в очередной раз оказывался отправлен по SSH в "программу-терминал" (или, говоря корректнее, эмулятор терминала) на "большой компьютер", которая в очередной раз интерпретировала мегабайтный вывод курла, отображая в конечном итоге полсотни строк текста. Теперь мне уже интересно, почему НЕ все программы ведут себя столь же прозрачно, и могут формировать много повторяющихся строк при перенаправлении вывода. Наверное, они пытаются поступать правильно и работать с произвольным терминалом через какой-нибудь ncurses, а не захардкоженные ANSI escape sequences, и получив от библиотеки ответ, что это не терминал, а вообще непойми что, сами откатывались на "некрасивый" формат вывода.Последнее время я начал присматриваться, как вообще можно подступиться к использованию нейросетей в быту, но, разумеется так, чтобы локальный запуск, безопасность, все дела... Так вот, один из известных "производителей" LLM, пригодных для локального инференса — французская Mistral AI, но, конечно, пообщаться с её нейронками можно и в облаке при помощи продукта под названием le Chat. И меня веселит это название, поскольку в школе я сначала изучал французский, и до сих пор помню, что "le chat" — это "кот" (произносится как "лё ша"). В общем, начитавшись новостей о всех этих новомодных тайминг-атаках на кэш процессора и историй о переполнении буфера в firmware сетевой карты, я уже давно ценю замечательное изречение: "Паранойя — это не когда разговариваешь только со своим котом, паранойя — это когда при коте боишься сболтнуть лишнего". А тут оно прямо заиграло новыми красками.
Знаете ли вы?
... что среди RFC-документов, описывающих многие важные стандарты, существует RFC 2119, определяющий смысл терминов MUST (NOT) / SHOULD (NOT) / MAY, используемых в других документах. Более того, есть также RFC 8174, разъясняющий, что RFC 2119 относится только к терминам, написанным заглавными буквами, а остальные слова подчиняются обычным правилам английского языка.
Последующие документы просто ссылаются на эти разъяснения:
... что среди RFC-документов, описывающих многие важные стандарты, существует RFC 2119, определяющий смысл терминов MUST (NOT) / SHOULD (NOT) / MAY, используемых в других документах. Более того, есть также RFC 8174, разъясняющий, что RFC 2119 относится только к терминам, написанным заглавными буквами, а остальные слова подчиняются обычным правилам английского языка.
Последующие документы просто ссылаются на эти разъяснения:
1.1. Conventions Used in This Document
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
...
Win2000 на раритетном ПК, ностальгия... А вот и нет! Свежайшая OpenSUSE MicroOS, ставящаяся на малюсенький одноплатник с управлением установкой через USB-UART converter. Универсальный iso-шник для AArch64 систем на базе UEFI, между прочим (современный U-Boot предоставляет минимальный набор UEFI-сервисов, достаточный для передачи управления загрузчику с воткнутой в одноплатник флешки — практически, как на x86_64 десктопе).
🔥2
#знаетеливы что "в линуксе" можно запретить изменять некоторый файл даже пользователю root. Поддержка разнится от ФС к ФС, но как минимум на Ext4 и Btrfs присутствует. При помощи утилиты chattr и админских прав можно выставить флаг immutable:
chattr +i /path/to/file, и теперь даже root не сможет изменить файл (пока не снимет этот атрибут при помощи chattr -i ...).A file with the 'i' attribute cannot be modified: it cannot be deleted or renamed, no link can be created to this file, most of the file's metadata can not be modified, and the file can not be opened in write mode. Only the superuser or a process possessing the CAP_LINUX_IMMUTABLE capability can set or clear this attribute.
🔥1
#загадка
Решил на праздниках почистить старые ветки в локальных копиях репозиториев. Причём локальных копий у меня обычно по две штуки — по одной на каждом из компьютеров (а иногда и не по одной, но не будем о грустном), поэтому помимо задачи "удалить то, что давно влито в main / заменено лучшим решением / ещё почему-то не пригодилось" есть ещё "удалить ненужные дубликаты между локальными клонами репозиториев" (ну или осознано сделать резервную копию того, что важно). И вот смотрю я на один такой дубликат (назовём его feature-branch) и вижу, что более свежая версия синхронизирована с моим личным форком на гитхабе (назовём его my-fork). Что же, одной проблемой меньше: нет надобности возиться с fetch-ем между машинами (об этом как-нибудь потом), можно будет просто сравнить более старую версию ветки с той, что с гитхаба.
И тут началось странное: я зашёл на вторую машину по ssh и обнаружил, что старая версия тоже совпадает с
В итоге я всё-таки вспомнил об одной особенности git с настройками по умолчанию, зашёл на гитхаб через веб, чтобы убедиться, и действительно: ...
... ветку на гитхабе я уже удалил ранее, а git fetch my-fork с настройками по умолчанию передвигает в локальном "зеркале" my-fork/<branch-name> указатели тех веток, которые всё ещё присутствуют в удалённом репозитории — даже, если это будет force update — но вот чтобы он удалял "удалённо-пропавшие" ветки и из локального "зеркала", нужно указать опцию --prune. А ещё есть опция --dry-run, чтобы сначала посмотреть, что будет удалено.
Ну и дисклеймер: сначала стоит почитать документацию и прикинуть, как опция --prune будет работать с вашим репозиторием и вашим workflow: может, не просто так её не включили по умолчанию.
Решил на праздниках почистить старые ветки в локальных копиях репозиториев. Причём локальных копий у меня обычно по две штуки — по одной на каждом из компьютеров (а иногда и не по одной, но не будем о грустном), поэтому помимо задачи "удалить то, что давно влито в main / заменено лучшим решением / ещё почему-то не пригодилось" есть ещё "удалить ненужные дубликаты между локальными клонами репозиториев" (ну или осознано сделать резервную копию того, что важно). И вот смотрю я на один такой дубликат (назовём его feature-branch) и вижу, что более свежая версия синхронизирована с моим личным форком на гитхабе (назовём его my-fork). Что же, одной проблемой меньше: нет надобности возиться с fetch-ем между машинами (об этом как-нибудь потом), можно будет просто сравнить более старую версию ветки с той, что с гитхаба.
И тут началось странное: я зашёл на вторую машину по ssh и обнаружил, что старая версия тоже совпадает с
my-fork/feature-branch. Ну, давно не делал fetch из удалённого репозитория, сейчас запущу git fetch my-fork и... ой, а ничего не поменялось! Ещё раз сделал git fetch my-fork на каждой из машин, перепроверил написание имени ветки в обоих репозиториях — глухо: и там, и там git log feature-branch показывает по две метки (локальную и my-fork/feature-branch) на головном коммите. Но коммиты-то разные!В итоге я всё-таки вспомнил об одной особенности git с настройками по умолчанию, зашёл на гитхаб через веб, чтобы убедиться, и действительно: ...
Ну и дисклеймер: сначала стоит почитать
Ну и в продолжение вчерашнего: #знаетеливы что "удалённый репозиторий" в разнообразных "сетевых" командах git (clone, fetch, push, ...) — это не обязательно гитхаб не обязательно какой-то хитрый сервер, настроить который под силу только профессиональному админу. Речь о неожиданно банальных, бытовых конфигурациях, от использования которых отделяет, скорее, незнание, что "а так можно было". Ключевая особенность — не надо настраивать никаких серверов.
Командукто бы мог подумать уже занято:
В новом репозитории
Если же у вас есть доступная по ssh машина
и не надо ни на какие гитхабы сначала загружать, потом выгружать — всё напрямую.
Наконец, вместо использования URL со схемой
Команду
git clone можно "натравить" на самую обычную локальную копию репозитория: вместо git clone git@github.com:username/repo можно выполнить git clone file:///home/username/projects/repo (с абсолютным путём к каталогу репозитория и тремя слешами после file:). Ну разве что, если оригинал и копия находятся в одном каталоге, то придётся передать git clone ещё один аргумент: "куда клонировать" — просто потому, что последний компонент исходного пути будет именем клона по умолчанию, а оно $ cd ~/projects
$ git clone file:///home/username/projects/repo
fatal: destination path 'repo' already exists and is not an empty directory.
$ git clone file:///home/username/projects/repo new-repo-copy
Cloning into 'new-repo-copy'...
В новом репозитории
git remote -v будет честно указывать file:///... в качестве origin. Ну и, конечно, никто не мешает регистрировать новые remotes с локальными URL и работать с ними, как с любыми другими:$ git remote add my-local-fork file:///home/username/projects/second-repo
$ git fetch my-local-fork
Если же у вас есть доступная по ssh машина
other-desktop, на которую вы заходите как remoteuser, то можно взаимодействовать с репозиториями оттуда:$ git clone ssh://remoteuser@other-desktop:/path/to/repo
и не надо ни на какие гитхабы сначала загружать, потом выгружать — всё напрямую.
Наконец, вместо использования URL со схемой
file:// можно и просто указать локальный путь. Насколько я понимаю, отличие будет в том, что при указании источника по URL в новом клоне в "скрытом" каталоге .git будут "загруженные" копии исходных объектов, а при использовании обычного пути к каталогу — по умолчанию будут создаваться жёсткие ссылки на исходные файлы, если возможно. С одной стороны — экономия места, с другой — если сильно поломать оригинал, есть возможность разломать и копию: в общем, думайте сами, решайте сами, читайте документацию.❤1
Можно, кстати, даже именованных remotes не добавлять, а вместо этого передавать URL целиком прямо в
git fetch! Правда, тогда придётся проявлять некоторую ловкость, чтобы добраться до результата — локального "зеркала" удалённых веток никто уже создавать не будет:$ git fetch file:///some/repo
remote: Enumerating objects: 1234, done.
...
From file:///some/repo
* branch HEAD -> FETCH_HEAD
$ git log FETCH_HEAD
... тут какие-то коммиты ...
$ git fetch file:///some/repo other-remote-branch
remote: Enumerating objects: 4321, done.
...
From file:///some/repo
* branch other-remote-branch -> FETCH_HEAD
$ git log FETCH_HEAD
... тут какие-то другие коммиты ...
Неочевидно, но факт: радиус кривизны рук — это величина типа "больше — лучше".
Давно было пора рассказать о полезном проекте ESPHome, да как-то хорошего примера не попадалось. И вот на новогодних праздниках пример наконец-то появился. Рассказ будет состоять из двух частей: костыльной аппаратной и, собственно, красивой программной на ESPHome.
Недавно я купил кольцевую лампу с питанием от USB и закрепил её за монитором. Днём позади монитора у меня окно, а вечером позади монитора у меня темно и грустно, так что лампа сглаживает контраст между ярким экраном и "фоном" шторы.
Я уже давно использую "умную" лампу (обычную "свечу" с цоколем E14) в качестве светового будильника: HomeAssistant получает с моего смартфона время срабатывания ближайшего будильника и постепенно зажигает лампу за 20 минут до. Вот я и решил попрактиковаться, и организовать управление замониторной лампой из HA, чтобы её пульт на столе не валялся.
План заключался в том, чтобы аккуратно (ну, почти) вывести из пульта лампы питание USB и необходимые сигналы, и присоединить к мелкой плате с ESP32 вроде этой — в общем, задача на полдня (ага, щаз...):
* GND и 5V питания от USB
* "входные" сигналы для четырёх кнопок ("вкл-выкл", "тёплый-холодный свет", "яркость +" и "яркость -")
* "выходные" сигналы: питание "тёплой" и "холодной" секций светодиодов для обратной связи (иначе я бы смог только "удалённо клацать по кнопкам" — такой себе интерфейс).
#esphome
Недавно я купил кольцевую лампу с питанием от USB и закрепил её за монитором. Днём позади монитора у меня окно, а вечером позади монитора у меня темно и грустно, так что лампа сглаживает контраст между ярким экраном и "фоном" шторы.
Я уже давно использую "умную" лампу (обычную "свечу" с цоколем E14) в качестве светового будильника: HomeAssistant получает с моего смартфона время срабатывания ближайшего будильника и постепенно зажигает лампу за 20 минут до. Вот я и решил попрактиковаться, и организовать управление замониторной лампой из HA, чтобы её пульт на столе не валялся.
План заключался в том, чтобы аккуратно (ну, почти) вывести из пульта лампы питание USB и необходимые сигналы, и присоединить к мелкой плате с ESP32 вроде этой — в общем, задача на полдня (ага, щаз...):
* GND и 5V питания от USB
* "входные" сигналы для четырёх кнопок ("вкл-выкл", "тёплый-холодный свет", "яркость +" и "яркость -")
* "выходные" сигналы: питание "тёплой" и "холодной" секций светодиодов для обратной связи (иначе я бы смог только "удалённо клацать по кнопкам" — такой себе интерфейс).
#esphome
AliExpress
Estardyn ESP32 C3 Super Mini плата на AliExpress
Смотри, что есть на AliExpress! И еще более 200 миллионов товаров со всего мира с возможностью поиска по фото и большими скидками. Погрузись в увлекательный мир шопинга без границ!
Итак, часть аппаратная, костыльная.
И сразу не лишним будет напомнить о технике безопасности: да, на USB пять вольт (если не быстрая зарядка). Но это между GND и Vbus пять вольт, а относительно, например, радиатора центрального отопления там может быть 100-200 вольт, просто ток микроскопический, если блок питания исправен. Несколько лет назад я осознал это внезапно, когда возился с какой-то подключённой к USB платой и ткнул ногой в тёплую батарею. Тут-то палец слегка кольнуло. К счастью, БП компьютера был исправен, так что ток оказался мизерный.
Ну так вот, методом осторожного тыка мультиметром я выяснил, что "управляющие" выводы "пультовой" микросхемы подтянуты к 5V, а нажатие кнопки замыкает их на GND. Питание светодиодов тоже между 0 и 5V. Вот и первая проблема: судя по документации, ESP32 не 5V tolerant. Ну, со входом (со стороны ESP32) сигнала питания светодиодов проблем нет, поможет делитель напряжения: ставим два резистора на десять килоом, посередине снимаем сигнал 0-2.5V. С выдачей сигнала на кнопки сложнее: сам я тривиальной схемы не придумал, а "в интернетах" тоже советуют либо забить, поставить токоограничительный резистор, и надеяться, что ESP32 не сгорит, либо соорудить нехитрый конвертер уровней. Я выбрал второй вариант, решив не срезать углы в учебном проекте, и вместо этого заняться традиционной русской забавой: новогодним поиском транзисторов.
Пожалуй, не буду строить из себя специалиста-электронщика и рекомендовать кому-то конкретный дизайн своих костылей, скажу лишь, что на каждую кнопку у меня приходится по одному полевому транзистору BSS138 и одному резистору (между истоком и затвором, чтобы статикой не побило), а питание каждого из двух каналов светодиодов измеряется при помощи делителя на двух 10k резисторах и сглаживающего фильтра из конденсатора и ещё одного резистора.
#esphome
И сразу не лишним будет напомнить о технике безопасности: да, на USB пять вольт (если не быстрая зарядка). Но это между GND и Vbus пять вольт, а относительно, например, радиатора центрального отопления там может быть 100-200 вольт, просто ток микроскопический, если блок питания исправен. Несколько лет назад я осознал это внезапно, когда возился с какой-то подключённой к USB платой и ткнул ногой в тёплую батарею. Тут-то палец слегка кольнуло. К счастью, БП компьютера был исправен, так что ток оказался мизерный.
Ну так вот, методом осторожного тыка мультиметром я выяснил, что "управляющие" выводы "пультовой" микросхемы подтянуты к 5V, а нажатие кнопки замыкает их на GND. Питание светодиодов тоже между 0 и 5V. Вот и первая проблема: судя по документации, ESP32 не 5V tolerant. Ну, со входом (со стороны ESP32) сигнала питания светодиодов проблем нет, поможет делитель напряжения: ставим два резистора на десять килоом, посередине снимаем сигнал 0-2.5V. С выдачей сигнала на кнопки сложнее: сам я тривиальной схемы не придумал, а "в интернетах" тоже советуют либо забить, поставить токоограничительный резистор, и надеяться, что ESP32 не сгорит, либо соорудить нехитрый конвертер уровней. Я выбрал второй вариант, решив не срезать углы в учебном проекте, и вместо этого заняться традиционной русской забавой: новогодним поиском транзисторов.
Пожалуй, не буду строить из себя специалиста-электронщика и рекомендовать кому-то конкретный дизайн своих костылей, скажу лишь, что на каждую кнопку у меня приходится по одному полевому транзистору BSS138 и одному резистору (между истоком и затвором, чтобы статикой не побило), а питание каждого из двух каналов светодиодов измеряется при помощи делителя на двух 10k резисторах и сглаживающего фильтра из конденсатора и ещё одного резистора.
#esphome
Теперь часть красивая, программная. Иногда в интернете можно встретить ругань на скетчи Arduino, мол непрофессионально это всё. Что ж, не будем писать кривой-косой код в Arduino IDE. Вместо этого будем, как взрослые серьёзные люди, код вообще не писать, а вместо него пользоваться YAML-конфигурацией!
ESPHome — это такой инструмент, который на вход принимает очень компактное декларативное описание подключённой "периферии" и требуемые автоматизации (а в простейшем случае — просто выставляет "крутилочки" наружу). Потом вы передаёте этот YAML питоновской тулзе, которая сама генерирует требуемый код "прошивки" на C, скачивает тулчейн для требуемого микроконтроллера и заливает прошивку (а при последующем обновлении даже можно заливать прошивку "по воздуху"!). Как говорится, просто откиньтесь на спинку табуретки :)
... и вот уже esphome знает 1) какое у нас железо и, соответственно, какой компилятор качать, 2) к какой сети подключаться и 3) как представиться местному DHCP-серверу. Плюс прошивка будет печатать стандартные логи. Но правильнее будет создать отдельный файл
а в основной конфигурации (которую можно положить, например, в гит) указать
Каждый возможный параметр я описывать не буду, поскольку есть довольно удобная документация.
Теперь можно каким-нибудь способом установить питоновский пакет
Прошиваем (текстовый скринкаст):
#esphome
ESPHome — это такой инструмент, который на вход принимает очень компактное декларативное описание подключённой "периферии" и требуемые автоматизации (а в простейшем случае — просто выставляет "крутилочки" наружу). Потом вы передаёте этот YAML питоновской тулзе, которая сама генерирует требуемый код "прошивки" на C, скачивает тулчейн для требуемого микроконтроллера и заливает прошивку (а при последующем обновлении даже можно заливать прошивку "по воздуху"!). Как говорится, просто откиньтесь на спинку табуретки :)
esp32:
board: esp32-c3-devkitm-1
framework:
type: esp-idf
wifi:
domain: .lan
fast_connect: true
ssid: "mywifinetwork"
password: "RovOgsovhed9" # ... is not my password :)
esphome:
name: monitor-lamp
logger:
# тут пусто
... и вот уже esphome знает 1) какое у нас железо и, соответственно, какой компилятор качать, 2) к какой сети подключаться и 3) как представиться местному DHCP-серверу. Плюс прошивка будет печатать стандартные логи. Но правильнее будет создать отдельный файл
secrets.yamlwifi_ssid: "mywifinetwork"
wifi_password: "RovOgsovhed9"
а в основной конфигурации (которую можно положить, например, в гит) указать
wifi:
domain: .lan
fast_connect: true
ssid: !secret wifi_ssid
password: !secret wifi_password
Каждый возможный параметр я описывать не буду, поскольку есть довольно удобная документация.
Теперь можно каким-нибудь способом установить питоновский пакет
esphome (я для этих целей относительно недавно открыл для себя pipenv) и подключить плату по USB для первоначальной прошивки. Важно: в описании моей платы явно указано, что нужно питать плату либо от USB, либо "через пины", но не одновременно двумя способами.Прошиваем (текстовый скринкаст):
pipenv run esphome run backlight-lamp.yaml
#esphome
asciinema.org
ESPHome initial flashing
Recorded by atrosinenko
Итак, минимальная конфигурация готова. Поскольку, как я уже говорил, при подключении к лампе мне придётся отключить питание от USB, то и перепрошивать придётся, постоянно переключаясь туда-сюда. Поэтому сразу добавим OTA-обновления и "api" (без него, как я понял, не получится по сети прочитать лог работы прошивки). Ещё раз перешьём по кабелю:
Теперьпитание компьютера тьфу, то есть USB-кабель можно отключить и подключить микроконтроллер к лампе. Вроде, с моей схемой подключения не должно быть фатальных проблем при любой начальной конфигурации пинов, хоть на вход, хоть на выход. Впрочем, если бы проблемы могли возникать, наверное, всё равно могло бы сложиться состояние гонки в момент начальной инициализации (кажется, документация на взаимоисключающие переключатели говорит примерно о том же).
Итак, плата при подключении не сгорела, аппаратные кнопки на пульте также работают штатно. Попробуем снять показания АЦП: добавим в конфиг первое описание сенсора и перешьем:
Теперь в логах можно наблюдать изменение яркости:
Светодиоды в лампе подключены с общим анодом, а измеряем мы, соответственно, напряжение на катодах, поэтому максимальная яркость — это 0, а минимальная, получается, примерно 1.3V (кажется, с расчётом то ли делителя напряжения, то ли фильтра, я всё-таки налажал...).
#esphome
api:
ota:
- platform: esphome
port: 4321
password: !secret ota_password
Теперь
Итак, плата при подключении не сгорела, аппаратные кнопки на пульте также работают штатно. Попробуем снять показания АЦП: добавим в конфиг первое описание сенсора и перешьем:
sensor:
- platform: adc
pin: GPIO03
update_interval: 1s
attenuation: auto
id: adc_led_warm
name: "Warm LEDs output level"
Теперь в логах можно наблюдать изменение яркости:
pipenv run esphome logs backlight-lamp.yaml
Светодиоды в лампе подключены с общим анодом, а измеряем мы, соответственно, напряжение на катодах, поэтому максимальная яркость — это 0, а минимальная, получается, примерно 1.3V (кажется, с расчётом то ли делителя напряжения, то ли фильтра, я всё-таки налажал...).
#esphome
asciinema.org
ESPHome ADC readings
Recorded by atrosinenko
Осталось научиться работать с кнопками. Тут описание в ESPHome состоит из двух частей: компонента GPIO Output и самой Generic Output Button:
Вот только как на эту кнопку нажать? Если есть настроенный сервер Home Assistant, то добавить в него новое устройство и нажимать :) А если нет? Тогда просто добавляем на верхний уровень конфига
Остались мелочи: добавить в конфиг встроенный в ESP32 датчик температуры и распаянный на плате светодиод:
и пересчитывать в проценты значения измеренной яркости (обратите внимание на именованную ссылку
Если честно, пока всё работает не настолько гладко, как хотелось бы, и кое-что придётся ещё отладить, но уже "за кадром". Впрочем, думаю, мне удалось хотя бы немного продемонстрировать выразительность ESPHome.
#esphome
output:
- platform: gpio
pin: GPIO20
id: pin_button_on_off
button:
- platform: output
name: "Power on/off"
output: pin_button_on_off
duration: 100ms
Вот только как на эту кнопку нажать? Если есть настроенный сервер Home Assistant, то добавить в него новое устройство и нажимать :) А если нет? Тогда просто добавляем на верхний уровень конфига
web_server: с настройками по умолчанию, и можно заходить в браузере на http://backlight-lamp.lan/, вот так вот просто!Остались мелочи: добавить в конфиг встроенный в ESP32 датчик температуры и распаянный на плате светодиод:
sensor:
- platform: internal_temperature
name: "Internal temperature"
output:
- platform: gpio
pin:
number: GPIO08
inverted: true
id: pin_status_led
light:
- platform: binary
name: "Status LED"
output: pin_status_led
и пересчитывать в проценты значения измеренной яркости (обратите внимание на именованную ссылку
led_output_filters — это стандартная возможность в yaml):- platform: adc
# ...
name: "Warm LEDs output level"
unit_of_measurement: "%"
accuracy_decimals: 0
filters: &led_output_filters
- calibrate_linear:
- 0.0 -> 100.0
- 1.3 -> 0.0
- clamp:
min_value: 0.0
max_value: 100.0
- platform: adc
# ...
name: "Cold LEDs output level"
unit_of_measurement: "%"
accuracy_decimals: 0
filters: *led_output_filters
Если честно, пока всё работает не настолько гладко, как хотелось бы, и кое-что придётся ещё отладить, но уже "за кадром". Впрочем, думаю, мне удалось хотя бы немного продемонстрировать выразительность ESPHome.
#esphome
А вот и обещанный способ упростить себе установку питоновского софта (как пользователю) при помощи
Раньше, когда в документации какой-нибудь программы мне говорили, что нужно сделать
Пляски выглядели примерно так:
1) выбрать, где будет валяться установочный каталог этой конкретной программы
2) инициализировать там Python virtual environment:
3) активировать конкретный virtual environment в текущем терминале:
4) находясь в нужном терминале выполнить
5) радостно пользоваться установленным софтом
В итоге на первом шаге я вынужден задуматься, а какой каталог мне сегодня не так жалко замусорить. На третьем — в текущем сессии терминала появляется нестандартное глобальное состояние. Перепутал вкладку терминала на четвёртом шаге — и в лучшем случае получаешь ошибку, что для замусоривания системы не хватает прав. А почему не запускается-то?.. а, это я на пятом шаге опять терминал перепутал. В общем, весело. Бонусом, мне обычно тревожно удалять что-то, не глядя, так что каталог, созданный на первом шаге ещё напомнит о себе через пару месяцев необходимостью увлекательных размышлений "а тут у меня точно ничего полезного не лежало? точно ведь?.."
К счастью, есть более удобный способ: pipenv. Правда, в нём также придётся выбрать некий каталог в качестве "установочного", но лишь условно: в нём будет лежать лишь один короткий файл
pipenv.Раньше, когда в документации какой-нибудь программы мне говорили, что нужно сделать
pip install <чтототам>, а в репозитории убунты этот пакет отсутствовал или был устаревшей версии, я нехотя начинал пляски с бубном. Систему-то замусоривать не хочется, да и личный профиль пользователя — тоже (а вдруг завтра я решу ещё что-то поставить, а там окажутся какие-нибудь конфликты версий общих библиотек).Пляски выглядели примерно так:
1) выбрать, где будет валяться установочный каталог этой конкретной программы
2) инициализировать там Python virtual environment:
virtualenv ~/installdir3) активировать конкретный virtual environment в текущем терминале:
source ~/installdir/bin/activate4) находясь в нужном терминале выполнить
pip install <чтототам>5) радостно пользоваться установленным софтом
В итоге на первом шаге я вынужден задуматься, а какой каталог мне сегодня не так жалко замусорить. На третьем — в текущем сессии терминала появляется нестандартное глобальное состояние. Перепутал вкладку терминала на четвёртом шаге — и в лучшем случае получаешь ошибку, что для замусоривания системы не хватает прав. А почему не запускается-то?.. а, это я на пятом шаге опять терминал перепутал. В общем, весело. Бонусом, мне обычно тревожно удалять что-то, не глядя, так что каталог, созданный на первом шаге ещё напомнит о себе через пару месяцев необходимостью увлекательных размышлений "а тут у меня точно ничего полезного не лежало? точно ведь?.."
К счастью, есть более удобный способ: pipenv. Правда, в нём также придётся выбрать некий каталог в качестве "установочного", но лишь условно: в нём будет лежать лишь один короткий файл
Pipfile с полностью человекочитаемым списком установленного софта и ещё один более длинный и полностью автосгенерированный Pipenv.lock. В остальном каталог полностью в моём распоряжении, и в него можно, например, положить файлы, с которыми я работаю при помощи этой программы. В этом варианте тоже будет инициализирован virtualenv, но он будет лежать где-то во временных файлах — а когда я вижу, что некий каталог полностью "вторичен" по отношению к лежащему отдельно "первичному" описанию (Pipenv + Pipenv.lock), то и удалять его обычно не так страшно :)GitHub
GitHub - pypa/pipenv: Python Development Workflow for Humans.
Python Development Workflow for Humans. Contribute to pypa/pipenv development by creating an account on GitHub.
Вот, как теперь выглядит мой типичный способ использования питоновского софта. Допустим, я хочу начать работать с esphome. Мне в любом случае придётся создать некий "каталог проекта", куда я буду складывать yaml-конфигурации устройств. Возможно, этот каталог даже будет положен в гит.
На самом деле, изменить глобальное состояние всё-таки придётся, но лишь единожды: потребуется установить сам , если его ещё нет. В убунте можно поставить системный пакет . Или можно воспользоваться официальной рекомендацией: .
Итак допустим, что я решил складывать все наработки в
Как видим, в каталоге появилось два файла:
Теперь, находясь всё в том же каталоге, я могу скомандовать
Обычно установка
Разумеется, каждый отдельныйсвести задачу к предыдущей всё-таки запустить shell с настроенным
pipenvpipenvpip install --user pipenvИтак допустим, что я решил складывать все наработки в
~/esphome. Но мне же не жалко, если в этом каталоге дополнительно будет валяться всего-то два текстовых файла, правда? Поэтому я перехожу в ~/esphome и выполняю там команду pipenv install:$ mkdir esphome
$ cd esphome
$ pipenv install
$ ls
Pipfile Pipfile.lock
Как видим, в каталоге появилось два файла:
Pipfile и Pipfile.lock. Первый является кратким описанием "что требуется получить", второй — машиночитаемым json-ом, перечисляющим конкретные версии всех пакетов (включая зависимости) для воспроизводимости окружения при переустановке. Не беспокойтесь, lock-файл многословен, но нам туда лезть не потребуется. И да, официальная документация рекомендует коммитить в гит оба этих файла. До начала установки конкретных пакетов Pipfile выглядит так:[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
[dev-packages]
[requires]
python_version = "3.13"
Теперь, находясь всё в том же каталоге, я могу скомандовать
pipenv install esphome, и через некоторое время esphome будет установлен, в Pipfile в секции [packages] добавится единственная строчка esphome = "*" (мы ведь попросили поставить esphome неважно какой версии), а вот Pipfile.lock разрастётся с 454 байт аж до 106 килобайт — но нам-то что, не нам же его редактировать.Обычно установка
esphome при помощи pip install добавила бы нам в PATH команду esphome. Но поскольку у нас может быть много разных окружений, каждое со своим набором пакетов, то для использования esphome через pipenv нужно 1) перейти в "установочный" каталог (или куда-нибудь ещё ниже по дереву) и 2) приписать перед требуемой командой esphome run — всё!~/esphome$ pipenv run esphome --version
Version: 2026.1.4
~/esphome$ cd subdir
~/esphome/subdir$ pipenv run esphome --version
Version: 2026.1.4
Разумеется, каждый отдельный
Pipfile не ограничен единственным "пользовательским" пакетом, и можно установить ещё что-нибудь при помощи pipenv install <имя>. Также есть команды, чтобы обновлять пакеты, удалять пакеты или, например, запустив pipenv shell, можно virtualenv, как привыкли — но тут уже лучше читать документацию.👍1
С точки зрения использования SD-карт сначала я был наивным пользователем цифровой "мыльницы", потом чуть менее наивным пользователем недорогих смартфонов с недостаточным объёмом встроенного накопителя. В те годы в основном я выбирал карты памяти по "классу скорости", ну и по отзывам.
Когда же я начал достаточно активно возиться с одноплатными компьютерами (да-да, тот самый #маленький_но_гордый_сервер), то с удивлением узнал, что SD-карты гораздо интереснее, чем казалось. Конечно понятно, что поддельная карточка или USB-флешка может обманывать не то, что со скоростью, а даже с объёмом — речь сейчас не о них. Оказалось, что даже заявляемые производителем характеристики сейчас иногда, если можно так выразиться, двумерные.
По одной "оси" уже давно совершенствуется скорость чтения и записи. Иногда — вместе с необходимостью аппаратной поддержки на стороне устройства, вплоть до добавления новых контактов в разъём. Грубо говоря, если вы в 2010 году купили цифровой фотоаппарат, умеющий писать видео в достаточно высоком разрешении, а вставленная в него карточка не успевает стабильно "поглощать" столько данных в единицу времени, сколько "отправляет" камера — то ой. Но скорость скорости рознь — важно, в каких конкретно условиях её требуется достигать.
В некоторый момент важным "потребителем" SD-карт стали смартфоны. И если для фото- и видеокамер была важна скорость линейного доступа, то у смартфонов аппетиты разнообразнее: теперь на карту можно установить тяжёлое приложение. Или, может, какое-то приложение запишет на карту большие по объёму данные, но будет читать их в случайном порядке. Поэтому стандартизовали ещё одну "ось" измерения "хорошести" карты памяти: Application Performance Class, характеризующий скорость произвольного доступа маленькими блоками: A1 и более продвинутый A2. А может не заявляться никакого A-класса: по C- или V-рейтингу карта вполне шустрая, а в качестве "системного диска" одноплатного компьютера — как повезёт.
Когда же я начал достаточно активно возиться с одноплатными компьютерами (да-да, тот самый #маленький_но_гордый_сервер), то с удивлением узнал, что SD-карты гораздо интереснее, чем казалось. Конечно понятно, что поддельная карточка или USB-флешка может обманывать не то, что со скоростью, а даже с объёмом — речь сейчас не о них. Оказалось, что даже заявляемые производителем характеристики сейчас иногда, если можно так выразиться, двумерные.
По одной "оси" уже давно совершенствуется скорость чтения и записи. Иногда — вместе с необходимостью аппаратной поддержки на стороне устройства, вплоть до добавления новых контактов в разъём. Грубо говоря, если вы в 2010 году купили цифровой фотоаппарат, умеющий писать видео в достаточно высоком разрешении, а вставленная в него карточка не успевает стабильно "поглощать" столько данных в единицу времени, сколько "отправляет" камера — то ой. Но скорость скорости рознь — важно, в каких конкретно условиях её требуется достигать.
В некоторый момент важным "потребителем" SD-карт стали смартфоны. И если для фото- и видеокамер была важна скорость линейного доступа, то у смартфонов аппетиты разнообразнее: теперь на карту можно установить тяжёлое приложение. Или, может, какое-то приложение запишет на карту большие по объёму данные, но будет читать их в случайном порядке. Поэтому стандартизовали ещё одну "ось" измерения "хорошести" карты памяти: Application Performance Class, характеризующий скорость произвольного доступа маленькими блоками: A1 и более продвинутый A2. А может не заявляться никакого A-класса: по C- или V-рейтингу карта вполне шустрая, а в качестве "системного диска" одноплатного компьютера — как повезёт.
❤1