Хакер - Змей-стражник. Сигнализация для велосипеда из старого смартфона на Android
hacker_frei
rexby
Содержание статьи
- Termux
- Termux:API
- Предварительная подготовка
- Пишем код
- Проходим сквозь стену
- Серверная часть
- Выводы
Наверняка у тебя где‑то завалялся старый смартфон на Android, а может, даже и не один. Я расскажу, как сделать из него надежную сигнализацию для твоего велосипеда, да и вообще для любого движимого и недвижимого имущества. Программировать будем на Python 3, при этом набор функций приложения без труда может быть изменен и расширен для решения других насущных задач.
Недавно я закончил сборку своего электровелосипеда. Теперь я могу не только колесить в близлежащих парках, но и использовать его в качестве полноценного средства передвижения. Скорость до 55 км/ч позволяет уверенно держаться в городском потоке машин, а бесшумность, отсутствие запаха бензина плюс небольшая масса — спокойно ездить по паркам и тротуарам, а также хранить транспорт дома. В общем, сплошные плюсы. Когда надо припарковаться и пойти по делам, на раме моего велосипеда защелкивается U-образный замок Kryptonite. Однако мотор и новенький аккумулятор уже не делают раму самой дорогой частью велосипеда. И поэтому остро встает вопрос защиты транспортного средства от тех, кто тоже следует за прогрессом, но не желает на него тратиться. Короче говоря, от воров.
Имеющиеся в продаже сигнализации для велосипедов просто отпугивают воришек громким звуком. Поэтому я решил сделать собственное устройство с обратной связью. Первое, что пришло в голову, — разработать сигнализацию на микроконтроллере. Но затем я подумал: почему бы не использовать старый смартфон, который лежит без дела? Ведь у него на борту уже есть все необходимое, чтобы решить поставленную задачу: мобильный интернет, GPS для определения местоположения, различные датчики для обнаружения внешних воздействий, камера и сенсорный экран. В общем, вопрос с железом был решен, осталось только реализовать программную часть. Чтобы лучше понять принцип работы описанного в статье устройства, можешь сразу посмотреть демонстрацию его работы.
TERMUX
Если ты еще не слышал про Termux, то срочно отправляйся изучать матчасть. «Хакер» уже писал на эту тему. Termux — это эмулятор терминала для операционной системы Android. Но пускай тебя не смущает слово «эмулятор», весь код выполняется нативно.
Как ты знаешь, в основе Android — ядро Linux. Termux добавляет к ядру минимальное окружение в виде набора утилит и интерпретатора Bash. Этот набор может быть легко расширен хорошо известной пользователям Linux командой apt install <package> (или pkg install <package>). При этом список пакетов достаточно обширный: от консольного редактора Vim до оконной системы X11 и среды рабочего стола Xfce. Все это работает без прав суперпользователя!
Кроме того, всегда есть возможность написать свою программу на С или даже языке ассемблера. Компилятор GCC также можно установить из пакетов. Если есть права root, все становится еще интереснее: можно подключить репозиторий root-repo и получить доступ к таким программам, как tcpdump или Aircrack-ng. Для наших же целей root-права не потребуются: все, что нам нужно, — это установить интерпретатор языка Python 3. К счастью, нужный пакет уже имеется в репозитории.
Termux я рекомендую установить из магазина софта с открытым исходным кодом F-Droid. Если у тебя в телефоне еще нет этого магазина, отправляйся на сайт f-droid.org и скачивай соответствующее приложение. Далее в самом F-Droid в строке поиска напиши Termux и установи следующие приложения: Termux, Termux:API и Termux:Widget. Если Android на твоем телефоне ниже седьмого, то тебе понадобится уже архивная версия. Ее ты можешь взять тут. После установки запускай Termux и инсталлируй необходимые пакеты и модули питона, которые нам понадобятся. Для этого в открывшемся терминале набери следующие команды:
pkg update
pkg install termux-api
pkg install openssh
pkg install qrencode
pkg install python
pkg install pip
pip install pystun3
Обрати внимание, что pystun3 в последней строке — это модуль питона, поэтому мы установили его командой pip install, а не pkg install.
TERMUX:API
Командой pkg install termux-api мы установили поддержку Termux-API, что позволяет взаимодействовать с системой Android из командной строки: делать фото, записывать и воспроизводить звук, подавать вибросигнал, получать информацию с датчиков телефона, выводить диалоговые окна и прочее. Чтобы изучить все возможности Termux-API, набери в командной строке termux-, нажми два раза Tab, и увидишь все доступные утилиты. Функциональности вполне достаточно, чтобы организовать взаимодействие с пользователем с помощью всплывающих сообщений (termux-toast), диалоговых окон (termux-dialog) и уведомлений (termux-notification).
Для сигнализации нам понадобится информация с датчика ускорения — акселерометра. Для этого посмотрим сначала, какие датчики присутствуют в телефоне:
termux-sensor -l
В полученном списке датчиков ищи тот, в названии которого есть слово accelerometer. Например, в моем телефоне имеется BMI160 Accelerometer и Linear Acceleration. Теперь можно попробовать опросить датчик такой командой:
termux-sensor -s "BMI160 Accelerometer" -n 1
В моем случае вывод этой команды имеет следующий вид:
{
"BMI160 Accelerometer": {
"values": [
0.35223388671875,
0.20556640625,
9.873580932617188
]
}
}
Обрати внимание на две вещи. Во‑первых, вывод информации по одному измерению занимает девять строк. В дальнейшем мы используем это для парсинга выходного потока с датчика ускорения. Во‑вторых, ускорение выводится в виде его компонентов по трем пространственным координатам. Если ты еще не забыл школьный курс физики, то уже сообразил, что сумма квадратов этих величин даст нам квадрат ускорения. Его‑то мы и будем вычислять для определения движения телефона.
Если в предыдущей команде опустить параметр -n, то данные с датчика будут выводиться непрерывно с интервалом в одну секунду. Интервал, в свою очередь, можно изменить, добавив ключ -d <число миллисекунд>. Этим мы и воспользуемся в программе. Датчик ускорения достаточно чувствительный, чтобы определить не только перемещение твоего девайса, но и вибрацию от легкого удара по столу, на котором лежит устройство.
ПРЕДВАРИТЕЛЬНАЯ ПОДГОТОВКА
Писать код можно прямо на телефоне, установив любой текстовый редактор, например Vim или Nano. Но проще делать это на компьютере, а затем отправить программу по SSH на телефон. Для этого в файл ~/.ssh/authorized_keys нужно положить свой ключ SSH. После чего в консоли Termux запустить демон sshd, введя одноименную команду. Учти, что демон sshd слушает нестандартный порт 8022, поскольку у него нет привилегий суперпользователя.
Теперь можно с ПК получить доступ к телефону командой ssh <IP-адрес телефона> -p 8022. Обрати внимание, что имя пользователя можно не указывать, Termux примет любое. Узнать IP-адрес телефона в твоей локальной сети можно, введя команду ifconfig в консоли Termux. Если ты пользователь Windows, то для подключения используй клиент Putty. Для пересылки файла программы из текущего каталога на компьютере в Termux достаточно в терминале на компе ввести такую команду:
cat program.py | ssh <IP-адрес телефона> -p 8022 "cat > program.py"
для Linux или такую для Windows:
set PATH=C:\путь\до\папки\putty;%PATH%
pscp -P 8022 program.py <IP-адрес телефона>
Если телефон подключен к домашней сети Wi-Fi, Termux не очень отзывчив при соединении по SSH. По всей видимости, это связано с переходом передатчика в телефоне в энергосберегающий режим. Если ты тоже столкнешься с такой проблемой, ее можно решить командой iw dev wlan0 set power_save off, выполненной от рута. А если таких прав нет, ты можешь подключиться к телефону либо по USB-кабелю, включив режим USB-модема, либо по Wi-Fi, переведя телефон в режим точки доступа.
ПИШЕМ КОД
Программа состоит из двух частей: клиентской и серверной. Клиент будет работать на телефоне, закрепленном на велосипеде, и периодически отправлять UDP-пакеты серверу — телефону, который находится рядом с тобой. В содержимом пакета может быть либо KNOCK, либо ALARM. Получив сообщение KNOCK, сервер будет знать, что клиент работает штатно и не имеет проблем с сетью. В случае сообщения ALARM будем проигрывать заранее подготовленный файл со звуком сирены.
Итак, приступаем к написанию клиентской части программы, которую назовем client_alarm.py. Сначала сделаем импорт необходимых модулей.
#!/data/data/com.termux/files/usr/bin/env python3
import socket
import json
from subprocess import Popen, PIPE
import os
import time
from nat import nat_traversal
Первая строка — это шебанг. Чтобы каждый раз не писать такой длинный путь, можешь использовать стандартный для Linux /usr/bin/env python3, а потом вызвать утилиту termux-fix-shebang client_alarm.py. Она исправит путь до программы env на подходящий для Termux. Далее зададим необходимые константы.
AVERAGE = 10
KNOCK_DELAY = 5
SENSOR_NAME = 'BMI160 Accelerometer'
ACCELEROMETER_DELAY = 20
ACCELERATION_THRESHOLD = 0.1
DELAY_AFTER_ALARM = 1
RPORT = 0
RHOST = ''
Здесь AVERAGE — число измерений ускорения, по которым мы будем вычислять среднее значение. Усреднение нужно для более стабильной работы, чтобы исключить ложные срабатывания сигнализации. KNOCK_DELAY — задержка в секундах между «отстукиванием» на сервер. ACCELEROMETER_DELAY — задержка в миллисекундах, которая будет передана команде termux-sensor. ACCELERATION_THRESHOLD — важный параметр, который определяет порог срабатывания сигнализации. Если сделать его слишком маленьким, то чувствительность возрастет, но при этом можно получить ложные срабатывания. Рекомендую подобрать этот параметр экспериментально так, чтобы добиться нужной тебе чувствительности. DELAY_AFTER_ALARM — пауза после каждого срабатывания сигнализации. Она нужна, чтобы не «засыпать» сервер пакетами с сигналом ALARM. RHOST и RPORT — адрес, куда будем отправлять пакеты.
И тут возникает проблема: чтобы организовать коммуникацию двух устройств по сети, хотя бы одно из них должно иметь публичный (еще говорят «белый») IP-адрес. Для этого нужно либо арендовать VPS-сервер, либо заказать соответствующую услугу у твоего интернет‑провайдера. После чего можно обеспечить прямую видимость устройств, подключив их к собственному серверу, например через OpenVPN или Wireguard. Как это сделать, «Хакер» писал здесь. Именно так я и сделал. Но если у тебя еще нет арендованного сервера, то не спеши расстраиваться. Я расскажу, как соединить два находящихся за NAT устройства по сети напрямую.
Проходим сквозь стену
NAT (network address translation) — это механизм трансляции сетевых адресов, который позволяет нескольким устройствам подключаться к хостам в интернете, используя один общий IP-адрес. На сегодняшний день именно технология NAT дает возможность более чем 20 миллиардам устройств по всему миру иметь доступ в глобальную сеть, при том что «белых», то есть маршрутизируемых в интернете, IPv4-адресов всего чуть более четырех миллиардов.
У разных провайдеров NAT может быть настроен по‑разному. Для соединения устройств мы будем использовать STUN (Session Traversal Utilities for NAT) — специальный сетевой протокол, который позволяет клиенту за NAT узнать свой внешний IP-адрес, а также способ трансляции порта во внешней сети. В данном случае за нас все сделает функция get_ip_info() модуля pystun3, который мы установили ранее.
При вызове данной функции будет сделано несколько запросов на разные адреса серверов STUN. В ответах на запросы сервер вернет информацию о том, с какого IP-адреса и порта к нему обращались. Для наших целей достаточно, чтобы при обращении на определенный порт разных хостов сети с одного и того же внутреннего порта провайдер назначал нам один и тот же внешний порт. Такой тип NAT называется Restricted NAT. Если же при обращении к разным хостам каждый раз будут назначаться разные внешние порты, то подключение извне к такому устройству вызовет серьезные трудности. Такой тип NAT называется Symmetric NAT.
Из проверенных мной операторов «большой четверки» симметричный NAT оказался только у «Билайна». Так что если ты обладатель сим‑карты других операторов связи, то все должно получиться.
INFO
Статья про разные типы NAT и способы их обхода
Итак, для прямого соединения двух телефонов напишем функцию nat_traversal. Ее можно поместить в отдельный файл nat.py, так как эта функция будет общая для обеих программ — клиентской и серверной. После чего необходимо импортировать функцию командой from nat import nat_traversal.
def nat_traversal():
from socket import socket, AF_INET, SOCK_DGRAM, timeout
import stun
import json
import os
import sys
import subprocess
import time
LPORT = 65000
LHOST = '0.0.0.0'
nat = stun.get_ip_info(LHOST, LPORT)
nat_json = json.dumps(nat)
os.system("clear")
os.system(f"echo '{nat_json}' | qrencode -t ansiutf8")
print(f"Your host parameters: {nat}")
os.system("termux-clipboard-set '' ")
print("Please, copy remote host info to your clipboard ...")
remote_nat = ''
for i in range(12):
cb = subprocess.getoutput('termux-clipboard-get')
try:
remote_nat = json.loads(cb)
if remote_nat:
break
except json.decoder.JSONDecodeError:
pass
time.sleep(5)
else:
print("Error: can't get remote host info", file=sys.stderr)
sys.exit(1)
print(f"Got remote host parameters: {remote_nat}")
fnat_type, rip, rport = remote_nat
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind((LHOST, LPORT))
sock.settimeout(5)
for i in range(12):
try:
sock.sendto(b'NAT\n', (rip, rport))
sock.recv(1024)
except timeout:
pass
else:
sock.sendto(b'NAT\n', (rip, rport))
break
else:
print(f"Error: can't connect to remote host {rip}:{rport}", file=sys.stderr)
sys.exit(1)
print(f"Connection established with {rip}:{rport}")
sock.settimeout(None)
return rip, rport, sock
Функция nat_tarversal первым делом вызывает функцию get_ip_info модуля pystun3, которая вернет три значения: тип NAT, внешний IP-адрес и внешний порт, соответствующий нашему внутреннему порту LPORT. После определения внешнего IP-адреса и порта на обоих телефонах необходимо произвести обмен этой информацией между устройствами. Для этого будем выводить в консоль результат работы функции get_ip_info в виде QR-кода.
В этом нам поможет установленная ранее утилита qrencode. После вывода QR-кода будем ожидать данных о втором устройстве в буфере обмена. Для этого предварительно очистим его с помощью termux-clipboard-set ''. Если в течение примерно минуты в буфере не появится нужных данных, то функция выведет ошибку и завершит работу программы. Таким образом, чтобы соединить по сети два наших устройства, на каждом из них после запуска программы необходимо с помощью сканера прочитать QR-код с экрана другого телефона.
Для этих целей можешь использовать опенсорсный сканер с F-Droid или скачать аналог с Play Market. В настройках приложения поставь галочки «Копировать в буфер обмена» и «Автозапуск сканера» для удобства использования. Недостаток данного метода в том, что в момент подключения оба устройства должны располагаться рядом, чтобы иметь возможность отсканировать QR-коды. Ты без труда можешь использовать любой другой канал для обмена, например СМС с помощью команд termux-sms-send и termux-sms-list.
Если данные о втором хосте были успешно прочитаны из буфера обмена, попытаемся установить соединение с указанным хостом в течение минуты. Для этого периодически будем отправлять в адрес второго хоста UDP-пакеты со строкой NAT. Первые отправленные пакеты с одного из устройств (клиента или сервера) сначала не будут достигать назначения, поскольку в таблице NAT на принимающей стороне еще нет правила, определяющего, куда передавать пришедшие пакеты. Но как только со второй стороны будет передан хотя бы один пакет, двусторонняя связь будет установлена. Функция nat_traversal отчитается об этом выводом сообщения Connection established with host:port и вернет в основную программу адрес и порт удаленного хоста, а также созданный ею сокет.
Стоит сказать, что соединение устройств «напрямую», минуя промежуточные хосты, часто используется в IP-телефонии, например когда ты говоришь по WhatsApp. Такой подход позволяет снизить нагрузку на сервер, а также существенно уменьшить сетевой маршрут для пакетов данных.
В основной программе вызываем функцию termux-wake-lock, которая предотвращает «засыпание» приложения Termux. Затем создадим сокет, используя пару RHOST:RPORT, а если она не задана, то вызовем только что написанную функцию nat_traversal():
os.system('termux-wake-lock')
if RHOST and RPORT:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
else:
RHOST, RPORT, sock = nat_traversal()
Далее добавим в клиентскую часть программы функцию для получения значения ускорения.
def sensor_get():
one_measure = ''
while not one_measure:
for i in range(9):
one_measure += p.stdout.readline().decode('utf-8')
if one_measure == '{}\n':
one_measure = ''
break
if one_measure:
data = json.loads(one_measure)
a = data[SENSOR_NAME]['values']
return a
Функция sensor_get() получает значение ускорения из стандартного вывода команды termux-sensor, которую мы запустим в отдельном потоке:
os.system('termux-sensor -c')
p = Popen(['/data/data/com.termux/files/usr/bin/termux-sensor', '-s', SENSOR_NAME, '-d', str(ACCELEROMETER_DELAY)], stdin=PIPE, stdout=PIPE)
Вызов termux-sensor -c подготовит датчики в случае, если раньше работа с ними была завершена некорректно.
Далее в бесконечном цикле мы будем получать AVERAGE измерений с датчика ускорения и каждый раз будем вычислять da — разницу текущего и предыдущего значений (если быть точным, da — это квадрат разности векторов ускорений: текущего и предыдущего). Вычисленные значения da будем складывать в переменную acceleration. Если после усреднения (acceleration /= AVERAGE) ускорение превысит предел, заданный в ACCELERATION_THRESHOLD, то на сервер будет отправлена строка ALARM и сделана задержка, чтобы дать серверу время отреагировать на пришедшее сообщение.
Если же с телефоном ничего не происходит, то все равно по прошествии времени KNOCK_DELAY мы будем отправлять на сервер строку KNOCK, чтобы он «знал», что сигнализация и сеть работают исправно. В случае прямого соединения устройств периодическая отправка пакетов на сервер, кроме всего прочего, необходима, чтобы из таблицы NAT провайдера не исчезло правило трансляции, созданное функцией nat_traversal. В ответ сервер будет отправлять сообщение OK, которое также служит для поддержания правила NAT на другой стороне.
a_prev = sensor_get()
print('sensor ready!')
t_prev = time.time()
while True:
try:
acceleration = 0
for i in range(AVERAGE):
a = sensor_get()
da = sum( (ai - a_prev_i)**2 for ai, a_prev_i in zip(a, a_prev))
acceleration += da
a_prev = a
acceleration /= AVERAGE
if acceleration > ACCELERATION_THRESHOLD:
sock.sendto(b'ALARM\n', (RHOST, RPORT))
print('ALARM')
time.sleep(DELAY_AFTER_ALARM)
t_cur = time.time()
if t_cur - t_prev > KNOCK_DELAY:
sock.sendto(b'KNOCK\n', (RHOST, RPORT))
print('KNOCK')
t_prev = t_cur
except KeyboardInterrupt:
break
p.send_signal(2)
sock.close()
Завершить работу программы можно, нажав Ctrl + C. После чего мы выйдем из бесконечного цикла чтения данных с датчика ускорения, а процессу, опрашивающему датчик, будет послан сигнал 2 (SIGINT).
На этом клиентская программа завершена. Осталось написать серверную часть, которую мы назовем server_alarm.py.
СЕРВЕРНАЯ ЧАСТЬ
Начнем с импорта модулей и задания констант.
#!/data/data/com.termux/files/usr/bin/env python3
import threading
import socket
import time
import os
from nat import nat_traversal
LPORT = 0
LHOST = '0.0.0.0'
WAIT_FOR_KNOCK_TIME = 15
DIR_NAME = '/data/data/com.termux/files/home/alarm/'
ALARM_FILE_NAME = 'alarm_short.mp3'
LINK_LOSS_FILE_NAME = 'link_loss.mp3'
LINK_RESTORE_FILE_NAME = 'link_restored.mp3'
KNOCK_FLAG = 0
KNOCK_WARN = 1
Здесь, по аналогии с клиентской программой, можешь задать LPORT, отличный от нуля, и LHOST = '0.0.0.0', тогда сервер будет слушать заданный порт на всех сетевых интерфейсах. Либо оставь LPORT нулевым, тогда будет вызвана функция nat_traversal. Параметр WAIT_FOR_KNOCK_TIME задает интервал, в течение которого должен поступить очередной пакет KNOCK от клиента. Если пакет не придет вовремя, будет сделано оповещение, что связь с клиентской программой потеряна.
Параметр DIR_NAME задает путь к папке со звуковыми файлами, а ALARM_FILE_NAME, LINK_LOSS_FILE_NAME и LINK_RESTORE_FILE_NAME — названия самих файлов для оповещения о тревоге, потере и восстановлении связи соответственно.

Принимать UDP-пакет будем в отдельном потоке. Для этого создадим класс — наследник threading.Thread:
class UDP_receive(threading.Thread):
def run(self):
global KNOCK_FLAG
while True:
data, addr = sock.recvfrom(1024)
if data == b'KNOCK\n':
print(data.decode('utf-8'), end = '')
KNOCK_FLAG = 0
sock.sendto(b'OK\n', addr)
elif data == b'ALARM\n':
print(data.decode('utf-8'), end = '')
sock.sendto(b'OK\n', addr)
os.system('termux-media-player play ' + DIR_NAME + ALARM_FILE_NAME)
else:
print('Unknown data:')
print(data.decode('utf-8'))
В случае приема сообщения KNOCK будем очищать флаг KNOCK_FLAG, а в случае получения ALARM сразу бить тревогу: воспроизводить файл со звуком сирены с помощью команды termux-media-player play.
В основной части программы вызовем termux-wake-lock, чтобы в фоне или при выключенном экране наше приложение оставалось активно. Создадим сокет и поток, в котором будем его слушать.
os.system('termux-wake-lock')
if LHOST and LPORT:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((LHOST, LPORT))
else:
RHOST, RPORT, sock = nat_traversal()
thread = UDP_receive()
thread.start()
Далее создадим основной цикл программы, выйти из которого можно c помощью комбинации Ctrl + C.
print('Starting alarm server... ')
while True:
try:
KNOCK_FLAG = 1
time.sleep(WAIT_FOR_KNOCK_TIME )
if KNOCK_FLAG:
KNOCK_WARN = 1
print('Связь потеряна')
os.system('termux-media-player play ' + DIR_NAME + LINK_LOSS_FILE_NAME)
else:
if KNOCK_WARN:
KNOCK_WARN = 0
print('Связь установлена')
os.system('termux-media-player play ' + DIR_NAME + LINK_RESTORE_FILE_NAME)
except KeyboardInterrupt:
break
sock.close()
os.kill(thread._native_id, 15)
Флаг KNOCK_WARN устанавливается, если пользователь уже был оповещен о потере связи. Когда связь восстановится (если придет очередной пакет KNOCK), пользователь получит сообщение об этом. В конце программы закроем сокет и «убьем» процесс UDP_receive().
Все, обе части программы готовы. Копируй их на телефоны, не забыв дать права на выполнение командами chmox +x client_alarm.py и chmod +x server_alarm.py. Также запиши на оба устройства файл nat.py, а в папку DIR_NAME на телефоне‑сервере положи звуки тревоги и оповещений. Можешь сохранить программы в папку .shortcuts, предварительно ее создав:
mkdir -p /data/data/com.termux/files/home/.shortcuts
chmod 700 -R /data/data/com.termux/files/home/.shortcuts
В таком случае можно будет поместить на рабочий экран телефона виджет с нашей программой для быстрого запуска. Для этого воспользуйся меню настройки виджетов в твоем телефоне.

ВЫВОДЫ
Думаю, ты убедился, что Termux в умелых руках — это мощный инструмент для программирования Android-смартфона. Код программ и звуковые файлы я выложу на GitHub. Используй их как готовое решение или дополни нужными возможностями. Например, с помощью termux-location получай GPS-координаты и отправляй их на сервер. Либо используй вместо датчика ускорения датчик освещенности или приближения.
Кроме того, можно в случае тревоги делать фото с камеры (для этого есть скрипт termux-camera-photo). А также совершать звонок на заданный номер телефона, используя termux-telephony-call, или отправлять SMS (termux-sms-send). Прямой способ соединения устройств, находящихся за NAT, можешь смело использовать в других своих сетевых приложениях. В общем, давай волю своей фантазии и не бойся экспериментировать. Удачи!
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei