blog

Source for my blog
git clone git://git.konyahin.xyz/blog
Log | Files | Refs

commit 54c093fc6f7a6d831d77c5000288fc2a4b840457
Author: Anton Konyahin <me@konyahin.xyz>
Date:   Thu, 30 Mar 2023 13:29:06 +0300

Initial commit

Diffstat:
A.gitignore | 3+++
Aarchetypes/default.md | 6++++++
Aconfig.toml | 5+++++
Acontent/_index.md | 10++++++++++
Acontent/about.md | 14++++++++++++++
Acontent/blog/_index.md | 5+++++
Acontent/blog/git-server.md | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acontent/blog/pomodoro-attiny45.md | 385+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astatic/img/pomodoro-cat.png | 0
Astatic/img/pomodoro-kicad.png | 0
Astatic/img/pomodoro-model.png | 0
Astatic/img/pomodoro-result.png | 0
Astatic/video/IMG_6273.MOV | 0
Astatic/video/IMG_6273.mp4 | 0
Athemes/lugo/README.md | 21+++++++++++++++++++++
Athemes/lugo/archetypes/default.md | 6++++++
Athemes/lugo/config.toml | 8++++++++
Athemes/lugo/layouts/_default/baseof.html | 39+++++++++++++++++++++++++++++++++++++++
Athemes/lugo/layouts/_default/list.html | 6++++++
Athemes/lugo/layouts/_default/rss.xml | 26++++++++++++++++++++++++++
Athemes/lugo/layouts/_default/single.html | 3+++
Athemes/lugo/layouts/blog/list.html | 17+++++++++++++++++
Athemes/lugo/layouts/partials/nav.html | 8++++++++
Athemes/lugo/layouts/partials/nextprev.html | 10++++++++++
Athemes/lugo/layouts/partials/taglist.html | 13+++++++++++++
Athemes/lugo/layouts/shortcodes/hidvid.html | 10++++++++++
Athemes/lugo/layouts/shortcodes/img.html | 20++++++++++++++++++++
Athemes/lugo/layouts/shortcodes/rawhtml.html | 2++
Athemes/lugo/layouts/shortcodes/tagcloud.html | 3+++
Athemes/lugo/layouts/shortcodes/vid.html | 6++++++
Athemes/lugo/static/rss.svg | 1+
Athemes/lugo/static/style.css | 128+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
32 files changed, 990 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +.hugo_build.lock + +public/ diff --git a/archetypes/default.md b/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/config.toml b/config.toml @@ -0,0 +1,5 @@ +baseURL = 'https://konyahin.xyz' +languageCode = 'ru-ru' +title = 'konyahin' +theme = 'lugo' +markup.highlight.noClasses = false diff --git a/content/_index.md b/content/_index.md @@ -0,0 +1,10 @@ +--- +title: "konyahin" +date: 2022-10-01T19:57:44+03:00 +--- + +Привет. Это мой персональный сайт, на котором я пишу всякие [посты в блог](/blog). [RSS link](/index.xml). + +Об используемых на сайте технологиях можно [прочитать здесь](/about). + +Исходный код некоторых проектов, которые я сделал, можно [найти тут](https://git.konyahin.xyz/). diff --git a/content/about.md b/content/about.md @@ -0,0 +1,14 @@ +--- +title: "О сайте" +date: 2022-10-01T17:51:16+03:00 +--- + +Этот сайт сделан с использованием следующих технологий и сервисов: + +- [OpenBSD](http://openbsd.org/) [(реферальная ссылка)](https://www.youtube.com/watch?v=xvFZjo5PgG0) +- [Epik](https://epik.com) +- [Vultr](https://www.vultr.com/) [(реферальная ссылка)](https://www.vultr.com/?ref=8362553) +- [Hugo](https://gohugo.io/) +- [Luke's Hugo Theme](https://github.com/LukeSmithxyz/lugo) + + diff --git a/content/blog/_index.md b/content/blog/_index.md @@ -0,0 +1,5 @@ +--- +title: "Блог" +date: 2022-10-01T17:49:16+03:00 +--- + diff --git a/content/blog/git-server.md b/content/blog/git-server.md @@ -0,0 +1,235 @@ +--- +title: "Поднимаем Git сервер на OpenBSD" +date: 2023-02-15T19:38:25+03:00 +--- + +Если вам хочется иметь запасное (или даже основное) хранилище для ваших +программных проектов, и вы не очень доверяете Github и его собратьям, то ваша +дорога лежит в сторону selfhosted git сервера. Давайте поднимем его, используя +машину на OpenBSD, и сверху накрутим простой web ui, для просмотра репозиториев +через браузер. + +Тему базовой настройки сервера и сертификата я оставлю за скобками, скажу +только что и httpd, и acme-client содержатся в базовой системе, работают просто +прекрасно и обладают хорошей документацией. + + +## Gitdaemon + +Помимо базовой системы нам потребуются ещё два пакета, +сам git и простой генератор статического сайта из git +репозиториев - stagit. Я выбрал именно его потому что мне +нравилось пользоваться решениями на его основе, например +[вот этим сайтом](https://git.suckless.org/). + +```sh +pkg_add git stagit +``` + +Git сервер будет работать из под своего собственного пользователя, +так что давайте его заведем. + +В OpenBSD принято добавлять к сервисным учеткам префикс `_`, то есть +наш пользователь должен называться `_git`. Однако, по эстетическим +причинам, я префикс не использую. Я, конечно же, не прав. + +Не делайте так. + +```sh +# -m приведет к созданию домашней директории для пользователя +user add -m git +``` + +Чтобы мы могли лить изменения со своего локального компьютера +на сервер по ssh, давайте добавим на него наш публичный ключ. + +```sh +cd /home/git +mkdir -p .ssh && chmod 700 .ssh +touch .ssh/authorized_keys && chmod 600 .ssh/authorized_keys + +cat > .ssh/authorized_keys << "END" +no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 *** you@example.com +END + +chown -R git:git .ssh +``` + +Перед ключом мы указали ряд настроек, которые ограничивают возможности +ssh соединения с этим ключом. Чтобы добавить ещё безопасности, стоит +урезать возможности нашего служебного пользователя. Для этого +мы поменяем ему shell на git-shell, позволяющий выполнять +только операции над git репозиториями. + +```sh +chsh -s /usr/local/bin/git-shell git +``` + +Всё что нам осталось, это создать директорию для хранения наших +репозиториев и натравить на неё gitdaemon. + +```sh +mkdir -p /git/ +chown -R git:git /git + +rcctl enable gitdaemon +rcctl set gitdaemon flags --export-all --base-path="/git" +rcctl set gitdaemon user git +rcctl start gitdaemon +``` + +`rcctl` это системная утилита, которая используется для +конфигурирования и настройки сервисов и демонов в OpenBSD. +В данном случае мы указываем с какими аргументами командной +строки сервис должен запускаться и под каким пользователем +работать. + +Если вы хотите увидеть, с какими аргументами демон будет +работать прямо сейчас, наберите `rcctl get gitdaemon`. + +Для добавления нового репозитория я использую небольшой скрипт, +который приведу полностью. + +```sh +#!/usr/bin/env sh + +set -e + +REPO=$1 + +if [ -z $REPO ] +then + echo "You should specify repository name" + exit 1 +fi + +mkdir -p /git/$REPO +cd /git/$REPO +git init --bare + +echo "Owner name" > owner +echo "git://git.example.com/$REPO" > url +${EDITOR:-vi} description + +chown -R git:git /git/$REPO +``` + +В качестве аргумента скрипт принимает название репозитория, которое будет так +же использоваться в качестве названия директории для него. Помимо директории мы +заполняем владельца и общедоступный url для скачивания на просмотр. Для +заполнения описания мы открываем текстовый редактор. Все эти вещи будут +использоваться stagit для генерации сайта. + +На этом моменте стоит попробовать создать новый репозиторий и закачать в него +какие-либо изменения, чтобы убедиться что всё работает правильно. Для +скачивания репозитория по ssh воспользуйтесь адресом вида +`ssh://git@example.com:/git/repo-name`. + +## Stagit + +Теперь, когда git daemon исправно работает, самое время прикрутить хоть +какой-то интерфейс, чтобы любопытствующие могли покопаться в ваших +проектах не скачивая их себе. + +Для начала давайте отредактируем `/etc/httpd.conf` и добавим туда новый +сервер для нашей веб морды. + +``` +server "git.example.com" { + listen on * tls port 443 + root "/htdocs/git" + tls { + certificate "/etc/ssl/example.com.fullchain.pem" + key "/etc/ssl/private/example.com.key" + } + + location "./well-known/acme-challenge/*" { + root "/acme" + request strip 2 + } + + location "*/style.css" { + request rewrite "/style.css" + } + + location "*/logo.png" { + request rewrite "/logo.png" + } + + location "*/favicon.png" { + request rewrite "/favicon.png" + } +} +``` + +Я исхожу из того, что вы используете https на своем сайте, +если это не так - конфиг станет ещё проще. + +Последние три блока нужны исключительно по той причине, что stagit генерирует +относительные ссылки на логотип, стили и favicon сайта. Чтобы не подкладывать +их в каждый репозиторий, я просто делаю редирект с любого урла, оканчивающегося +запросом одного из этих файлов, на корень, где они, собственно и лежат. + +Данный конфиг будет раздавать пользователям статику из директории +`/var/www/htdocs/git`, которую нам надо создать и передать во владение +пользователю git, так как контент в ней будет генерироваться из него +(но об этом чуть позже). + +``` +mkdir -p /var/www/htdocs/git +chown -R git:git /var/www/htdocs/git +``` + +Чтобы проверить что мы правильно написали конфиг, стоит воспользоваться +командой `httpd -n`, которая заставляет сервер распарсить и проверить +конфиги, но ничего не запускать. Если всё ок, то давайте перезапустим httpd. + +``` +rcctl restart httpd +``` + +Делать генерацию сайта руками после каждого обновления репозиториев -- +занятие недостойное человека, поэтому давайте поручим его компьютеру. + +Для этого нам нужно создать скрипт, выполняющийся каждый раз после того, +как сервер получил какие-либо изменения. Нам нужно создать файл по адресу +`/usr/local/share/git-core/templates/hooks/post-receive` и записать туда +приведенный ниже скрипт. + +``` +#!/usr/bin/env sh + +set -e + +# удаляем индекс, если он уже есть +rm -rf /var/www/htdocs/git/index.html + +# обходим все имеющиеся у нас репозитории +cd /git/ +for repo in */ ; do + mkdir -p /var/www/htdocs/git/$repo + cd /var/www/htdocs/git/$repo + stagit /git/$repo +done + +# создаем новый индекс файл +cd /var/www/htdocs/git/ +stagit-index /git/* >> index.html +``` + +Не забудьте сделать этот хук исполняемым. + +``` +chmod +x /usr/local/share/git-core/templates/hooks/post-receive +``` + +Теперь всё должно работать. Попробуйте запушить что-нибудь в свой +репозиторий и зайти на главную страницу веб интерфейса. + +Интерфейс очень простой и полностью статичный, речь не идет +ни о каких пулл-реквестах или поиске по коду, но если вам +это и не нужно, то stagit должен вам понравиться. + +Внешний вид можно настроить с помощью css файла. Погружаться +в эту тему я не буду, если хотите, можете посмотреть на +мой собственный [css файл](https://git.konyahin.xyz/style.css). diff --git a/content/blog/pomodoro-attiny45.md b/content/blog/pomodoro-attiny45.md @@ -0,0 +1,385 @@ +--- +title: "Помодоро-таймер на Attiny45" +date: 2022-10-16:15:34+03:00 +--- + +Коробка с ардуино и кучкой электронных компонентов стояла у меня в шкафу уже +несколько лет. Я купил её в районе 2017-го года, побаловался с Arduino IDE, +потом с программатором, приехавших ко мне с aliexpress, помигал диодами и +успокоился. Интерес иссяк, придумать проект, который вдохновил бы меня на +действия, мне так и не удалось. Я пережил несколько переездов, сменил город, +переехал ещё раз, каждый раз с упорством таская за собой оранжевую коробку, с +надписью Amperka на боку, и, наконец-то, спустя пять лет, понял что таскал её +не зря. Что мне снова хочется что-то делать. И у меня даже была идея - помидоро +таймер. + +Мой список требований к прибору выглядел так: + +- включать рабочий режим по нажатию кнопки (красный диод) +- через 25 минут сообщать о том, что работа кончилась +- переключаться в режим отдыха (зеленый диод) +- через пять минут сообщать, что отдых тоже пора сворачивать +- засыпать. + +{{<img src="/img/pomodoro-model.png" caption="Примеряем компоненты на площадке">}} + +# Bill of materials + +{{<img src="/img/pomodoro-kicad.png" caption="Вот так выглядела получившаяся в KiCad схема">}} + +Так как проект достаточно простой, то и компонентов для него было нужно не так +много. Большая их часть встречается в любом базовом наборе для начинающего, +докупать пришлось только гнездо для батарейки. Я использовал: + +- два диода, красный и зеленый, они будут показывать, что идет работа/отдых +- кнопка без фиксации, единственный инструмент ввода нашего устройства +- пьезо-динамик, нужен чтобы издавать звуки извещая о смене режима +- батарейный отсек под 3V батарейку (у меня это CR2032) +- attiny45, самый простой микроконтроллер, который был у меня дома +- конденсатор на 100 нФ, блокировочный по питанию, чтобы ток был стабильнее и +его не +затрагивали переключения режимов +- два резистора на 10 кОм, использовались как подтягивающие для кнопки +и ножки сброса +- два резистора на 220 Ом, чтобы ограничить ток, идущий через диоды +- две [макетные платы](https://amperka.ru/product/troyka-perfboard), к которым +я всё это припаял +- ну и немного проводов, чтобы всё припаянное соединить. + +# Пройдемся по коду + +В `main.c` файле у нас содержится общий код проекта, осуществляющий оркестрацию +работы устройства. Например, там хранится текущий режим работы, в виде `enum` и +переменной для его хранения: + +```c +enum status_e +{ + ON, + WORK, + REST +}; + +// int8_t чтобы сэкономить пару спичек на хранении значения +int8_t status = ON; +``` + +Для перехода из статуса в статус написано три функции, +выполняющие всю нужную работу. Так как они более-менее +однотипные, то я покажу только одну. + +```c +void +status_to_rest() +{ + status = REST; + // подаем 1 на ножку с зеленым диодом, и 0 на ножку с красным + PORTB &= ~(1 << LED_RED); + PORTB |= (1 << LED_GREEN); + // играем мелодию (об этом ниже) + play_melody(&rest_melody); + // вызываем переключение в другой статус через REST_MIN минут (опять же, подробности ниже) + set_timer_callback(REST_MIN, &status_to_on); +} +``` + +`PORTB` это специальный регистр, отвечающий за ножки, относящиеся к порту B. +Так как Attiny45 это простой микроконтроллер и ножек у него мало, то все они +относятся к порту B. Задавая значение того или иного бита в этом регистре, мы +можем подавать или убирать напряжение с ножек микроконтроллера. + +Сама конструкция может выглядеть загадочно, но там работает обычная битовая +логика. `(1 << LED_GREEN)` дает нам число вида 00001000, с единичкой на том +месте, которое соответствует пину, связанному с зеленым диодом. Соответственно +выражение `PORTB |= (1 << LED_GREEN)` устанавливает в единичку этот же самый +бит (так как мы выполняем побитовое ИЛИ), а `PORTB &= ~(1 << LED_GREEN)` +наоборот, зануляет его (так как мы выполняем побитовое И с инвертированным +значением, таким в котором на нужном месте, и только на нем, стоит нолик). + +В самой функции `main` мы сначала настраиваем +контроллер под наши нужды: + +```c +// указываем что обе эти ножки работают на вывод +DDRB |= (1 << LED_GREEN); +DDRB |= (1 << LED_RED); + +// указываем какой именно режим сна нам нужен и разрешаем его использование +set_sleep_mode(SLEEP_MODE_PWR_DOWN); +sleep_enable(); +// включаем обработку прерываний +sei(); + +// настраиваем наши прерывания, инициализируем код связанный с музыкой и таймером +init_interrupt(switch_status); +init_music(); +init_timer(); +``` + +`DDRB` это ещё один специальный регистр. Он также относится к порту B, но +регулирует не состояние, а режим работы ножки. Ножка может работать как выход, +то есть мы можем сами подавать на неё ток или убирать его, а может как вход. В +последнем случае мы хотим проверять есть ли напряжение на ножке и, если есть, +то сколько. Эти две ножки должны управлять диодами, так что мы включаем для них +режим вывода. + +Теперь, когда все настроено, мы устраиваем маленький стартовый концерт: + +```c +// зажигаем все диоды +PORTB |= (1 << LED_GREEN); +PORTB |= (1 << LED_RED); +// играем стартовую мелодию +play_melody(&start_melody); +// по её окончании все диоды гасим +PORTB &= ~(1 << LED_GREEN); +PORTB &= ~(1 << LED_RED); +``` + +Всё что нам осталось, это зациклить работу нашей программы, заодно проверяя, не +пора ли увести микроконтроллер в режим сна: + +```c +while (true) { + if (sleep) + { + sleep = false; + sleep_cpu(); + } +} +``` + +## Что будет, если нажать на кнопку + +Чтобы упростить код нашей программы, мы не будем проверять нажата ли кнопка на +каждом цикле. Микроконтроллер может сам сообщать нам, если её состояние +меняется. Происходит это с помощью механизма прерываний. Чуть выше мы +использовали функцию `init_interrupt`, чтобы повесить на нажатие кнопки смену +режим работы устройства. Давайте посмотрим на её код. + +```c +void +init_interrupt(void (*on_press) (void)) +{ + // сохраняем коллбэк функцию, чтобы вызвать её позже + callback = on_press; + + // ножку, к которой привязана кнопка, переключаем в режим ввода + DDRB &= ~(1 << PB2); + // включаем внешние прерывания + GIMSK |= (1 << INT0); +} +``` + +Режим работы микроконтроллера можно настраивать меняя состояние тех или иных +регистров. Мы уже работали с ножками, меняя DDRB и PORTB, теперь же мы +воспользуемся новым регистром - GIMSK. В документации на Attiny45 он гордо +именуется `General Interrupt Mask Register` и выставляя на нем INT0 в единичку +мы включаем внешние прерывания. Теперь изменение напряжения на ножке PB2 будет +приводить к срабатыванию прерывания, которое остановит нормальное выполнение +программы и передаст управление сециальному куску кода. Вот этому: + +```c +ISR (INT0_vect) +{ + if (PINB & (1 << PB2)) + return; + _delay_ms(30); + if (PINB & (1 << PB2)) + return; + (*callback)(); +} +``` + +`ISR` это специальный макрос, который позволяет нам определять функции для +обработки прерываний. В скобках после него указывается название прерывания, в +данном случае `INT0_vect`. Далее мы два раза, с перерывом в 30мс, проверяем +состояние кнопки. Делается это чтобы избежать ложных срабатываний при дребезге +контактов. Между контактами кнопки могут проскакивать отдельные электрические +сигналы, особенно если мы начинаем её нажимать или отпускать, приближая +контакты друг к другу. Два замера через 30мс дают нам большую уверенность, в +том, что прерывание сработало на полноценное нажатие кнопки. После этого мы +вызываем коллбэк функцию, которая меняет режим работы устройства и проигрывает +какую-нибудь мелодию. + +## Издаем звуки + +Чтобы проиграть мелодию мы будем использоваь пьезодинамик. Внутри него +находится специальный кристал, который меняет свой размер при изменении +приложенного к нему напряжения. Если менять напряжение достаточно часто, то +кристалл будет своим движением производить звук. Не особо музыкальный, но для +привлечения внимания к прибору нам хватит и такого. + +Звук это волна, но так как наш микроконтроллер работает по цифровым, а не по +аналоговым принципам, то просто так подать волну на пьезодинамик мы не можем. +Мы будем имитировать её с помощью PWM, широтно-импульсной модуляции. Суть её +состоит в том, что вместо того, чтобы подавать на выход 0.5 мы будем 50% времени +держать на выходе 1 и 50% времени 0. Если делать это достаточно быстро, то для +внешнего наблюдателя это будет выглядеть как работа в половину мощности. + +```c +void +init_music(void) +{ + // ножка с пьезодинамиком работает на выход и, по началу, сигнал на неё мы не даем + DDRB |= (1 << SOUND); + PORTB &= ~(1 << SOUND); + + TCCR0A |= (1 << WGM01); + TCCR0A |= (1 << COM0A0); +} +``` + +`TCCR0A` это ещё один регистр нашего микроконтроллера, отвечающий за настройки +его внутреннего таймера. Устанавливая в единичку бит `WGM01` мы говорим, что +работать он должен в режиме CTC (Clear Timer on Compare Match). При каждом +срабатывании таймера его значение будет увеличиваться на единицу и сравниваться +со значением, лежащим в регистре `OCR0A`. Когда они окажутся равны, то +состояние ножки с динамиком переключится, а когда значение переполнится и +отсчет начнется с 0, то состояние ножки поменяется ещё раз. Таким образом мы +можем управлять тем, какой процент времени на ножку подается напряжение. Это +значение называется Duty Cycle или коэффициент заполнения. + +Мелодия для проигрывания у нас хранится в простенькой структуре, содержащей +число нот и сами эти ноты. + +```c +typedef struct melody_s { + uint8_t length; + uint8_t tones[]; +} melody_t; +``` + +Ноты для наших мелодий зашиты в код программы, вместе с временем в мс, в продолжении которого будет звучать каждая нота. + +```c +#define TONE_A 42 +#define TONE_AS 39 +#define TONE_B 37 +#define TONE_C 71 +#define TONE_CS 67 +#define TONE_D 63 +#define TONE_DS 59 +#define TONE_E 56 +#define TONE_F 53 +#define TONE_FS 50 +#define TONE_G 47 +#define TONE_GS 44 + +#define DELAY 250 +``` + +Значения для нот высчитывались исходя из частоты микроконтроллера, но статью с +формулами для расчета я потерял. Так что просто поверьте в эти значения. Сами +мелодии объявлены в `main.c`, вот пример одной из них: + +```c +melody_t start_melody = { + .length = 5, + .tones = { + TONE_A, + TONE_D, + TONE_G, + TONE_E, + TONE_B + } +}; +``` + +Логика функции, которая играет музыку, крайне проста: + +```c +void +play_melody(const melody_t *melody) +{ + // запускаем таймер, выставляя ему частоту срабатывания + TCCR0B |= (1 << CS01); + + for (int8_t i = 0; i < melody->length; i++) + { + // выставляем duty cycle, равный значению текущей ноты + OCR0A = melody->tones[i]; + // пока мы здесь ждем 250мс, нота продолжает звучать + _delay_ms(DELAY); + } + + // выключаем таймер + TCCR0B &= ~(1 << CS01); +} +``` + +Вот и всё, что нужно, чтобы издавать звуки. Осталось только определить, когда +именно их надо издавать. + +## Ещё один таймер + +Отсчитывать время работы (или отдыха) у нас будет другой таймер. Настроен он таким образом, чтобы срабатывать приблизительно раз в секунду и, при каждом своем срабатывании, вызывать прерывание. + +```c +void +init_timer(void) +{ + // отключаем прерывания, чтобы они не помешали нам полностью настроить таймер + cli(); + // на каждом шаге таймер будет сранивать свое значени вот с этим, как было с `OCR0A` у таймера звука + OCR1A = 244; + // указываем что этот таймер тоже работает в режиме CTC + TIMSK |= (1 << OCIE1A); + // выставляем ещё несколько бит для настройки таймера + // CTC1 запускает таймер + // CS13 CS12 CS10 - настраиваем то, с какой периодичностью, + // относительного частоты процессора, он будет срабатывать, + // в данном случае это CK/4096 + TCCR1 |= (1 << CTC1) | (1 << CS13) | (1 << CS12) | (1 << CS10); + // снова включаем прерывания + sei(); +} +``` + +Если мы умножим 4096 на 244, то получим 999424. Так как наш микроконтроллер +должен работать с частотой 1000000 операций в секунду, то мы можем ожидать, что +таймер будет вызывать прерывание где-то раз в эту самую секунду. Но Attiny не +очень точны в плане частоты. У микроконтроллеров из разных партий частота может +немного отличаться, к тому же на неё может влиять температура и поданое +напряжение. Чтобы приблизить ожидаемое время срабатывания к реальному, я прогонял +тесты и выяснил, что за одну минуту таймер срабатывает в среднем 57 раз. Это +значение записано у меня в `SEC` и если вы решите использовать мой код со своим +микроконтроллером, то скорее всего вам нужно будет его поменять. Для устройств, +которые требуют более точного измерения времени, стоит воспользоваться внешними +часами реального времени, например `DS1307`. + +```c +static uint8_t minutes = 0; +static uint8_t seconds = 0; +static void (*callback) (void) = NULL; + +ISR (TIMER1_COMPA_vect) +{ + if (seconds > 0) + { + --seconds; + return; + } + else + { + // тот самый SEC равный 57, а не 60 + seconds = SEC; + --minutes; + } + if (minutes == 0 && callback != NULL) + callback(); +} +``` + +Код прерывания ещё проще. Отсчитываем секунды и минуты, заданные кодом смены +режим работы, и, когда дошли до нуля, дергаем указанный при старте коллбэк. + +Когда очередной отрезок отдыха закончится, мы сыграем завершающую мелодию и +уведем микроконтроллер в сон. Так он будет меньше есть батарейку между сеансами +работы. Нажатие на кнопку выведет его из сна и весь процесс повторится снова. + +Вот такой вот получился проект. С исходным кодом целиком можно [познакомиться здесь](https://git.konyahin.xyz/pomodoro-avr/log.html). Несмотря на его простоту, в процессе я успел познакомиться с многими функциями Attiny, такими как таймеры, прерывания и режимы сна. Ну а главное, я получил нечто физическое, как результат своей работы. И это очень приятное чувство. + +{{< rawhtml >}} +<video controls src="/video/IMG_6273.mp4" height="400px" /> +{{< /rawhtml >}} diff --git a/static/img/pomodoro-cat.png b/static/img/pomodoro-cat.png Binary files differ. diff --git a/static/img/pomodoro-kicad.png b/static/img/pomodoro-kicad.png Binary files differ. diff --git a/static/img/pomodoro-model.png b/static/img/pomodoro-model.png Binary files differ. diff --git a/static/img/pomodoro-result.png b/static/img/pomodoro-result.png Binary files differ. diff --git a/static/video/IMG_6273.MOV b/static/video/IMG_6273.MOV Binary files differ. diff --git a/static/video/IMG_6273.mp4 b/static/video/IMG_6273.mp4 Binary files differ. diff --git a/themes/lugo/README.md b/themes/lugo/README.md @@ -0,0 +1,21 @@ +# Luke's Hugo Theme + +A simple Hugo theme I plan on using for my personal website, [Based.Cooking](https://based.cooking) and [LandChad.net](https://landchad.net). + +## get started + +```sh +hugo new site new-site +cd new-site +git clone https://github.com/lukesmithxyz/lugo themes/lugo +echo "theme = 'lugo'" >> config.toml +cp themes/lugo/static/style.css static/ +``` + +## stuff + +- Makes one RSS feed for the entire site at `/index.xml` +- Stylesheet is in `/style.css` and includes some important stuff for partials. +- If a post is tagged, links to the tags are placed at the bottom of the post. +- `nextprev.html` adds links to the Next and Previous articles to the bottom of a page. +- `taglist.html` links all tags an article is tagged to for related content. diff --git a/themes/lugo/archetypes/default.md b/themes/lugo/archetypes/default.md @@ -0,0 +1,6 @@ +--- +title: "{{ replace .Name "-" " " | title }}" +date: {{ .Date }} +draft: true +--- + diff --git a/themes/lugo/config.toml b/themes/lugo/config.toml @@ -0,0 +1,8 @@ +title = "Website Name" +baseURL = 'https://example.org' +languageCode = 'en-us' + +[params] + # "relatedtext" is the text that appears above the tag list at the bottom of pages. + #relatedtext = "Related:" + favicon = "/favicon.ico" diff --git a/themes/lugo/layouts/_default/baseof.html b/themes/lugo/layouts/_default/baseof.html @@ -0,0 +1,39 @@ +<!DOCTYPE html> +<html lang="{{ .Site.Language }}"> +<head> + <title>{{ if not .IsHome }}{{ .Title | title }} | {{ end }}{{ .Site.Title }}</title> + <link rel="canonical" href="{{ .Site.BaseURL }}"> + <link rel='alternate' type='application/rss+xml' title="{{ .Site.Title }} RSS" href='/index.xml'> + <link rel='stylesheet' type='text/css' href='/style.css'> + {{ with .Site.Params.favicon }}<link rel="icon" href="{{ . }}"> + {{ end -}} + <meta name="description" content="{{ with .Params.description }}{{ . }}{{ else }}{{ .Summary }}{{ end }}"> + {{ if isset .Params "tags" }}<meta name="keywords" content="{{ with .Params.tags }}{{ delimit . ", " }}{{ end }}"> + {{ end -}} + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="robots" content="index, follow"> + <meta charset="utf-8"> +</head> +<body> +{{ if .Site.Menus.main }}{{ partial "nav.html" . }}{{ end -}} +<main> +<header><h1 id="tag_{{ .Title }}">{{ block "title" . }}{{ end }}</h1></header> +<article> +{{ block "main" . }} +{{ .Content }} +{{ end }} +{{ if .Param "nextprev" }}{{ partial "nextprev.html" . -}}{{ end -}} +{{ if .Param "taglist" }}{{ partial "taglist.html" . }}{{ end -}} +</article> +</main> +{{ block "footer" . }} +<footer> + {{ if not .IsHome }} + <a href="/">на главную</a> <br/> + пишите свои комментарии на <a href="mailto:me@konyahin.xyz">me@konyahin.xyz</a> + {{ end }} + {{- if .Param "showrss" }}<br><br><a href="/index.xml"><img src="/rss.svg" style="max-height:1.5em" alt="RSS Feed" title="Subscribe via RSS for updates."></a>{{ end }} +</footer> +{{ end }} +</body> +</html> diff --git a/themes/lugo/layouts/_default/list.html b/themes/lugo/layouts/_default/list.html @@ -0,0 +1,6 @@ +{{ define "title" -}} +{{ .Title }} +{{- end }} +{{ define "main" -}} +{{ .Content }} +{{- end }} diff --git a/themes/lugo/layouts/_default/rss.xml b/themes/lugo/layouts/_default/rss.xml @@ -0,0 +1,26 @@ +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> + <channel> + <title>{{ .Site.Title }}</title> + <link>{{ .Permalink }}</link> + <description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description> + <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }} + <language>{{.}}</language>{{end}}{{ with .Site.Author.email }} + <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }} + <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }} + <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }} + <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }} + {{ with .OutputFormats.Get "RSS" }} + {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }} + {{ end }} + {{ range .Site.RegularPages }} + <item> + <title>{{ .Title }}</title> + <link>{{ .Permalink }}</link> + <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate> + {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}} + <guid>{{ .Permalink }}</guid> + <description>{{- .Content | html -}}</description> + </item> + {{ end }} + </channel> +</rss> diff --git a/themes/lugo/layouts/_default/single.html b/themes/lugo/layouts/_default/single.html @@ -0,0 +1,3 @@ +{{ define "title" -}} +{{ .Title }} +{{- end }} diff --git a/themes/lugo/layouts/blog/list.html b/themes/lugo/layouts/blog/list.html @@ -0,0 +1,17 @@ +{{ define "title" -}} +{{ .Title }} +{{- end }} +{{ define "main" -}} +{{ .Content }} +<ul> +{{- range.Pages }} + <li> + {{- if .Param "datesinlist" }}<time datetime="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">{{ .Date.Format "2006 Jan 02" }}</time> &ndash; {{ end -}} + <a href="{{ .RelPermalink }}">{{ .Title }}</a> + {{- if .Param "authorsinlist" }} + {{- range .Param "authors" }} by {{ . }}{{ end -}} + {{ end -}} + </li> +{{- end }} +</ul> +{{- end }} diff --git a/themes/lugo/layouts/partials/nav.html b/themes/lugo/layouts/partials/nav.html @@ -0,0 +1,8 @@ +<nav> + <ul> + {{- $sec := .Page.Section }}{{ $file := .File.TranslationBaseName -}} + {{ range.Site.Menus.main.ByWeight }}{{ $base := path.Base .URL }} + <li><a {{ if or ( eq $sec $base ) ( eq $file $base ) ( and (eq $sec "") ( eq $file "_index") (eq $base "/") ) }}class="menuactive" {{ end }}href="{{ .URL }}"><span class=pre>{{ .Pre }}</span><span class=menuname>{{ .Name }}</span></a></li> + {{- end }} + </ul> +</nav> diff --git a/themes/lugo/layouts/partials/nextprev.html b/themes/lugo/layouts/partials/nextprev.html @@ -0,0 +1,10 @@ +{{ if or .Next .Prev -}} +<div id="nextprev"> +{{- with .Prev }} +<a href="{{ .RelPermalink}}"><div id="prevart">Previous:<br>{{.Title}}</div></a> +{{ end -}} +{{- with .Next -}} +<a href="{{ .RelPermalink}}"><div id="nextart">Next:<br>{{.Title}}</div></a> +{{ end -}} +</div> +{{ end -}} diff --git a/themes/lugo/layouts/partials/taglist.html b/themes/lugo/layouts/partials/taglist.html @@ -0,0 +1,13 @@ + {{- if isset .Params "tags" -}} + {{- $tagsLen := len .Params.tags -}} + {{- if gt $tagsLen 0 -}} + <div style="clear:both" class=taglist> + {{- with .Site.Params.relatedtext }}{{ . }}<br>{{ end -}} + {{- range $k, $v := .Params.tags -}} + {{- $url := printf "tags/%s" (. | urlize | lower) -}} + <a id="tag_{{ . | lower }}" href="{{ $url | absURL }}">{{ . | title }}</a> + {{- if lt $k (sub $tagsLen 1) }} &middot; {{ end -}} + {{- end -}} + </div> + {{- end -}} + {{- end }} diff --git a/themes/lugo/layouts/shortcodes/hidvid.html b/themes/lugo/layouts/shortcodes/hidvid.html @@ -0,0 +1,10 @@ +<details> + <summary>Click to reveal video.</summary> +<iframe src="{{ index .Params 0 }}" + loading="lazy" + sandbox="allow-same-origin allow-scripts allow-popups" + allowfullscreen frameborder="0" + class="embvid" + title="Embedded Video"> +</iframe> +</details> diff --git a/themes/lugo/layouts/shortcodes/img.html b/themes/lugo/layouts/shortcodes/img.html @@ -0,0 +1,20 @@ +<!-- + class: class of the figure + link: url the image directs to + alt: alternative text + caption: caption + mouse: what the image says when moused over ("title" in HTML) +--> +<figure {{ with .Get "class" }}class="{{.}}"{{ end -}}> + {{- with .Get "link"}}<a href="{{.}}">{{ end -}} + <img src="{{ .Get "src" }}" + {{- with .Get "mouse" }} title="{{.}}"{{ end -}} + {{- with .Get "alt" }} alt="{{.}}"{{ end -}} + > + {{- if .Get "link"}}</a>{{ end -}} + {{- with .Get "caption" -}} + <figcaption> + {{- . -}} + </figcaption> + {{- end -}} +</figure> diff --git a/themes/lugo/layouts/shortcodes/rawhtml.html b/themes/lugo/layouts/shortcodes/rawhtml.html @@ -0,0 +1,2 @@ +<!-- raw html --> +{{.Inner}} diff --git a/themes/lugo/layouts/shortcodes/tagcloud.html b/themes/lugo/layouts/shortcodes/tagcloud.html @@ -0,0 +1,3 @@ +{{ if isset .Site.Taxonomies "tags" }}{{ if not (eq (len .Site.Taxonomies.tags) 0) }} <ul id="tagcloud"> + {{ range $name, $items := .Site.Taxonomies.tags }}{{ $url := printf "%s/%s" "tags" ($name | urlize | lower)}}<li><a href="{{ $url | absURL }}" id="tag_{{ $name }}">{{ $name | title }}</a></li> + {{ end }}</ul>{{ end }}{{ end }} diff --git a/themes/lugo/layouts/shortcodes/vid.html b/themes/lugo/layouts/shortcodes/vid.html @@ -0,0 +1,6 @@ +<iframe src="{{ index .Params 0 }}" + loading="lazy" + sandbox="allow-same-origin allow-scripts allow-popups" + allowfullscreen frameborder="0" + title="Embedded Video"> +</iframe> diff --git a/themes/lugo/static/rss.svg b/themes/lugo/static/rss.svg @@ -0,0 +1 @@ +<?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 455.731 455.731" style="enable-background:new 0 0 455.731 455.731;" xml:space="preserve"><g><rect x="0" y="0" style="fill:#F78422;" width="455.731" height="455.731"/><g><path style="fill:#FFFFFF;" d="M296.208,159.16C234.445,97.397,152.266,63.382,64.81,63.382v64.348 c70.268,0,136.288,27.321,185.898,76.931c49.609,49.61,76.931,115.63,76.931,185.898h64.348 C391.986,303.103,357.971,220.923,296.208,159.16z"/><path style="fill:#FFFFFF;" d="M64.143,172.273v64.348c84.881,0,153.938,69.056,153.938,153.939h64.348 C282.429,270.196,184.507,172.273,64.143,172.273z"/><circle style="fill:#FFFFFF;" cx="109.833" cy="346.26" r="46.088"/></g></g></svg> diff --git a/themes/lugo/static/style.css b/themes/lugo/static/style.css @@ -0,0 +1,128 @@ +body { + font-family: sans-serif ; + background: white ; + color: #110000 ; +} + +main { + max-width: 800px ; + margin: auto ; +} + +img { + max-width: 100% ; +} + +header h1 { + text-align: left ; +} + +footer { + max-width: 800px ; + margin: auto ; + text-align: left ; +} + +/* For TAGLIST.HTML */ +.taglist { + text-align: center ; + clear: both ; +} + +/* For NEXTPREV.HTML */ +#nextprev { + /* The container for both the previous and next articles. */ +} +#prevart { + float: left ; + text-align: left ; +} +#nextart { + float: right ; + text-align: right ; +} +#nextart,#prevart { + max-width: 33% ; +} +/* Background */ .chroma { background-color: #ffffff } +/* Other */ .chroma .x { } +/* Error */ .chroma .err { } +/* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } +/* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; } +/* LineHighlight */ .chroma .hl { display: block; width: 100%;background-color: #ffffcc } +/* LineNumbersTable */ .chroma .lnt { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } +/* LineNumbers */ .chroma .ln { margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } +/* Keyword */ .chroma .k { font-weight: bold } +/* KeywordConstant */ .chroma .kc { font-weight: bold } +/* KeywordDeclaration */ .chroma .kd { font-weight: bold; font-style: italic } +/* KeywordNamespace */ .chroma .kn { font-weight: bold } +/* KeywordPseudo */ .chroma .kp { font-weight: bold } +/* KeywordReserved */ .chroma .kr { font-weight: bold } +/* KeywordType */ .chroma .kt { font-weight: bold } +/* Name */ .chroma .n { } +/* NameAttribute */ .chroma .na { } +/* NameBuiltin */ .chroma .nb { font-weight: bold; font-style: italic } +/* NameBuiltinPseudo */ .chroma .bp { font-weight: bold; font-style: italic } +/* NameClass */ .chroma .nc { color: #666666; font-weight: bold; font-style: italic } +/* NameConstant */ .chroma .no { color: #666666; font-weight: bold; font-style: italic } +/* NameDecorator */ .chroma .nd { } +/* NameEntity */ .chroma .ni { } +/* NameException */ .chroma .ne { } +/* NameFunction */ .chroma .nf { color: #666666; font-weight: bold; font-style: italic } +/* NameFunctionMagic */ .chroma .fm { } +/* NameLabel */ .chroma .nl { } +/* NameNamespace */ .chroma .nn { color: #666666; font-weight: bold; font-style: italic } +/* NameOther */ .chroma .nx { } +/* NameProperty */ .chroma .py { } +/* NameTag */ .chroma .nt { } +/* NameVariable */ .chroma .nv { color: #666666; font-weight: bold; font-style: italic } +/* NameVariableClass */ .chroma .vc { } +/* NameVariableGlobal */ .chroma .vg { } +/* NameVariableInstance */ .chroma .vi { } +/* NameVariableMagic */ .chroma .vm { } +/* Literal */ .chroma .l { } +/* LiteralDate */ .chroma .ld { } +/* LiteralString */ .chroma .s { color: #666666; font-style: italic } +/* LiteralStringAffix */ .chroma .sa { color: #666666; font-style: italic } +/* LiteralStringBacktick */ .chroma .sb { color: #666666; font-style: italic } +/* LiteralStringChar */ .chroma .sc { color: #666666; font-style: italic } +/* LiteralStringDelimiter */ .chroma .dl { color: #666666; font-style: italic } +/* LiteralStringDoc */ .chroma .sd { color: #666666; font-style: italic } +/* LiteralStringDouble */ .chroma .s2 { color: #666666; font-style: italic } +/* LiteralStringEscape */ .chroma .se { color: #666666; font-style: italic } +/* LiteralStringHeredoc */ .chroma .sh { color: #666666; font-style: italic } +/* LiteralStringInterpol */ .chroma .si { color: #666666; font-style: italic } +/* LiteralStringOther */ .chroma .sx { color: #666666; font-style: italic } +/* LiteralStringRegex */ .chroma .sr { color: #666666; font-style: italic } +/* LiteralStringSingle */ .chroma .s1 { color: #666666; font-style: italic } +/* LiteralStringSymbol */ .chroma .ss { color: #666666; font-style: italic } +/* LiteralNumber */ .chroma .m { } +/* LiteralNumberBin */ .chroma .mb { } +/* LiteralNumberFloat */ .chroma .mf { } +/* LiteralNumberHex */ .chroma .mh { } +/* LiteralNumberInteger */ .chroma .mi { } +/* LiteralNumberIntegerLong */ .chroma .il { } +/* LiteralNumberOct */ .chroma .mo { } +/* Operator */ .chroma .o { } +/* OperatorWord */ .chroma .ow { font-weight: bold } +/* Punctuation */ .chroma .p { } +/* Comment */ .chroma .c { color: #888888; font-style: italic } +/* CommentHashbang */ .chroma .ch { color: #888888; font-style: italic } +/* CommentMultiline */ .chroma .cm { color: #888888; font-style: italic } +/* CommentSingle */ .chroma .c1 { color: #888888; font-style: italic } +/* CommentSpecial */ .chroma .cs { color: #888888; font-weight: bold } +/* CommentPreproc */ .chroma .cp { color: #888888; font-weight: bold } +/* CommentPreprocFile */ .chroma .cpf { color: #888888; font-weight: bold } +/* Generic */ .chroma .g { } +/* GenericDeleted */ .chroma .gd { } +/* GenericEmph */ .chroma .ge { } +/* GenericError */ .chroma .gr { } +/* GenericHeading */ .chroma .gh { } +/* GenericInserted */ .chroma .gi { } +/* GenericOutput */ .chroma .go { } +/* GenericPrompt */ .chroma .gp { } +/* GenericStrong */ .chroma .gs { } +/* GenericSubheading */ .chroma .gu { } +/* GenericTraceback */ .chroma .gt { } +/* GenericUnderline */ .chroma .gl { } +/* TextWhitespace */ .chroma .w { }