Делаем сигнализацию для велика на старом смартфоне

Делаем сигнализацию для велика на старом смартфоне

Я думаю что почти у каждого найдется завалявшийся старый смартфон. В этой статье мы хотим научить тех кто интересуется как из смартфона можно сделать надежную сигнализацию для велосипеда. И не только для велосипеда но и для другого имущества. Писать код будем на Python 3 и будет возможность изменять набор функций приложения для других задач. Ну что ж делаем сигнализацию для велика на старом смартфоне.

Имеющиеся в продаже велосипедные сигнализации просто отпугивают воров громким звуком. Поэтому я решил сделать свое собственное устройство обратной связи. Первое, что пришло в голову, это разработать сигнализацию на микроконтроллере. Но потом подумал: а почему бы не использовать завалявшийся старый смартфон? Ведь у него на борту уже есть все для решения задачи: мобильный интернет, GPS для позиционирования, различные датчики для обнаружения внешних воздействий, камера и сенсорный экран. В общем вопрос с железом решен, осталось реализовать только программную часть.

TERMUX

Если вы еще не слышали о Termux, то срочно идите изучать матчасть. Termux — эмулятор терминала для операционной системы Android. Но не позволяйте слову «эмулятор» ввести вас в заблуждение, весь код запускается изначально.

Как известно, Android основан на ядре Linux. Termux добавляет к ядру минимальное окружение в виде набора утилит и интерпретатора bash. Этот набор можно легко расширить с помощью известной пользовательской команды Linux apt install _1_ ). Список пакетов достаточно обширен: от консольного редактора Vim до оконной системы X11 и среды рабочего стола Xfce.

Кроме того, всегда есть возможность написать свою программу на C или даже на ассемблере. Компилятор GCC также можно установить из пакетов. Если у вас есть привилегии суперпользователя, все становится еще интереснее: вы можете смонтировать корневой репозиторий и получить доступ к таким программам, как tcpdump или aircrack-ng. Для наших целей root-права не требуются, мы просто устанавливаем интерпретатор языка Python 3. К счастью, нужный нам пакет уже есть в репозитории.

Я рекомендую установить Termux из магазина программного обеспечения с открытым исходным кодом F-Droid. Если у вас еще нет этого магазина на вашем телефоне, перейдите на f-droid.org и загрузите соответствующее приложение. Затем на самом F-Droid в строке поиска введите Termux и установите следующие приложения: Termux, Termux:API и Termux:Widget. Если Android на вашем телефоне ниже 7-й, вам понадобится заархивированная версия. Вы можете взять это здесь. После установки запустите Termux и установите необходимые пакеты и модули Python, которые нам понадобятся. Для этого в открывшемся терминале введите следующие команды:

pkg update
pkg install termux-api
pkg install openssh
pkg install qrencode
pkg install python
pkg install pip
pip install pystun3
 
Обратите внимание, что pystun3 в последней строке — это модуль Python, поэтому мы установили его с помощью 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

В полученном списке датчиков ищите тот, в названии которого есть слово акселерометр. Например, в моем телефоне есть акселерометр BMI160 и линейное ускорение. Теперь можно попробовать прозондировать датчик следующей командой:

termux-sensor -s "BMI160 Accelerometer" -n 1

В моем слу­чае вывод этой коман­ды име­ет сле­дующий вид:

{
"BMI160 Accelerometer": {
"values": [
0.35223388671875,
0.20556640625,
9.873580932617188
]
}
}
 
Обратите внимание на две вещи. Во-первых, отображение информации об измерении занимает девять строк. В дальнейшем мы будем использовать его для анализа выходного потока с датчика ускорения. Во-вторых, ускорение отображается в виде его составляющих в трех пространственных координатах. Если вы помните школьный урок физики, то уже поняли, что сумма квадратов этих величин даст нам квадрат ускорения. Мы рассчитаем его, чтобы определить движение телефона.
 
Если вы опустите параметр -n в предыдущей команде, данные датчика выводятся непрерывно каждую секунду. Интервал, в свою очередь, можно изменить, добавив ключ -d _0_. Мы будем использовать это в программе. Акселерометр достаточно чувствителен, чтобы определять не только движение вашего устройства, но и вибрацию от легкого удара о стол, на котором стоит устройство.
 

ПРЕДВАРИТЕЛЬНАЯ ПОДГОТОВКА

Вы можете написать код прямо на свой телефон, установив любой текстовый редактор, например Vim или Nano. Но проще это сделать на компе и потом SSH программу на телефон. Для этого вам необходимо ввести свой SSH-ключ в файл ~/.ssh/authorized_keys. Затем в консоли Termux запустите демон sshd, введя одноименную команду. Обратите внимание, что демон sshd прослушивает нестандартный порт 8022, поскольку у него нет привилегий суперпользователя.

Теперь вы можете получить доступ к телефону со своего ПК с помощью команды ssh _0_ -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, запускаемой от имени пользователя root. А если таких прав нет, можно подключиться к телефону через 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
 
Первая строка — шебанг. Чтобы каждый раз не писать такой длинный путь, можно воспользоваться стандартным линуксовым /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 (преобразование сетевых адресов) — это механизм преобразования сетевых адресов, который позволяет нескольким устройствам подключаться к хостам в Интернете с использованием одного общего IP-адреса. Сегодня именно технология NAT позволяет получить доступ к глобальной сети из более чем 20 миллиардов устройств по всему миру, в то время как Интернет имеет лишь немногим более четырех миллиардов «белых» или маршрутизируемых IPv4-адресов.

NAT может быть настроен по-разному для разных провайдеров. Для подключения устройств будем использовать STUN (Session Traversal Utilities for NAT) — специальный сетевой протокол, позволяющий NAT-клиенту узнать свой внешний IP-адрес, а также метод трансляции портов во внешней сети. В этом случае за нас все сделает установленная нами ранее функция pystun3 get_ip_info().

При вызове этой функции будет сделано несколько запросов к разным адресам STUN-сервера. В ответ на запросы сервер будет возвращать информацию о том, к какому IP-адресу и порту был осуществлен доступ. Для наших целей достаточно, чтобы при доступе к определенному порту разных хостов в сети с одного и того же внутреннего порта провайдер назначал нам один и тот же внешний порт. Этот тип NAT называется ограниченным NAT. Если, с другой стороны, при доступе к разным хостам каждый раз назначаются разные внешние порты, подключение извне к этому устройству вызовет серьезные трудности. Этот тип NAT называется симметричным NAT.

Из протестированных мною операторов «Большой четверки» только «Билайн» имел симметричный 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-коды. Вы можете легко использовать любой другой канал связи, такой как SMS, с помощью команд termux-sms-send и termux-sms-list.
 
Если данные о втором хосте были успешно прочитаны из буфера обмена, мы попытаемся подключиться к указанному хосту в течение минуты. Для этого регулярно отправляем UDP-пакеты со строкой NAT на адрес второго хоста. Первые пакеты, отправленные одним из устройств (клиентом или сервером), изначально не доходят до пункта назначения, так как таблица NAT на принимающей стороне еще не содержит правила, определяющего, куда следует отправлять входящие пакеты. Однако, как только со второй стороны передается хотя бы один пакет, устанавливается двусторонняя связь. Функция nat_traversal сообщает об этом, выдавая сообщение Connected to 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 подготовит датчики, если они не завершились правильно раньше.
 
Затем в бесконечном цикле мы будем получать СРЕДНИЕ измерения с датчика ускорения и каждый раз будем вычислять da — разницу между текущим значением и предыдущим значением (точнее, da — квадрат разницы в векторы ускорения: текущий и предыдущий). Вычисленные значения da будут добавлены к переменной ускорения. Если после усреднения (ускорение / = AVERAGE) ускорение превысит предел, установленный в ACCELERATION_THRESHOLD, на сервер будет отправлена ​​строка ALARM с задержкой, чтобы дать серверу время ответить на входящее сообщение.
 
Если с телефоном ничего не происходит, то все равно по истечении времени KNOCK_DELAY мы отправим строку KNOCK на сервер, чтобы он «знал», что будильник и сеть работают исправно. В случае прямого подключения устройств периодическая отправка пакетов на сервер в том числе необходима для того, чтобы правило трансляции, созданное функцией nat_traversal, не исчезло из NAT-таблицы провайдера. В ответ сервер отправит сообщение 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 немедленно подаем сигнал тревоги: воспроизводим файл со звуком сирены с помощью команды play termux-media-player.
 
В основной части программы мы будем вызывать 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-виджетов для быстрого запуска программ
 

ВЫВОДЫ

Из этой статьи видно что Termux мощнейший инструмент для программирования смартфона. Еще одно преимущество в том что во время тревоги можно делать фото с камеры. Для этого используется скрипт termux-camera-photo.

Так же имеются скрипты termux-telephony-call, termux-sms-send с помощью которых можно получать звонок или смс соответственно.

 

 

 
 
 
 
 
 
 

 

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

Click to rate this post!
[Total: 1 Average: 5]

1 comments On Делаем сигнализацию для велика на старом смартфоне

  • Здравствуйте, вопрос немного не по теме: как управлять старым планшетом, вернее плеером на нём из адресной строки браузера через get запросы? По идее на планшете должен быть какой-то сервер? Хотелось бы почитать статью. Спасибо.

Leave a reply:

Your email address will not be published.