Карта покрытия и радиоузлов Казани
Отображаются узлы с переданными координатами GPS или фиксированной позицией
REST API для ботоводов и разработчиков
Открытый высокоскоростной Edge API на базе Cloudflare D1. Подключайте Telegram-боты, городские радиомониторы, аналитические скрипты и свои картографические сервисы.
https://mesh.aidns.ru
!Hex или Int ID
Возвращает полный паспорт узла по его идентификатору. Поддерживает форматы: Hex с восклицательным знаком (!401a11d0), чистый Hex (401a11d0), короткий 4-значный Hex (11d0) или целочисленный ID (1075450320).
curl -s "https://mesh.aidns.ru/api/node/!401a11d0"
Выполняет быстрый полнотекстовый поиск по всей базе (позывной, имя, Hex, локация QTH) с поддержкой пагинации и сортировки.
Возвращает общую статистику: количество узлов в базе, сколько зафиксировано в радиоэфире, число узлов с прямым приёмом (0 hops) и топ используемых плат (Heltec, T-Beam, Cardputer и др.). Идеально для команды бота /stats.
curl -s https://mesh.aidns.ru/api/stats
Возвращает все узлы с координатами GPS, их ролью, уровнем сигнала (SNR) и числом прыжков. Готовый датасет для подключения в собственные интерактивные карты (Leaflet, Mapbox, QGIS) или для гео-уведомлений.
curl -s https://mesh.aidns.ru/api/map
Готовый шаблон Telegram-бота для Казани
Пример на Python (aiogram 3) с командами /whois, /stats и поиском
Ниже приведён готовый, самодостаточный скрипт бота. Бот умеет мгновенно определять владельца ноды по команде /whois !401a11d0, искать узлы по ключевым словам и выдавать свежую сводку по радиосети.
# pip install aiogram aiohttp
import asyncio
import aiohttp
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
BOT_TOKEN = "ВАШ_ТОКЕН_ИЗ_BOTFATHER"
API_BASE = "https://mesh.aidns.ru/api"
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()
# 1. Команда /whois
@dp.message(Command("whois"))
async def cmd_whois(message: types.Message):
args = message.text.split(maxsplit=1)
if len(args) < 2:
return await message.reply("Использование: /whois !401a11d0 или /whois 1075450320", parse_mode="HTML")
node_query = args[1].strip()
async with aiohttp.ClientSession() as session:
async with session.get(f"{API_BASE}/node/{node_query}") as resp:
if resp.status == 200:
data = await resp.json()
n = data.get("node", {})
name = n.get("community_name") or n.get("long_name") or n.get("short_name") or "Без имени"
snr = f"{n.get('last_snr')} dB" if n.get('last_snr') is not None else "нет данных"
hops = f"{n.get('last_hops')} hop(s)" if n.get('last_hops') is not None else "—"
hw = n.get("hw_model") or "Не указано"
qth = n.get("qth") or "Не указан"
text = (
f"📡 Паспорт узла {n.get('node_id_hex')}\n\n"
f"👤 Позывной: {name}\n"
f"🏷 Оборудование: {hw} ({n.get('role')})\n"
f"📍 QTH / Локация: {qth}\n"
f"📶 Сигнал шлюза: {snr} ({hops})\n"
f"🕒 Был в сети: {n.get('last_seen') or n.get('first_seen')}\n\n"
f"🌐 Открыть в атласе Казани"
)
await message.reply(text, parse_mode="HTML")
else:
await message.reply(f"❌ Узел {node_query} не найден в реестре Казани.", parse_mode="HTML")
# 2. Команда /stats
@dp.message(Command("stats"))
async def cmd_stats(message: types.Message):
async with aiohttp.ClientSession() as session:
async with session.get(f"{API_BASE}/stats") as resp:
if resp.status == 200:
s = await resp.json()
text = (
f"📊 Радиосеть Meshtastic Казань\n\n"
f"👥 Всего нод в реестре: {s.get('total')}\n"
f"📻 Зафиксировано в эфире: {s.get('radio_total')}\n"
f"⚡ Прямой приём (0 hops): {s.get('direct_hops')}\n"
f"⚠️ Требуют настройки (роутеры): {s.get('warnings')}\n\n"
f"🗺 Карта покрытия Казани"
)
await message.reply(text, parse_mode="HTML")
async def main():
print("Бот запущен...")
await dp.start_polling(bot)
if __name__ == "__main__":
asyncio.run(main())