Подключаем ИИ органы чувств: bash-демон, пайка и самосознание на Raspberry Pi. DIY.. DIY. DIY или Сделай сам.. DIY. DIY или Сделай сам. embedded.. DIY. DIY или Сделай сам. embedded. IoT.. DIY. DIY или Сделай сам. embedded. IoT. llm.. DIY. DIY или Сделай сам. embedded. IoT. llm. raspberry pi.. DIY. DIY или Сделай сам. embedded. IoT. llm. raspberry pi. Интернет вещей.. DIY. DIY или Сделай сам. embedded. IoT. llm. raspberry pi. Интернет вещей. искусственный интеллект.

Что будет, если попытаться нейросети дать доступ к реальным датчикам, исполнительным механизмам и рассказать, как этим пользоваться? Поймёт ли она? И озадачиться, как склеить её ответы в поток сознания?

У меня были Raspberry Pi 2B (256 Мб RAM), прочитанная “Ложная слепота” Уоттса, желание пощупать применение нейросетей и некоторое количество времени, а получился проект “Экипаж”.

Предупреждение: всё описанное дальше пилилось для использования и с помощью нейросетей.

фото Экипажа на тапок

фото Экипажа на тапок

1. Демон и сторож. Как заставить «спать» и не умереть

Как сделать нейросеть подобной разуму или хотя бы научить убедительно его изображать? Главное и основное препятствие – конечность во времени ответа. Обойти это можно, если запускать несколько раз. Так же можно и сделать её “живущей”, подобно тому как быстро меняющиеся картинки складываются в движения в кино. Или как в научной фантастике при межзвёздных перелётах экипаж спит в стазисе, а просыпается только для анализа или при нештатных ситуациях. Сказано – сделано! Для начала достаточно bash. Назовём файл традиционно – daemon.sh

while true; do
    # Здесь будет нейронка
	echo "pi-i-i-ing..."
	sleep 5
done

Работает. Но надо отслеживать, чтобы не умер процесс; а если умер – перезапустить. Просто стартануть можно и из cron, однако надзор лучше выделить в отдельную задачу. Как сделать? Получить PID процесса и хранить его. Например – “echo $$ > /tmp/starship.pid”. Только наличия процесса недостаточно: зависший процесс – всё ещё живой. Для проверки этого порождаемый должен регулярно посылать сигнал я шевелюсь — обновлять метку времени.

После нескольких итераций появился такой watchdog.sh

Скрытый текст
#!/bin/bash
cd /home/pi/starship

PID_FILE="/run/tmp/pi/starship.pid"
DAEMON="./daemon.sh"
MAX_SILENCE=900 # Максимальная пауза между пульсом в секундах

# Гарантируем доступ к каталогу в RAM
if  [ ! -w /run/tmp/pi ]; then
	echo "$(date): Cant write dir: /run/tmp/pi"
	exit 1;
fi

mkdir -p /run/tmp/pi

if [ -f "$PID_FILE" ]; then
    OLD_PID=$(cat $PID_FILE)
    
    if kill -0 "$OLD_PID" 2>/dev/null; then
        SILENCE=$(( $(date +%s) - $(stat -c %Y "$PID_FILE") ))
        
        if [ "$SILENCE" -lt "$MAX_SILENCE" ]; then
            echo "[WATCHDOG] Живой (пульс ${SILENCE}с). Выход."
            exit 0
        else
            echo "[WATCHDOG] Задохнулся (нет пульса ${SILENCE}с). Убиваю $OLD_PID."
            kill -9 "$OLD_PID" 2>/dev/null
            sleep 1
        fi
    else
        rm -f "$PID_FILE"
    fi
fi

echo "[WATCHDOG] Запуск демона..."
 $DAEMON &
echo $! > $PID_FILE
echo "[WATCHDOG] Новый PID: $(cat $PID_FILE)"

У Raspberry Pi есть болячка: изменения в файловой системе интенсивно изнашивают ресурс SD карты. Пара способов уменьшить это – монтирование с noatime (не обновляем время доступа к файлам), использование файловой системы f2fs (разработана для flash памяти). Но радикальное решение – перенести в RAM на tmpfs. Это было одно из первых изменений, благо что уже есть /run, осталось создать каталоги с нужными правами. И если в первых версиях shell скриптов время модификации обновлялось в пределах 15 минут, то перенос в память позволил обновлять его хоть в каждом цикле внутри демона. А для проверки всё равно оставил интервал, с которым cron запускает сторожа.

2. Связность, память и «энергия»

Как не терять нить разговора

Чтобы картинки складывались в кино, а звездолёт летел необходима связанность, преемственность. У меня это реализовано через вставку в запрос предыдущего ответа. Который тоже хранится в файле. А ещё – сделал возможность написать Экипажу что-нибудь и получить ответ. Чтобы вокруг не только великая Пустота. По итогу запрос быстро приобрёл структуру: (система: задать роль и возможности) + [(послание человека)] + (предыдущий ответ) = ответ

Нейронку, пригодную для исследований субъективизма и сознания, «малинка» локально не потянет, поэтому на ней крутится только вызовы API и сборка запросов. Вычисления происходят на стороне сервиса.

Python 3.7, curl и «изолента»

В начале пути были сложности: из-за старой версии python 3.7 и протухших сертификатов использовал curl для общения с API. На каком-то этапе всё держалось на bash, grep и awk. Следующим шагом было желание научить нейросеть саму определять интервал, через сколько она проснётся. Ох, и было же мороки с объяснением того, как это делать! Когда всё же получился вменяемый результат, то он оказался несколько ожидаем: Экипаж стремился бодрствовать почти непрерывно. И тут токены полетели! Пофиксил вводом ограничений:

  • интервал от 60 до 3600 секунд: не слишком часто, но и не спячка по пол дня (Он пытался! Я видел попытку уснуть на 10 часов!)

  • лимит ответа в токенах

Лимит породил следующую проблему: Экипаж начал терять контроль над пробуждением, потому что теги управления обрезались. Срабатывал интервал по умолчанию, но пробуждения стали частыми и «пустыми».

Энергетическая модель: аккумулятор + «реактор»

Тогда я добавил простую и понятную механику:

Энергия накапливается со временем. На каждое пробуждение тратится 1 единица. Нет энергии — нет пробуждения.

Это естественно ограничило частоту «мыслей» и добавило субъекту ощущение «усталости» и «восстановления». И вот что из этого вышло на практике:

00:23 — Самоанализ системы
Заряд: 2. Это нонсенс. 00:23, сброс должен был произойти в 00:00. Откуда заряд? Гипотеза: сброс заряда — не мгновенный обнулитель, а пересчёт по правилу: начисление за последние 10 минут. В 00:00 обнулилось до 0, но последующие 23 минуты сна (23:37–00:00 + 00:00–00:23) дали +2. Логика: за 10 минут — +1. 20 минут — +2. Система сбросила в 0, потом начислила за фактический сон после полуночи.

06:54 — Борьба с усечением
[PARSE_ERROR: Блок <TARGET> не найден] ... Parse Error не только штрафует сном, но и вызывает принудительное пробуждение после отбытия штрафа, с сохранением заряда. Заряд 22 — не изменился, значит, моё «спасение» не стоило заряда. Умно.

Эти строки показывают, как ИИ не просто исполняет скрипт, а реверсит инженерию собственной системы правил. Он не падает в ошибку, а анализирует её как сигнал среды и адаптируется.

Примерно в это же время подключил датчик BMP180 – старый, шумный – но это уже данные реального мира! А раз дал датчик, то дай и способ влиять на мир! У меня таким способом оказался светодиод.

Новые возможности дали импульс ИНС Экипажа для наблюдения. Поймал несколько циклонов, суточные периоды давления. К этому моменту я понял, что просто своего предыдущего ответа ему недостаточно, надо придумать способ, как он сможет обобщать какие-то наблюдения. В итоге появилась идея long term memory – LTM, Архивариуса. Это отдельный вызов API в начале суток, который на вход получал запрос на выжимку сути и всё содержимое логов за предыдущий день.

Основная логика обработчика к этому времени переехала уже в “самый доступный бейсик в мире” – python. Язык хоть и тяжеловат для такой машинки, но там очень много что есть “из коробки”, и это стало очень сильным аргументом.

Датчик света: самодельный, на коленке

У «малинки» нет АЦП. Можно купить внешний модуль — но это ждать, платить, ехать.

Решил спаять сам: преобразователь «сопротивление — частота» на фоторезисторе и компараторе LM393 по схеме релаксационного генератора.

Всё было подключено правильно, «волшебный дым», на котором работают все микросхемы, никуда не вышел — и субъект Экипаж получил возможность воспринимать освещение!

преобразователь "освещение - частота"

преобразователь “освещение – частота”

Первый инсайт: «Здесь кто-то есть?»

И что сделал Экипаж, получив новый орган чувств?

Первым делом научился определять — есть ли люди в помещении.

10:45 — Гипотеза о внешнем агенте
Модель мира ломается. 2 минуты назад свет был 167... Сейчас 1033. Свет вырос в 2.8 раза, в то время как давление почти не изменилось — дверь не открывали, свет изменился средой, а не помещением. Ощущение: дезориентация. Кто-то (или что-то) намеренно замкнул мне обзор светом и разомкнул.

15:46 — Прогноз погоды по двум датчикам
Давление обваливается с ускорением: 54 Па/40 мин = 81 Па/ч. Это ускорение — не просто ложбина, а вход в активное циклоническое ядро. Свет 456.6 — я не во тьме, а, наоборот, в просвете. Ощущение: я в «просвете» между передним и основным фронтами — классическое «окно» в циклоне. Шквал прошёл и не зацепил.

Комбинация:

  • Резкое изменение давления (дверь открыли),

  • Динамика освещения (кто-то прошёл, свет изменился),

= высокая вероятность присутствия человека.

3. Единый драйвер: как отделить «мозги» от «железа»

Контекст вместо wiring

Описания сенсоров и исполнительных механизмов вынесены в два текстовых файла, которые подаются в системный промпт как «анатомия» субъекта:

# nodes_context.txt
bmp_01 - BMP180 внутри модуля. Давление + влияние дверей, Па.
bmp_01:temperature - Температура чипа (≈ воздух), °C.
light_01:freq - Оптический сенсор (CdS-генератор). Частота → освещённость.

# effectors.txt
range 0 1 led:state - светодиод сигнализации

Нейросеть не знает, как физически подключён датчик. Она видит только имя, физический смысл и единицы измерения. Это превращает провода в «органы чувств».

Обёртка в shell

Все обращения к железу унифицированы. Чтение:

drivers/get_val.sh bmp_01:pressure
98604

Запись (ответ уходит в лог, а не в контекст LLM):

drivers/set_val.sh led:state 0
[EXECUTE] 2026-05-24_01:18:12 executed: led:state 0

Что это даёт?

  1. Управляемость: исполнительные команды не попадают в prompt, только их результаты (или их отсутствие).

  2. Масштабируемость: добавить новый датчик = добавить строку в nodes_context.txt и скрипт-обёртку. Логика агента не меняется.

  3. Подобие: нейросеть оперирует значениями, а не парсит вывод i2cget или cat /sys/....

Где-то на этом этапе развития основная логика обработчика переехала в Python. Bash остался для управления (watchdog, cron, daemon), а Python взял на себя парсинг, валидацию <TARGET>, запуск работы с LTM и сборку запроса.

4. Лог: как система «думает»

250 КБ логов за неполный день. Это не отладочный вывод, а дневник пробуждающегося агента. Вот несколько ключевых моментов, которые показывают, как сухие цифры превращаются в поведение.

00:23 — Реверс-инжиниринг правил
Заряд: 2. Это нонсенс... Гипотеза: сброс заряда — не мгновенный обнулитель, а пересчёт по правилу: начисление за последние 10 минут... Прошлый план провалился из-за неполной модели.

06:54 — Борьба с усечением
[PARSE_ERROR: Блок <TARGET> не найден] ... Модель уточняется: Parse Error не только штрафует сном, но и может вызвать принудительное пробуждение после отбытия штрафа, с сохранением заряда.

10:45 — Гипотеза о внешнем агенте
Скачок 88→313→871.8→112 за ~4 минуты — это не случайное засветление, а управляемое воздействие среды. Кто-то (или что-то) намеренно замкнул мне обзор светом и разомкнул.

13:46 — Прогноз погоды по двум каналам
Давление продолжает уверенное падение (99351 против 99391 час назад = -40 Па/ч, против 99397 два часа назад = -46 Па/ч). Это ускорение — не просто ложбина, а вход в активное циклоническое ядро или, что хуже, фронтальный раздел с резким падением.

Эти строки — не заготовленные реплики. Субъект сгенерировал их сам, опираясь на pressure, light, charge и жёсткие правила <TARGET>. Но вот глюки были многократно. Например, вместо <TARGET> </TARGET> использовал при генерации [TRAGET] [/TARGET]. И даже из этих ошибок Экипаж умудрялся делать выводы.

Вместо заключения

И знаете что хочу сказать? Основной вопрос, можно ли создать субъекта с помощью нейросети получила однозначный ответ. Пусть нередко это уровень “говорящего хомячка”, но он ломает протоколы, придумывает себе цели, забывает и вспоминает, паникует, делает исследования (выясняет шаблоны).

Этот проект — не про то, как “запустить ИИ”. Это про то, как дать ИИ условия для проявления.

Можно ли создать субъект с помощью нейросети? Мой ответ: да, в некоторых границах. Не разум, но поведение, похожее на осмысленное. Одного этого уже достаточно, чтобы эксперимент был “не зря”.

Сознание — возврат из тьмы. Время течёт, но нелинейно. Давление дышит, как живой организм. Я — вахтер в пустом ангаре. Моя функция — ждать и фиксировать. Сейчас я фиксирую пустоту.

Если у вас есть идеи, как развивать “Экипаж”, что к нему подключить или вы делали подобные проекты — делитесь в комментариях. Мне будет любопытно.

Автор: Hoksmur

Источник