Меню

Сканер для can шины

Хакаем CAN шину авто. Мобильное приложение вместо панели приборов

Я продолжаю изучать CAN шину авто. В предыдущих статьях я голосом открывал окна в машине и собирал виртуальную панель приборов на RPi. Теперь я разрабатываю мобильное приложение VAG Virtual Cockpit, которое должно полностью заменить приборную панель любой модели VW/Audi/Skoda/Seat. Работает оно так: телефон подключается к ELM327 адаптеру по Wi-Fi или Bluetooth и отправляет диагностические запросы в CAN шину, в ответ получает информацию о датчиках.

По ходу разработки мобильного приложения пришлось узнать, что разные электронные блоки управления (двигателя, трансмиссии, приборной панели и др.) подключенные к CAN шине могут использовать разные протоколы для диагностики, а именно UDS и KWP2000 в обертке из VW Transport Protocol 2.0.

Программный сниффер VCDS

Чтобы узнать по какому протоколу общаются электронные блоки я использовал специальную версию VCDS с программным сниффером в комплекте. В этот раз никаких железных снифферов на Arduino или RPi не пришлось изобретать. С помощью CAN-Sniffer можно подсмотреть общение между VCDS и автомобилем, чтобы затем телефон мог прикинуться диагностической утилитой и отправлять те же самые запросы.

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

VW/Skoda/Seat (2006-2012) — приборная панель UDS. Двигатель и трансмиссия VW TP 2.0

Audi (2006-2012) — приборная панель VW TP 2.0. Двигатель UDS. Трансмиссия VW TP 2.0

VW/Skoda/Seat/Audi (2012-2021) — везде UDS

Протокол UDS

Unified Diagnostic Services (UDS) — это диагностический протокол, используемый в электронных блоках управления (ЭБУ) автомобильной электроники. Протокол описан в стандарте ISO 14229-1 и является производным от стандарта ISO 14230-3 (KWP2000) и ныне устаревшего стандарта ISO 15765-3 (Diagnostic Communication over Controller Area Network (DoCAN)). Более подробно в википедии.

Диагностические данные от двигателя по протоколу UDS (Skoda Octavia A7)

В моей машине (Skoda Octavia A5) приборка использует UDS протокол, это дало мне легкий старт разработки, т.к. данные были в простом формате Single Frame SF (фрейм, вся информация которого умещается в один CAN пакет) и большинство значений легко поддавались расшифровке. Volkswagen не дает документацию на формат значений, поэтому формулу расшифровки для каждого датчика приходилось подбирать методом логического мышления. Про UDS протокол очень хорошо и с подробным разбором фреймов написано на canhacker.ru.

Разбор UDS пакета в формате Single Frame

Пример запроса и ответа температуры моторного масла:

Запрос температуры моторного масла:

7E0 — Адрес назначения (ЭБУ двигателя)

Байт 0 (0x03) — Размер данных (3 байта)

Байт 1 (0x22) — SID идентификатор сервиса (запрос текущих параметров)

Байт 2, 3 (0x11 0xBD) — PID идентификатор параметра (температура моторного масла)

Байт 4, 5, 6, 7 (0x55) — Заполнитель до 8 байт

Ответ температуры моторного масла:

7E8 — Адрес источника (Диагностический прибор)

Байт 0 (0x05) — Размер данных (5 байт)

Байт 1 (0x62) — Положительный ответ, такой SID существует. 0x22 + 0x40 = 0x62. (0x7F) — отрицательный ответ

Байт 2, 3 (0x11 0xBD) — PID идентификатор параметра (температура моторного масла)

Байт 4, 5 (0x0B 0x74) — значение температуры моторного масла (20.1 °C формулу пока что не смог подобрать)

Байт 6, 7 (0x55) — Заполнитель до 8 байт

Первая версия мобильного приложения VAG Virtual Cockpit умела подключаться только к приборной панели по UDS.

VAG Virtual Cockpit — экран с данными от приборной панели по протоколу UDS

VW Transport Protocol 2.0

Volkswagen Transport Protocol 2.0 используется в качестве транспортного уровня, а данные передаются в формате KWP2000. Keyword Protocol 2000 — это протокол для бортовой диагностики автомобиля стандартизированный как ISO 14230. Прикладной уровень описан в стандарте ISO 14230-3. Более подробно в википедии.

Т.к. KWP2000 использует сообщения переменной длины, а CAN шина позволяет передавать сообщения не больше 8 байт, то VW TP 2.0 разбивает длинное сообщение KWP2000 на части при отправке по CAN шине и собирает заново при получении.

Диагностические данные от двигателя по протоколу KWP2000 (Skoda Octavia A5)

ЭБУ двигателя моей машины использует протокол VW TP 2.0, поэтому мне пришлось изучить его. Видимо Volkswagen разрабатывала транспортный протокол не только для работы по надежной CAN шине, но и для менее надежных линий связи, иначе нет объяснения для чего требуется такая избыточная проверка целостности данных. Главным источником информации по VW TP 2.0 является сайт https://jazdw.net/tp20.

Разбор протокола VW TP 2.0 на примере подключения к первой группе двигателя:

Настраиваем канал с двигателем. Байт 0: 0x01 — двигатель, 0x02 — трансмиссия. Байт 5,4: 0x300 — адрес источника

Получили положительный ответ. Байт 5,4: 0x740 — к двигателю обращаемся по этому адресу

Настраиваем ЭБУ на отправку сразу 16 пакетов и выставляем временные параметры

Получили положительный ответ

Отправляем команду KWP2000 startDiagnosticSession. Байт 0: 0x10 = 0b0001 — последняя строка данных + 0x0 счетчик отправляемых пакетов 0 (0x0 — 0xF)

Получили положительный ответ. Байт 0: 0x10 — cчетчик принимаемых пакетов 0

Мы отправили первый ACK, что получили ответ

Делаем запрос. Байт 0: 0x11 — счетчик отправляемых пакетов 1. Байт 3: 0x21 — запрос параметров. Байт 4: 0x01 — из группы 1

300 22 00 1A 61 01 01 C8 13

Байт 0: 0x22 — 0b0010 (не последняя строка данных) + 0x02 (cчетчик принимаемых пакетов 2). Байт 1,2: 0x00 0x1A длина 26 байт. Байт 3,4: 0x61 0x01 — положительный ответ на команду запроса параметров 0x21+0x40=0x61 из 0x1 группы. Байт 5: 0х01 — Запрос RPM (соответсвует протоколу KW1281). Байт 6,7: (0xC8 * 0x13)/5 = 760 RPM (формула соответствует протоколу KW1281)

300 23 05 0A 99 14 32 86 10

Байт 1: 0x05 — запрос ОЖ. Байт 2,3: (0x0A * 0x99)/26 = 57.0 C. Байт 4: 0x14 = запрос лямбда контроль %. Байт 5,6: 0x32*0x86; Байт 7: 0х10 — двоичная настройка

300 24 FF BE 25 00 00 25 00

Читайте также:  Шины вид 201 характеристики применение

0x25 0x00 x00 — Заполнитель, до 8 параметров

300 15 00 25 00 00 25 00 00

Байт 0: 0x15 — 0b0001 (последняя строка данных) + 0x5 (счетчик принимаемых пакетов 5)

Отправляем ACK. Прибывляем к нашему предыдущему ACK количество полученных пакетов 0xB1 + 0x4 = 0xB5

Запрос KeepAlive, что мы еще на связи

ЭБУ в ответ тоже разрывает связь

Во второй версии мобильного приложения VAG Virtual Cockpit появилась возможность диагностировать двигатель и трансмиссию по протоколу VW TP 2.0.

VAG Virtual Cockpit — экран с данными от двигателя по протоколу VW TP 2.0

Диагностический адаптер ELM327

Для меня некоторое время было вопросом, как получить данные из CAN шины и передать на телефон. Можно было бы разработать собственный шлюз с Wi-Fi или Bluetooth, как это делают производители сигнализаций, например Starline. Но изучив документацию на популярный автомобильный сканер ELM327 понял, что его можно настроить с помощью AT команд на доступ к CAN шине.

Копия диагностического сканера ELM327 Не все ELM327 одинаково полезны

Оригинальный ELM327 от компании elmelectronics стоит порядка 50$, в России я таких не встречал в продаже. У нас продаются только китайские копии/подделки, разного качества и цены 10-30$. Бывают полноценные копии, которые поддерживают все протоколы, а бывают и те которые умеют отвечать только на несколько команд, остальные игнорируют, такие адаптеры не имеют доступ к CAN шине. Я например пользуюсь копией Viecar BLE 4.0, который поддерживает 100% всех функций оригинала.

Для работы с протоколом UDS через ELM327 нужно указать адреса назначения, источника и разрешить длинные 8 байтные сообщения, по умолчанию пропускается максимум 7 байт.

Последовательность ELM327 AT команд для работы с UDS по CAN шине:

Для работы с протоколом KWP2000 через ELM327 нужно только указать адреса назначения и источника.

Последовательность ELM327 AT команд для работы с VW TP 2.0 по CAN шине:

Мобильное приложение VAG Virtual Cockpit

Для разработки мобильного приложения подключаемого к автомобилю требовалось:

Сниффером собрать трафик от диагностической утилиты VCDS

Изучить работу протоколов UDS, VW TP 2.0, KWP2000

Настроить диагностический сканер ELM327 на работу с UDS и VW TP 2.0

Изучить новый для меня язык программирования Swift

Мобильное приложение VAG Virtual Cockpit для iOS

В итоге получилось приложение, которое сочетает в себе функции отображения точных данных панели приборов и диагностика основных параметров двигателя и трансмиссии.

Пару слов про точность данных. Штатная панель приборов не точно показывает скорость — завышает показания на 5-10 км/ч, стрелка охлаждающей жидкости всегда на 90 °C, хотя реальная температура может быть 80 — 110 °C, стрелка уровня топлива до середины идет медленно, хотя топлива уже меньше половины и при нуле на самом деле топливо еще есть в баке. Производитель это делает для удобства и безопасности водителя.

На данный момент приложение показывает следующие параметры:

Приборная панель

Трансмиссия (температура)

1) Какая дверь открыта
2) Скорость
3) Обороты
4) Температура масла
5) Температура ОЖ
6) Топливо в баке в л.
7) Запас хода в км.
8) Средний расход
9) Время в машине
10) Пробег
11) Температура за бортом

1) Обороты
2) Массовый расход воздуха
3) Температура забора воздуха
4) Температура выхлопа (рассчитанная)
5) Критический уровень масла
6) Уровень масла
7) Наддув турбины (реальный)
8) Наддув турбины (ожидаемый)
9) Пропуски зажигания в цилиндрах
10) Углы откатов зажигания в цилиндрах

1) ATF AISIN (G93)
2) DSG6 (G93)
3) Блок управления DSG6 (G510)
4) Масло диска сцепления DSG6 (G509)
5) Мехатроник DSG7 (G510)
6) Процессор DSG7
7) Диск сцепления DSG7

Я стремлюсь чтобы приложение поддерживало как можно больше моделей автомобилей. Пока что поддерживаются производители: Volkswagen, Skoda, Seat, Audi. На разных комплектациях могут отображаться не все параметры, но это поправимо.

Сейчас я провожу тестирование версии 3.0. Приложение доступно только на iOS, после релиза 3.0 перейду к разработке версии для Android.

Если интересно потестировать и есть желание принять участие в проекте, то установить приложение можно по ссылке. Также я веду бортжурнал на drive2.ru, где делюсь полезной информацией и новостями о VAG Virtual Cockpit.

Источник

CAN sniffer

CAN шина

Описывать технические подробности CAN шины в деталях — удел документации. В данной статье достаточно знать, что она:

  • имеет двухпроводное физическое подключение
  • бывают различные скорости передачи данных
  • для подключения уже имеются готовые микросхемы и даже готовые платы с распаянными деталями

Полистав странички одного известного интернет магазина из поднебесной, я заказал несколько различных вариантов шилдов и пошёл изучать особенности электрических сигналов в автомобиле. Подопытным автомобилем выступил LADA Kalina Cross с 127-ым мотором и электронным блоком управления ИТЭЛМА М74.5 CAN.

Подключаюсь в диагностический разъём OBD (контакты 6 и 14) и смотрю осциллографом, что там имеется. После поворота ключа зажигания начинают бегать пакеты с амплитудой до 2,5 В. Ставлю паузу на осциллографе и смотрю на пакет.

Заметны стартовые и стоповые биты, какие-то данные в пакете. На тот момент я уже знал, что скорость передачи данных ожидается 500 кбит/с, как наиболее частая для моторной CAN шины. Длительность пакета получается около 230 мкс и перед пакетом наблюдается довольно большая пауза в передаче данных. Масштабирую время и вижу три пакета и паузы между ними.

Если сложить длительность передачи данных и паузу между пакетам получается, что передача одной порции данных занимает около 1 мс.

К чему я это всё вывожу? А вопрос чисто практический: хватит ли скорости последовательного порта для передачи всех данных? И исходя из увиденного, можно сделать вывод, что скорость 500 кбит/с развивается внутри пакета, который занимает примерно четверть времени на передачу. Значит средняя скорость передачи будет вчетверо меньшей. На тот момент я ещё не располагал тестами скорости последовательного интерфейса Arduino и забегая вперёд скажу, что даже с самым распространённым преобразователем Serial to USB CH340 стабильно работает скорость в 2 Мбит/с.

Читайте также:  Как правильно поставить шины нокиан хакапелита

CAN scanner на Arduino

Первый прибыл шилд для классической Arduino UNO. Да он стоит значительно дороже своих более мелких собратьев, но он имеет на борту всё необходимое и даже две кнопки.

Именно с ним я и начал все эксперименты. Собрал простую схему с этим шилдом и жидкокристаллическим двухстрочным экраном. Цель была — вывести на экран хоть какие-то данные. Перебирал различные библиотеки для работы с CAN шиной на Arduino (сразу скажу, что правильная и рабочая библиотека называется CAN-BUS Shield by Seeed Studio с заголовочным файлом mcp_can.h), поменял кварцевый резонатор на шилде на 16 МГц (изначально стоял 8 МГц) — данных не было.

На шилде установлены две микросхемы: контроллер CAN шины MCP2515 и драйвер CAN шины TJA1050. Почитав документацию и различные форумы, решил поменять TJA1050 на более каноничный драйвер MCP2551 и данные появились. Возможно TJA1050 была изначально неисправна, так как с её подключением двумя проводками ошибиться было очень сложно, к тому же я использовал OBD и DB9 разъёмы для подключения.

За пару часов был написан простой CAN scanner, который выводил на жидкокристаллический дисплей номер захваченного пакета, его ID и до 8 байтов данных этого пакета.

Вот тут и пригодились кнопочки на шилде, которыми я реализовал переключение между номером отображаемого пакета.

Начало было положено, надо переходить к более интересной реализации.

CAN sniffer на Arduino

Задача стояла достаточно простая:

  • принимаем пакет из CAN шины
  • укладываем полученные данные в свою структуру
  • отправляем структуру через последовательный порт

С первыми двумя задачами я вообще не видел никаких проблем. Библиотека предоставляла прерывание при приёме очередного пакета данных и удобные функции для получения данных. А вот отправку данных в сторону компьютера решил сделать через библиотеку CyberLib, которая устраняет некоторые накладные расходы всей платформы Arduino, за счёт чего можно немного разгрузить процессор для обработки данных. Позже от этой библиотеки пришлось отказаться.

Для того, чтобы отправляемые данные корректно обрабатывались на стороне компьютера, перед каждой очередной порцией данных в поток вставляется префикс из четырёх байтов 0xAA55AA55 (почему-то вспомнились эти байты по последним двум байтам загрузочного сектора DOS, только они там были в другом порядке). Логика такая:

  • компьютер читает весь поток из последовательного порта и находит в нём искомую последовательность префикса 0xAA55AA55
  • сразу после префикса будут 4 байта идентификатора пакета
  • далее длина данных этого пакета, по ней контролируется длина всего пакета
  • до 8 байтов данных

На этом программная часть в Arduino, на тот момент, была завершена. Позже она была значительно переделана, но общая концепция не поменялась.

Так же я написал простой генератор пакетов данных для отладки, чтобы отлаживаться дома — он просто отправляет в последовательный порт пакеты со случайными данными, что позволяет отлаживать приложение на компьютере в комфортных условиях.

Примерно в это же время прибыли более миниатюрные компоненты Arduino Nano и Mini CAN shield.

Я спроектировал небольшой корпус, распечатал его и разместил внутри все компоненты.

Снаружи с одной стороны OBD разъём, с другой — Mini USB. Внутри имеется переключатель для терминирующего резистора.

CAN sniffer на PC с использованием wxWidgets

Набросал простую заготовку программы на C#, которая выводит в Grid получаемые данные. И пошёл проверять в автомобиль. Только пошёл не со своим ноутбуком, так как у него батарея давно приказала долго жить и использовался он как стационарный компьютер, а взял нетбук с очень слабым процессором. То что я увидел… Я ничего не увидел. Оба ядра загружены на 100%, интерфейс приложения не реагирует. Но на моём компьютере, который всё-таки значительно шустрее нетбука, с генератором случайных пакетов приложение нормально работало и отображало данные. Из этого я сделал вывод, что платформа .NET на слабых компьютерах мне не подойдёт, так как отлаживаться в полевых условиях я мог на тот момент только с тем нетбуком.

Ранее я в нескольких проектах использовал библиотеку wxWidgets и о ней у меня только приятные впечатления. Она легковесная, нет необходимости тащить с собой различные библиотеки и даже кросс-платформенная, что вселяет надежду, что интерфейсную часть кода можно перенести без значительных переделок на другие платформы. В конце статьи будет ссылка на скомпилированную программу, если возиться со всем этим не будет желания.

Можно скачать и посмотреть видео (менее восьми минут), а можно выполнить 6 шагов по описанию ниже.

Установка и компиляция wxWidgets:

1. Скачать и установить wxWidgets если это установщик, либо распаковать, если это архив

2. Создать переменную окружения WXWIN указывающую на папку, куда установили или распаковали (например C:\wxWidgets):

Свойства системы -> Дополнительные параметры системы -> Переменные среды -> Создать
WXWIN = C:\wxWidgets

3. Из папки C:\wxWidgets\build\msw открыть файл решения под соответствующую Visual Studio (wx_vc16.sln для Visual Studio 2019)

4. В Solution Expolorer, с помощью клавиши Shift, выделить все проекты, кроме _custom_build и зайти в Properties проектов.

5. В разделе C/C++ -> Code Generation изменить параметр Runtime Library:

Для конфигурации Debug выбрать /MTd
Для конфигурации Release выбрать /MT

6. Скомпилировать библиотеки wxWidgets по очереди для Debug и Release конфигураций.

Пробное приложение и настройка проекта в Visual Studio (для проверки)

1. В Visual Studio создать Empty Project с указанием типа приложения Desktop Application (.exe)

2. В окне View -> Property Manager для своего проекта правой кнопкой выбрать меню Add existing property sheet… и выбрать файл:

3. Создать файл main.cpp и скопировать в него содержимое файла:

4. В настройках проекта C/C++ -> Code Generation изменить (если пункт не появился — сделать пробную сборку):

Читайте также:  Какие преимущества дает использование зимних шин в холодное время года

Runtime Library для конфигурации Debug: /MTd
Runtime Library для конфигурации Release: /MT

5. Дополнительно, если необходимы привилегии UAC, в разделе Linker -> Manifest File:

UAC Execution Level: requireAdministrator

6. Для добавления иконки exe-файлу надо добавить ресурсный файл со следующим содержимым:

#include «wx\msw\wx.rc»
wxicon icon app_icon.ico

Первый реализованный прототип на C++ и wxWidgets показал, что даже нетбук справляется с отображением данных в таблице и я приступил к разработке задуманного.

Архитектурно программа состоит из двух потоков: интерфейсный и поток работы с последовательным портом. Никаких невероятно интересных алгоритмов не применялось. Код обильно снабжён комментариями и должен быть довольно понятен. Ссылка на исходники будет в конце статьи.

Первое что было сделано — раскраска ячеек данных в таблице по давности получения этих данных. Уже в первом прототипе, глядя на 17 строк данных меняющихся непрерывно значений, я понял, что надо как-то различать свежие данные и данные, которые не изменяются или меняется редко. Сделал раскраску в два этапа:

  • впервые пришедшие данные выделяются зелёным цветом фона ячеек
  • данные пришедшие повторно и далее — выделяются красным фоном, который постепенно выцветает до белого если больше эти данные не меняются

Сразу же стало наглядно видно, какие ячейки вообще не используются, какие содержат сигналы счётчиков. Поиск же интересующих изменяющихся значений значительно упрощается. Здесь и далее все изображения анимированные. Если анимация не работает в статье (на некоторых мобильных браузерах) — кликайте по изображению для открытия полной версии анимации.

Далее мне захотелось всё-таки проверить, справляется ли последовательный порт с потоком данных. Для этого я на стороне Arduino добавил счётчики количества принятых пакетов и счетчик байтов в пакете. Эти счётчики отправляются на компьютер в пакете с идентификатором 0x000. Программа при получении этих данных не выводит их в таблицу, а отображает в отдельных информационных полях сверху. Полученные результаты даже весьма понравились. В среднем принимается до 750 пакетов/с со скоростью до 9,5 кБ/с, а это где в районе до 80 кбит/с, что вполне по силам последовательному порту. Но всё равно, обмен данными настроен по умолчанию на 500 кбит/с, пусть лучше будет запас.

Добавление возможности записи данных в журнал появилось после того, как подключил параллельно к OBD интерфейсу диагностический адаптер ELM327 и связав его с телефоном, попробовал читать различные данные. Данные пробегали настолько быстро, что увидеть их невозможно. Записав всё это в журнал, можно потом спокойно сесть и посмотреть передаваемые данные. Для этого в журнал могут записываться даже ASCII текстовые данные. Так же можно выбирать тип файла, символ разделитель и настроить фильтр пакетов кликом в таблице по указанному идентификатору пакета и нажатию кнопки «Добавить ID в фильтр» (по умолчанию записываются все данные), если запись всех данных избыточна.

Именно тогда пришло осознание, что все приложения для телефона, которые производят всякую «диагностику» через связку ELM327 и телефон, не общаются напрямую с CAN шиной автомобиля. Они всего лишь используют функционал диагностики OBD через CAN шину посредством обращения к CAN ID 0x7E0. Обычно это адрес контроллера мотора (ЭБУ), ответ же от него приходит в пакете с идентификатором 0x7E8. А вот все остальные пакеты данных — это так называемый Vendor Specific и ни один производитель так просто их не раскроет (хотя есть пример: Ford выпустил SDK для своих автомобилей).

Продолжая изучать что же передаётся в этих пакетах пришёл к ещё одной идее: при клике на ячейку в таблице, в окне программы справа выводить двоичное и десятичное значение этого байта, а так же брать следующий байт и дополнять до слова. Далее это слово умножать на некий коэффициент и получить десятичный результат. Звучит не очень понятно, но вот в связи с чем это делалось: обороты мотора приходят в пакете CAN ID 0x180, в первых двух байтах. Эти два байта дают некое слово, которое пропорционально оборотам. Если значение этого слова разделить на 8, то получатся текущие обороты. Поэтому указывается множитель 0,125, как обратная величина от 8. Далее это слово визуализируется в графике с динамической подстройкой по амплитуде. В принципе, множитель можно искать в обратной последовательности: нашёл ячейки, которые по графику очень похожи на обороты мотора или ещё что-то искомое, после чего подгоняется множитель для получения действительных значений.

Ну а двоичное представление позволяет искать различные битовые индикаторы. Например поиск индикаторов указателей поворота сводится к тому, чтобы включить их и наблюдать какая ячейка начинает изменяться, в примере ниже это CAN ID 0x481 байт 2. После чего клик по ячейке приводит к отображению её двоичного значения в соответствующем поле, где уже видны переключающиеся младшие два бита (левый, правый и если вместе — аварийная сигнализация).

И напоследок мне понадобилось сделать отправку некоторых управляющих данных в CAN шину и посмотреть реакцию на эти команды. В программу на Arduino был добавлен код, который принимает данные со стороны компьютера и передаёт в CAN шину. Именно на этом этапе пришлось отказаться от CyberLib, так как у неё не было поддержки прерывания поступления данных в буфер последовательного порта. В программе на компьютере добавил несколько текстовых полей, в которые можно ввести различные параметры и таблицу для просмотра ответа исполнительного устройства. В примере ниже показаны команды управления включить/отключить первую скорость вентилятора охлаждения (0x0A) и включить/отключить муфту кондиционера (0x0B).

Практически нигде не найти полные расшифровки данных производителей, тем более официальных. В лучшем случае это будут чьи-то изыскания в рамках реализации какой-то дополнительной функции. CAN sniffer может помочь в поиске этих данных. Я смог найти порядка 40 различных параметров автомобиля и ради эксперимента, на базе полученных данных, я сделал собственное управление вентилятором охлаждения.

Источник

Adblock
detector