upd deps + build toc

This commit is contained in:
Sergey Yarkov 2023-04-14 22:47:33 +03:00
parent 69af4e882d
commit 56132501cd
No known key found for this signature in database
GPG Key ID: E91CE1FABC619B6F
10 changed files with 574 additions and 477 deletions

878
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,13 +18,13 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/image": "^0.16.2",
"@astrojs/rss": "^2.3.1",
"@astrojs/sitemap": "^1.2.1",
"@astrojs/image": "^0.16.6",
"@astrojs/rss": "^2.3.2",
"@astrojs/sitemap": "^1.2.2",
"@astrojs/solid-js": "^2.1.0",
"@netlify/functions": "^1.4.0",
"@sindresorhus/slugify": "^2.2.0",
"astro": "^2.1.7",
"astro": "^2.3.0",
"astro-icon": "^0.8.0",
"astro-netlify-cms": "^0.5.3",
"hast-util-to-string": "^2.0.0",
@ -36,10 +36,10 @@
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.1.0",
"sharp": "^0.32.0",
"solid-js": "^1.6.15"
"solid-js": "^1.7.3"
},
"devDependencies": {
"@types/mime-types": "^2.1.1",
"sass": "^1.60.0"
"sass": "^1.62.0"
}
}

View File

@ -0,0 +1,23 @@
---
import { buildToc } from "@root/utils";
import type { MarkdownHeading } from "astro";
import TableOfContentsHeading from "./TableOfContentsHeading.astro";
export interface Props {
headings: MarkdownHeading[];
}
const toc = buildToc(Astro.props.headings);
---
<nav class="toc">
<h2>Содержание</h2>
<ul>
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
</ul>
</nav>
<style lang="scss" is:global>
.toc {
}
</style>

View File

@ -0,0 +1,16 @@
---
import type { MarkdownHeadingToc } from "@root/utils";
export interface Props {
heading: MarkdownHeadingToc;
}
const { heading } = Astro.props;
---
<li>
<a href={`#${heading.slug}`}>{heading.text}</a>
<ul>
{heading.subheadings.map((subheading) => <Astro.self heading={subheading} />)}
</ul>
</li>

View File

@ -412,7 +412,7 @@ Now after opening the port you can send commands, don't forget to set the carria
The web application will consist of just a few elements: an input box where we can write our commands, buttons to open and close the serial port, connection status. You can see the finished version [here](https://web-serial-example.netlify.app/).
### Create HTML markup and styles
## Create HTML markup and styles
The HTML markup will look like this:

View File

@ -14,7 +14,6 @@ tags:
pubDate: 2022-11-04T21:00:00.000Z
updatedDate: 2023-02-13T16:34:23.326Z
---
# Что вы узнаете
По итогу вы узнаете, как отправлять и принимать данные от устройства с последовательным портом в браузере, разработаете демонстрационное веб-приложение, которое отправляет команды в микроконтроллер с последовательным портом и напишите прошивку для микроконтроллера [AVR128DA48](http://ww1.microchip.com/downloads/en/DeviceDoc/40002183A.pdf) для обработки этих команд. Готовый вариант проекта можно посмотреть [здесь](https://web-serial-example.netlify.app/), исходники загружены в [репозиторий](https://github.com/sergeyyarkov/avr128da48_web-serial-example) на GitHub.
@ -49,17 +48,17 @@ WebSerial API - это браузерный API, который предоста
UART передает данные последовательно по одному биту в одном из трех режимов:
- односторонний режим - данные отправляются только в одном направлении, от передатчика к приемнику
- полудуплексный режим - устройства могут передавать и принимать данные по очереди
- полнодуплексный режим - устройства могут передавать и принимать данные одновременно
* односторонний режим - данные отправляются только в одном направлении, от передатчика к приемнику
* полудуплексный режим - устройства могут передавать и принимать данные по очереди
* полнодуплексный режим - устройства могут передавать и принимать данные одновременно
Так как в этом проекте предполагается использование асинхронной версии UART, то соответственно необходимо устанавливать **одинаковую** скорость передачи битов в секунду (`baud rate`, `bps`) на двух устройствах. Наиболее распространенные скорости являются: **4800, 9600, 19.2 К, 57.6К и 115.2К.**
Данные передаются в виде так называемых `пакетов`, где каждый пакет содержит в себе:
- стартовый и стоповый биты - стартовый бит сигнализирует о поступлении битов данных, стоповый соответственно о конце данных.
- биты данных - пользовательские данные, которые поступают сразу после стартового бита, может содержаться от 5 до 8 битов.
- бит четности - необязательный бит, который идет после битов данных и перед стоповым битом и используется для обнаружения ошибок.
* стартовый и стоповый биты - стартовый бит сигнализирует о поступлении битов данных, стоповый соответственно о конце данных.
* биты данных - пользовательские данные, которые поступают сразу после стартового бита, может содержаться от 5 до 8 битов.
* бит четности - необязательный бит, который идет после битов данных и перед стоповым битом и используется для обнаружения ошибок.
<figure>
<img src="/media/uart-packet.jpg" width="700" height="300" alt="Структура UART пакета" format="webp">
@ -84,7 +83,7 @@ UART передает данные последовательно по одно
Как я упомянул ранее, прошивка будет написана для микроконтроллера AVR128DA48. На самом деле неважно какой микроконтроллер вы используете, вам достаточно нужно уметь отправлять и принимать данные используя UART периферию вашего микроконтроллера.
Суть программы будет очень проста: необходимо написать некий **_обработчик команд_** который будет принимать на вход строку (например `led_toggle`) и вызывать необходимую функцию, которая будет выполнять какое-то действие, в данном случае переключать светодиод.
Суть программы будет очень проста: необходимо написать некий ***обработчик команд*** который будет принимать на вход строку (например `led_toggle`) и вызывать необходимую функцию, которая будет выполнять какое-то действие, в данном случае переключать светодиод.
Постановка задачи ясна, теперь рассмотрим реализацию данной программы. Я буду использовать среду разработки MPLAB X IDE и язык C для написания прошивки.
@ -104,10 +103,10 @@ UART передает данные последовательно по одно
#endif /* CONSTANTS_H */
```
- `F_CPU` - тактовая частота микроконтроллера
- `BAUD_RATE` - скорость передачи данных UART
- `BUFFER_SIZE` - размер буфера, куда будем складывать поступающие данные
- `EOT` - End-of-Transmission, ASCII символ конца передачи данных
* `F_CPU` - тактовая частота микроконтроллера
* `BAUD_RATE` - скорость передачи данных UART
* `BUFFER_SIZE` - размер буфера, куда будем складывать поступающие данные
* `EOT` - End-of-Transmission, ASCII символ конца передачи данных
## Инициализация USART
@ -157,11 +156,11 @@ void USART1_Initialize(void) {
Для полнодуплексного режима инициализация асинхронной версии USART происходит следующим образом:
- Конфигурация скорости передачи данных путем записывания значения в регистр `USARTn.BAUD`
- Конфигурация размера `фрейма`, в нашем случае это 8 бит.
- Конфигурация пина `TX` на выход и пина `RX` на вход.
- Включение прерывания на то, когда закончился прием данных (когда пришел один пакет).
- Включение приемника и передатчика.
* Конфигурация скорости передачи данных путем записывания значения в регистр `USARTn.BAUD`
* Конфигурация размера `фрейма`, в нашем случае это 8 бит.
* Конфигурация пина `TX` на выход и пина `RX` на вход.
* Включение прерывания на то, когда закончился прием данных (когда пришел один пакет).
* Включение приемника и передатчика.
Теперь, рассмотрим функции отправки данных:
@ -213,9 +212,9 @@ void command_list(void);
Для решения этой задачи я предлагаю создавать массив структур, где в каждой структуре будет содержаться указатель на функцию которую нам нужно выполнить, и название самой команды.
- `command_define` будет создавать новую структуру в массиве.
- `command_process` будет вызывать соответствующую функцию по имени команды.
- `command_list` будет отправлять по UART информацию о существующих командах.
* `command_define` будет создавать новую структуру в массиве.
* `command_process` будет вызывать соответствующую функцию по имени команды.
* `command_list` будет отправлять по UART информацию о существующих командах.
Реализовать задуманное можно следующим образом создав файл `command.c`:
@ -369,9 +368,9 @@ void hello_world(void) {
С помощью функции `command_define` мы можем определить, по какой переданной команде по USART запускать функцию:
- `led_toggle` будет переключать светодиод.
- `hello_world` выводить сообщение “Hello world”.
- `command_list` будет показывать список имеющихся команд.
* `led_toggle` будет переключать светодиод.
* `hello_world` выводить сообщение “Hello world”.
* `command_list` будет показывать список имеющихся команд.
## Демонстрация работы
@ -379,10 +378,10 @@ void hello_world(void) {
Выберете ваше устройство из списка (в моем случае это `/dev/ttyACM1`) и установите следующие настройки:
- Скорость: **9600** бод
- Количество битов: **8**
- Четность: **None**
- Стоповые биты: **1**
* Скорость: **9600** бод
* Количество битов: **8**
* Четность: **None**
* Стоповые биты: **1**
<figure>
<img
@ -414,7 +413,7 @@ void hello_world(void) {
Веб-приложение будет состоять всего из нескольких элементов: поле для ввода, куда мы сможем писать наши команды, кнопки для открытия и закрытия последовательного порта, статус соединения. Готовый вариант можно посмотреть [здесь](https://web-serial-example.netlify.app/).
### Создание разметки HTML и стилей
## Создание разметки HTML и стилей
Разметка HTML будет выглядеть следующим образом:
@ -586,12 +585,12 @@ class SerialPortHandler {
}
```
- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) является классом, который кодирует строку в массив без знаковых 8-ми битных целых чисел [`Uint8Array`](https://webidl.spec.whatwg.org/#idl-Uint8Array).
- [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder#examples) наоборот, декодирует в строку. Экземпляры этих классов нам понадобятся, т.к мы будет оперировать данными с типом `Uint8Array`.
- Функции `onConnect` и `onDisconnect` будут вызываться при подключении или отключении устройства.
- Свойство [`options`](https://wicg.github.io/serial/#serialoptions-dictionary) содержит необходимые параметры, которые будут использоваться для передачи и приема данных.
- Свойство [`port`](https://wicg.github.io/serial/#serialport-interface) - объект, который будет содержать методы для работы с портом и информацию об устройстве после установки соединения.
- Функция `setupListeners` просто будет добавлять обработчики событий.
* [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder) является классом, который кодирует строку в массив без знаковых 8-ми битных целых чисел [`Uint8Array`](https://webidl.spec.whatwg.org/#idl-Uint8Array).
* [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder#examples) наоборот, декодирует в строку. Экземпляры этих классов нам понадобятся, т.к мы будет оперировать данными с типом `Uint8Array`.
* Функции `onConnect` и `onDisconnect` будут вызываться при подключении или отключении устройства.
* Свойство [`options`](https://wicg.github.io/serial/#serialoptions-dictionary) содержит необходимые параметры, которые будут использоваться для передачи и приема данных.
* Свойство [`port`](https://wicg.github.io/serial/#serialport-interface) - объект, который будет содержать методы для работы с портом и информацию об устройстве после установки соединения.
* Функция `setupListeners` просто будет добавлять обработчики событий.
Далее рассматривается реализация этих методов.
@ -640,12 +639,12 @@ const port = await navigator.serial.requestPort({
После того как пользователь выбрал устройство, метод `requestPort` возвращает [`port`](https://wicg.github.io/serial/#serialport-interface), который можно открыть передав туда [параметры](https://wicg.github.io/serial/#serialoptions-dictionary):
- `baudRate` : скорость передачи данных.
- `dataBits` : количество бит данных во фрейме (7 или 8).
- `stopBits` : количество стоповых битов в конце пакета (1 или 2).
- `parity` : режим четности ( `none` , `even` или `odd` ).
- `bufferSize` : размер буферов чтения и записи, которые должны быть созданы.
- `flowControl` : режим управления потоком ( `none` или `hardware` ).
* `baudRate` : скорость передачи данных.
* `dataBits` : количество бит данных во фрейме (7 или 8).
* `stopBits` : количество стоповых битов в конце пакета (1 или 2).
* `parity` : режим четности ( `none` , `even` или `odd` ).
* `bufferSize` : размер буферов чтения и записи, которые должны быть созданы.
* `flowControl` : режим управления потоком ( `none` или `hardware` ).
Свойство `baudRate` является единственным обязательным параметром, остальные же являются необязательными и имеют [значения по умолчанию](https://wicg.github.io/serial/#serialoptions-dictionary).
@ -893,8 +892,8 @@ setInterval(async () => {
# Полезные материалы
- [Статус поддержки браузеров Can I use](https://caniuse.com/web-serial)
- [Документация к WebSerial API](https://wicg.github.io/serial/)
- [Подробное руководство по Stream API](https://web.dev/streams/)
- [Список USB идентификаторов](https://devicehunt.com/all-usb-vendors)
- [Доступ к различным устройствам из браузера](https://web.dev/devices-introduction/)
* [Статус поддержки браузеров Can I use](https://caniuse.com/web-serial)
* [Документация к WebSerial API](https://wicg.github.io/serial/)
* [Подробное руководство по Stream API](https://web.dev/streams/)
* [Список USB идентификаторов](https://devicehunt.com/all-usb-vendors)
* [Доступ к различным устройствам из браузера](https://web.dev/devices-introduction/)

View File

@ -1,4 +1,5 @@
---
import type { MarkdownHeading } from "astro";
import type { CollectionEntry } from "astro:content";
import { DEFAULT_LANGUAGE, SITE_METADATA } from "@root/constants";
import BaseHead from "@components/BaseHead.astro";
@ -6,14 +7,16 @@ import BaseLayout from "@components/BaseLayout.astro";
import ArticleContent from "@components/ArticleContent.astro";
import ArticlePost from "@components/ArticlePost.astro";
import OpenGraph from "@components/OpenGraph.astro";
import TableOfContents from "@root/components/TableOfContents.astro";
export interface Props {
lang?: string | undefined;
article: CollectionEntry<"blog">;
headings: MarkdownHeading[];
}
const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
const { lang = DEFAULT_LANGUAGE, article } = Astro.props as Props;
const { lang = DEFAULT_LANGUAGE, article, headings } = Astro.props as Props;
const {
data: { title, description, coverImage },
} = article;
@ -36,6 +39,7 @@ const {
<BaseLayout>
<div class="container">
<!-- <TableOfContents headings={headings} /> -->
<ArticlePost lang={lang} article={article}>
<ArticleContent>
<slot />

View File

@ -36,11 +36,11 @@ export async function getStaticPaths() {
type Props = { article: CollectionEntry<"blog">; availableOnLang: CollectionEntry<"blog">[] };
const { Content } = await Astro.props.article.render();
const { Content, headings } = await Astro.props.article.render();
const { article, availableOnLang } = Astro.props;
---
<ArticleLayout lang={article.id.split("/")[0]} article={article}>
<ArticleLayout lang={article.id.split("/")[0]} article={article} headings={headings}>
{
availableOnLang.length > 1 && (
<div class="languages-available">

View File

@ -3,7 +3,8 @@
color-scheme: light;
/* Fonts */
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;
--font-fallback: -apple-system, BlinkMacSystemFont, Segoe UI, Arial, sans-serif, Apple Color Emoji,
Segoe UI Emoji;
--font-family-base: "Rubik", var(--font-fallback);
--font-family-monospace: "IBM Plex Mono", Menlo, monospace;
--font-size-xs: 0.75rem;

View File

@ -1,3 +1,4 @@
import type { MarkdownHeading } from "astro";
import type { CollectionEntry } from "astro:content";
import { DEFAULT_LANGUAGE } from "@root/constants";
import slugify from "@sindresorhus/slugify";
@ -62,3 +63,20 @@ export function formatToArticleBlocks(articles: CollectionEntry<"blog">[]) {
export function getUniqueTags(articles: CollectionEntry<"blog">[]): string[] {
return Array.from(new Set(articles.map((a) => a.data.tags).flat()));
}
export type MarkdownHeadingToc = MarkdownHeading & { subheadings: MarkdownHeading[] };
export function buildToc(headings: MarkdownHeading[]) {
const toc: MarkdownHeadingToc[] = [];
const parentHeadings = new Map();
headings.forEach((h) => {
const heading = { ...h, subheadings: [] };
parentHeadings.set(heading.depth, heading);
if (heading.depth === 1) {
toc.push(heading);
} else {
parentHeadings.get(heading.depth - 1).subheadings.push(heading);
}
});
return toc;
}