Однажды встретились Orange PI 5, Heltect v3, свободное время и J4F и в Саратове появился второй LLM бот для Meshtastic. Сегодня расскажу как все это повторить если у вас в одном месте и в одно время появится примерно такое же.
Кратенько про Meshtastic. Сейчас у нас в Саратове по данным https://map.onemesh.ru/ 114 нод, по данным моей ноды - 150 из которых около ~40 постоянно онлайн). В качестве железа этого проекта используется стационарный Heltect v3 с увеличенной антенной закрепленный на окне и подключенный к WiFi и MQTT. Так как нода Meshtastic не умеет мультиконнект, то к ноде подключена интеграция Home Assistant которая умеет работать как прокси. Но это не обязательно, то же самое умеет meshmonitor, его можно запускать как угодно, даже есть инсталяторы под разные OS.
Eще забавный факт, вчера человек летел из Махачкалы с LILYGO T-Echo, судя по flightradar24 в 245км (в районе фролово) от Саратова на высоте примерно 10 км, и мы перекидывались сообщениям с ним почти до его подлета к Тамбову, и даже удалось перекинуться сообщениями с Пензой.
LLM нода - Orange PI 5 8G RAM c 513G m2 SSD. На нем крутится Ubuntu 22.04.5 c ollama и c закаченной моделькой phi4-mini
gals@orangepi5-8g:~ $ ollama list
NAME ID SIZE MODIFIED
phi4-mini:latest 78fad5d182a7 2.5 GB 2 weeks ago
Типичная трата ресурсов при обработке запроса из meshtastic такая


А теперь к основной части, самому скрипту бота на python который работает как прослойка между Meshatastic и Ollama . Вообще скрипт задумывался как тупой пересыльщик сообщений из Meshatastic в телегу, а так же записыватель базы всех сообщений и нод в sqlite3 базу. Там все тупо, скучно и не интересно. На интересных особенностях остановимся далее. Актуальная версия доступна на github, вместе с systemd service, env и requirements.txt
Пробежимся по интерeсным моментам
Кроме основного дефолтного канала у меня в Meshtastic настроены каналы со своими отдельными ключами шифрования. 1 - семья, 2 - друзья, они зашиты в константу CHANNELS
Так как в Meshtastic есть ограничение на размер в сообщении в 230 байт, притом что кириллица это 2 байта, а emoji могут быть больше, а так же потому что ресурсы llm ноды ограничены мы добавляем системный промпт "Ты чатбот для Meshtastic. Отвечай по-русски. Ответ строго <= 110 символов. Без списков, без пояснений, без приветствий." в функцию ollama_reply, так же туда же добавляем "temperature": 0.4, "top_p": 0.9, что бы ответ был быстрее. Так как LLM может выдать ответ длиннее чем можно, и сообщения тогда вообще не уйдет ответ еще проходит через функцию clamp_200 которая обрезает сообщение до 110 символов. У нас в Cаратове как-то сложилось что обе модели LLM отвечают только на сообщения которые начинаются с !llm и это тупо захардкожено прямо в скрипте.
Скрытый текст
def ollama_reply(prompt: str) -> str:
# Request to model to be very short
system = (
"Ты чатбот для Meshtastic. "
"Отвечай по-русски. "
"Ответ строго <= 110 символов. "
"Без списков, без пояснений, без приветствий."
)
payload = {
"model": OLLAMA_MODEL,
"prompt": f"{system}\n\nВопрос: {prompt}\nОтвет:",
"stream": False,
# Not speed up
"options": {
"temperature": 0.4,
"top_p": 0.9,
},
}Второй интересный момент - функция send_response. В meshtastic есть 3 вида текстовых сообщений, обычные, ответы (ака thread) и так же реакции на сообщения. При этом API sendText из API умеет только первый тип, для обхода этого ограничения send_response использует _sendPacket из API напрямую. Что забавно, в реакции можно пихать не только emoji но и текст. В скрипте для примера тоже захордкожены реакции на hi и пинг
Выглядит это примерно так

Говорят что это работает только в приложении на Android, а на IOS этого нет, но сам не проверял.
Собственно у нас в саратове 2 llm, одна моя, вторая вроде крутится на видюхе (к сожалению и модель и модель видюхи забыл), и сама посерьезнеe и побольше, на скринах дальше можно глянуть ответы и сравнить их скорость. sg-n это описываемая тут нода, а NWAY - другая. Ради забавы ради позадавал вопросы и на китайском.
Скрытый текст





По идее если добавить в скрипте в ответ бота !llm то можно устроить теоретически бесконечный диалог :) но канал не резиновый и небольшую рекурсию можно устроить и одним запросом, напоминаю что sg-h это бот

В целом модель phi4-mini ведет себя отлично, даже без ограничений которые наложены запросом из мештастик бота. Эта же модель подключена к телеграм боту основанному на вот этом проекте где ограничений нет, и там она вполне хорошо держит конекст в диалоге, выдает довольно релевантные ответы, но иногда может призадуматься.


Если, вдруг, вы дочитали до конца и у вас есть Home Assistant c настроенной интеграцией с Meshtastci, то вот вам бонусом HA автоматизация которая по запросу !w выдает текущую погоду в канал
Скрытый текст
alias: Meshatic Weather
description: ""
triggers:
- domain: meshtastic
type: channel_message.received
entity_id: meshtastic.gateway_sg_h_channel_primary
trigger: device
conditions:
- condition: template
value_template: "{{ \"!w\" in (trigger.event.data.message | lower) }}"
actions:
- action: weather.get_forecasts
metadata: {}
data:
type: daily
enabled: false
- delay:
hours: 0
minutes: 0
seconds: 12
milliseconds: 0
- action: meshtastic.broadcast_channel_message
metadata: {}
data:
ack: true
channel: meshtastic.gateway_sg_h_channel_primary
message: >-
Погода: ?️ {{state_attr('weather.home', 'temperature')
}}{{state_attr('weather.home', 'temperature_unit')}} ?
{{state_attr('weather.home', 'humidity')}}% ?
{{state_attr('weather.home', 'wind_speed')}}{{state_attr('weather.home',
'wind_speed_unit')}} / {{ ((state_attr('weather.home', 'wind_speed') |
float(0)) / 3.6) | round(1) }}m/s {% set deg =
state_attr('weather.home', 'wind_bearing') | float(0) %}{% set dirs = [
"С",
"СВ",
"В",
"ЮВ",
"Ю",
"ЮЗ",
"З",
"СЗ"
] %}{% set idx = ((deg + 22.5) // 45) | int %} ?↗️ {{ dirs[idx % 8]
}}({{ deg }})° ?️ {{state_attr('weather.home', 'cloud_coverage')}}%
☀️(uv_index) {{state_attr('weather.home', 'uv_index')}} ?️
{{state_attr('weather.home', 'pressure')}}{{state_attr('weather.home',
'pressure_unit')}}{% set hpa = state_attr('weather.home', 'pressure') |
float(0) %}{% set mmhg = (hpa * 0.750064) | round(0) %}{% if mmhg < 745
%}{% set level = "низкое" %}{% elif mmhg <= 765 %}{% set level = "норм"
%}{% else %}{% set level = "высокое" %}{% endif %}/{{ mmhg }}mm ({{
level }}). {% set t = states('weather.home') %}{% set map = {
'sunny': 'клево и солнечно',
'clear-night': 'Темно и ясно',
'cloudy': 'облачно немношк',
'partlycloudy': 'Иногда облачность',
'rainy': 'лужи и дождь',
'pouring': 'не забываем зонт, ливень',
'snowy': 'готовьте лыжи, снег',
'snowy-rainy': 'херовато, снег с дождём',
'windy': 'ветрено нафик',
'windy-variant': 'может сдуть нафик',
'fog': 'загадочно и туманно',
'hail': 'не забудьте каску, град',
'lightning': 'в укрвтие, гроза',
'lightning-rainy': 'Гроза с дождём'
} %}Воопщем - {{ map[t] if t in map else t }}
mode: singleв тексте выглядит это примерно так

rodial
Если хочется заморочиться и теоретически ускорить генерацию в 2 раза, можно попробовать запустить модель на NPU RK3588S, который может использовать до 4GB RAM. Как минимум снять нагрузку с процессора.
sergeygals Автор
Думал над этим, но вменяемых инструкций не нашел. На самом деле сначала я хотел заюзать orange pi rv2 у которого даже в названии Ai есть, но не получилось. Счас копаю эту тему поглубже
rodial
orange pi rv2 обещает 2TOPS
NPU в RK3588S обещает 6TOPS
По поводу запуска на NPU в RK3588S я смог накопать только что нужны специальные драйвера в ядре и они есть в ubuntu rockchip 24.04. У меня ubuntu rockchip 22.04 и нужных драйверов нет, переустановить её никак руки не дойдут поэтому как дальше не знаю, но самым простым способом запустить мне видится https://github.com/Pelochus/ezrknn-llm/
Попросил ИИ посчитать производительность модели на CPU RK3588S в сравнимых величинах, пишет что CPU RK3588S ~0.8 TOPS, но учётом квантизации полагаю скорость генерации будет сравнимой c orange pi rv2.
sergeygals Автор
Да, седня поковырял, ее тоже нашел, но не проперло, и поддерживает не все, например phi4-mini, которая лучше всех показала на pi 5 нету. модели переконвертировать надо.
Зато поигрался с orange pi rv2, тулза называется ky-ort там те же проблемы поддерживает не все, надо конвертить, а то что было и я попробовал, дико жрет проц и глючит
ну и еще один минус, все это запускается кастомными тулзами, то есть Ollama минус, а это значит код который работает с Ollama, скороее всего заиспользовать не получится и надо во что то другое переделывать, опять же не очень понятно как, c доками там не очень