DIY: передаем данные на километры с помощью контроллера Micro::Bit и радиомодуля EBYTE LoRa (Часть 2)
В предыдущей статье мы начали строить систему сбора данных о погоде на базе контроллера micro::bit и радиомодуля LoRa и остановились на сборке. Сегодня займемся программированием радиомодулей, рассмотрим программы для периферийных узлов и создадим сайт, на котором будут отображаться полученные системой данные.
Программируем радиомодули на узле шлюза
Для нашей системы сбора данных мы подготовили несколько программ.
Для шлюза в интернет для Raspberry Pi программы на Python (версии 2.x и 3.x), а также JavaScript:
e32-get-config.py — чтение конфигурации модуля LoRa EBYTE серии E32;
e32-set-loranet-nodes-config.py — запись новой конфигурации в модуль LoRa EBYTE серии E32;
e32-loranet-get-all.py — опрос периферийных узлов с целью получения от них результатов измерений по радиоканалу;
index.js — сайт для просмотра результатов измерений на базе Node.js и Express
Для периферийных узлов 1, 2 и 3 для micro::bit:
microbit-lora-net-host1.hex
microbit-lora-net-host2.hex
microbit-lora-net-host3.hex
Все исходные тексты программ для Raspberry Pi, а также загрузочные коды для micro::bit, упомянутые в этой статье, можно загрузить здесь.
Конфигурация модулей LoRa EBYTE E32
Радиомодули LoRa EBYTE представляют собой довольно сложные и многофункциональные устройства, которые можно настраивать, записывая в них через UART байты конфигурации.
Если вы приобрели новые модули, то прежде, чем их использовать, было бы неплохо определить их текущую конфигурацию, а затем задать новую, подходящую для вашей системы сбора данных.
Как минимум нужно задать рабочую частоту, уровень выходной мощности передатчика и режим работы (прозрачный или фиксированный). В фиксированном режиме работы следует указать номер канала и адрес получателя.
Детальное описание параметров конфигурации и режимов работы можно найти в технической спецификации (даташите, data sheet) модуля на сайте. Мы рассмотрим только самые важные параметры, имеющие отношение к нашей системе сбора данных.
Чтение конфигурации радиомодулей E32
Для чтения текущей конфигурации радиомодулей E32 мы подготовили программу e32-get-config.py. При запуске эта программа выводит на консоль Raspberry Pi байты конфигурации в том виде, как они были получены, а также расшифровку значений:
$ python e32-get-config.py
Конфигурация модуля E32: 0xc0000e1a0fc7
Сохраняем параметры при выключении питания: 0xc0
Нужен подтягивающий резистор для UART: 1
Таймаут в режиме сохранения энергии: 0
Адрес: 0x000e
Режим UART: 0x0
Скорость UART: 0x3
Скорость радиоканала: 0x2
Номер радиоканала: 0x1a
Режим Fixed: 1
Включен режим FEC: 1
Выходная мощность: 3
Как видите, программа вернула шесть байт конфигурации: 0xc0000e1a0fc7, а также расписала значения отдельных параметров, извлеченных из полученных данных.
Расскажем о том, как закодированы параметры. В табл. 1 приведено назначение каждого из шести байт конфигурации. Видно, что некоторые параметры занимают по одному байту (байты 0 и 4), некоторые — сразу два байта (байты 1 и2), а некоторые кодируются отдельными битами (байты 3 и 5).
Табл. 1. Байты конфигурации E32
Номер байта | Элемент | Описание |
0 | Заголовок команды | 0xC0 — при выключении питания модуля параметры сохраняются; 0xC2 — при выключении питания модуля параметры не сохраняются (удобно для тестирования); |
1 | Адрес, старший байт | Значения от 0x00 до 0xFF |
2 | Адрес, младший байт | Значения от 0x00 до 0xFF |
3 | Формат данных UART, скорость передачи данных | Биты 7, 6 — режим работы порта UART Биты 5, 4, 3 — скорость передачи данных через UART Биты 2, 1, 0 — скорость передачи данных по радиоканалу |
4 | Номер канала передачи данных | Значения от 0x00 до 0x1F |
5 | Дополнительные параметры конфигурации | Бит 7 — прозрачный или фиксированный режим передачи данных Бит 6 — используется ли подтягивающий резистор (1 — используется, 0 – не используется, схема с открытым коллектором) Биты 5, 4, 3 — задержка пробуждения модуля при получении данных по радиоканалу Бит 2 — если равен 1, используется режим упреждающей коррекции ошибок FEC (Forward Error Correction), если 0 — этот режим отключен Биты 1, 0 — выходная мощность радиомодуля |
Первый байт содержит 0xC0. Это означает, что при включении электропитания модуля конфигурация сохраняется с памяти модуля.
Байты 1 и 2 содержат значение 0x000e. Это адрес модуля. Назначение модулям индивидуальных адресов позволяет выполнять адресную отправку пакетов данных. В данном случае, чтобы модуль получил данные, их надо отправлять, указывая адрес 0x000e (работает только в так называемом фиксированном режиме). По умолчанию адрес модуля установлен в нулевое значение.
Обратите внимание на байт 4, который задает номер канала передачи данных для фиксированного режима. Вы можете задавать различные номера каналов для разных подсистем, работающих на одной территории, и при этом данные будут получены только теми модулями, которые настроены на канал отправки данных.
Таким образом, в фиксированном режиме можно направлять данные нужным радиомодулям, задавая при передаче их адрес и номер канала.
Займемся теперь байтом конфигурации с номером 3 (табл. 2).
Табл. 2. Байт конфигурации с номером 3
Бит 7 | Бит 6 | Режим работы порта UART |
0 | 0 | 8N1 (установлен по умолчанию) |
0 | 1 | 8O1 |
1 | 0 | 8E1 |
1 | 1 | 8N1 |
Здесь 8N1 означает, что в пакете 8 бит данных, нет служебного бита проверки четности, используется один стоп-бит в конце пакета.
Режим 8O1 аналогичен предыдущему, но используется контроль по нечетности. В режиме 8E1 то же самое, но применяется контроль по четности.
Наша программа вернула режим UART, равный 0, что означает использование 8N1.
Биты 5, 4 и 3 байта 3 задают скорость передачи данных в bps (табл. 3).
Табл. 3. Скорость передачи данных UART в bps
Бит 5 | Бит 4 | Бит 3 | Скорость передачи данных в bps |
0 | 0 | 0 | 1200 |
0 | 0 | 1 | 2400 |
0 | 1 | 0 | 4800 |
0 | 1 | 1 | 9600 (установлена по умолчанию) |
1 | 0 | 0 | 19200 |
1 | 0 | 1 | 38400 |
1 | 1 | 0 | 57600 |
1 | 1 | 1 | 115200 |
Программа e32-get-config.py вернула скорость UART как 3, что означает использование скорости передачи данных 9600 bps.
Биты 2, 1 и 0 байта 3 задают скорость передачи данных по радиоканалу в bps (табл. 4).
Табл. 4. Скорость передачи данных по радиоканалу в bps
Бит 5 | Бит 4 | Бит 3 | Скорость передачи данных в bps |
0 | 0 | 0 | 300 |
0 | 0 | 1 | 1200 |
0 | 1 | 0 | 2400 (установлена по умолчанию) |
0 | 1 | 1 | 4800 |
1 | 0 | 0 | 9600 |
1 | 0 | 1 | 19200 |
1 | 1 | 0 | 19200 |
1 | 1 | 1 | 19200 |
В нашем случае программа e32-get-config.py показала скорость передачи по радиоканалу, равную 2. Это соответствует 2400 bps.
Байт конфигурации с номером 5 содержит множество различных параметров.
Как видно из табл. 1, бит 7 определяет режим передачи данных — прозрачный или фиксированный. Программа e32-get-config.py определила, что в модуле используется фиксированный режим.
Бит 6 имеет отношение к схемотехнике контроллера. Если он равен 1 (как в нашем случае), то радиомодуль использует свой подтягивающий резистор, а если 0 — то подтягивающего резистора нет.
Использование битов 5, 4, 3 байта с номером 5 зависят от так называемого режима работы радиомодуля. Этот режим задается при помощи контактов M0 и M1 радиомодуля. Установкой нужного режима возможно переключение модуля в «спящий» режим, при этом пробуждение произойдет при получении радиосигнала. Биты 5, 4 и 3 задают задержу при пробуждении.
В нашей системе сбора данных мы используем только нормальный режим, когда на контакты M0 и M1 подается нулевое напряжение. При необходимости подробное описание остальных режимов вы найдете в технической документации на радиомодуль.
Бит 2 показывает, используется ли упреждающая коррекция ошибок FEC (Forward Error Correction). Если он равен 1, этот режим используется, если 0 — данный режим отключен.
Программа e32-get-config.py показала, что в тестируемом модуле алгоритм FEC включен, что способствует устойчивой связи на больших расстояниях.
И, наконец, биты 1 и 0 пятого байта конфигурации задают выходную мощность радиомодуля (табл. 5).
Табл. 5. Выходная мощность радиомодуля
Бит 1 | Бит 0 | Выходная мощность для E32-915T30D, E32-868T30D и E32-433T30D | Выходная мощность для E32-915T20D, E32-868T20D и E32-433T20D |
0 | 0 | 30 dBm или 1 Вт (установлена по умолчанию) | 20 dBm или 100 мВт (установлена по умолчанию) |
0 | 1 | 27 dBm или 501 мВт | 17 dBm или 50 мВт |
1 | 0 | 24 dBm или 251 мВт | 14 dBm или 25 мВт |
1 | 1 | 21 dBm или 126 мВт | 10 dBm или 10 мВт |
Байт 4 конфигурации задает номер канала (табл. 6). Чтобы определить частоту по номеру канала, используйте формулы, приведенные ниже в таблице.
Табл. 6. Номер канала
Модуль | Номер канала по умолчанию | Формула вычисления частоты по номеру канала Channel | Рабочий диапазон частот, МГц |
E32-433T20D, E32-433T30D | 23 (0x17) | 410 МГц + Channel * 1 МГц | 410-441 |
E32-868T20D, E32-868T30D | 6 (0x6) | 862 МГц + Channel * 1 МГц | 862-893 |
E32-915T20D, | 15 (0xF) | 900 МГц + Channel * 1 МГц | 900-931 |
Например, для модулей E32-868T20D и E32-868T30D можно установить частоту 869 МГц, если задать номер канала, равный 7 (862+7=869).
Описание программы чтения конфигурации
Исходный код программы чтения конфигурации радиомодулей серии E32 представлен в листинге 1.
Листинг 1. Файл https://github.com/AlexandreFrolov/loranet/blob/main/py/e32-get-config.py
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import serial
import time
import sys
from time import sleep
def print_e32_config (received_data):
if sys.version_info[0] > 2:
received_data_hex = received_data.hex();
else:
received_data_hex = received_data.encode('hex');
received_long = int(received_data_hex, 16)
print('Конфигурация модуля E32:\t\t\t0x' + str(format(received_long, '012x')))
save_param = (received_long >> 40) & 0xFF
save_param_str = str(format(save_param, '#02x'))
if (save_param == 0xC0):
print('Сохраняем параметры при выключении питания:\t' + save_param_str)
elif (save_param == 0xC2):
print('Не сохраняем параметры при выключении питания:\t' + save_param_str)
else:
print('Неверное значение для сохранения параметров:\t' + save_param_str)
uart_pull_up_resistor_needed = (received_long & 0x40) >> 6
print('Нужен подтягивающий резистор для UART:\t\t' + str(uart_pull_up_resistor_needed))
energy_saving_timeout = (received_long & 0x38) >> 3
print('Таймаут в режиме сохранения энергии:\t\t' + str(energy_saving_timeout))
address = (received_long >> 24) & 0xFFFF
print('Адрес:\t\t\t0x' + str(format(address, '04x')))
uart_mode = ((received_long >> 16) & 0xC0) >> 6
print('Режим UART:\t\t' + str(format(uart_mode, '#02x')))
uart_speed = ((received_long >> 16) & 0x38) >> 3
print('Скорость UART:\t\t' + str(format(uart_speed, '#02x')))
air_data_rate = (received_long >> 16) & 0x7
print('Скорость радиоканала:\t' + str(format(air_data_rate, '#02x')))
channel = (received_long >> 16) & 0x1F
print('Номер радиоканала:\t' + str(format(channel, '#02x')))
fixed_mode = (received_long & 0x80) >> 7
print('Режим Fixed:\t\t' + str(fixed_mode))
fec = (received_long & 0x4) >> 2
print('Включен режим FEC:\t' + str(fec))
output_power = received_long & 0x3
print('Выходная мощность:\t' + str(output_power))
def gpio_init ():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
M0 = 22
M1 = 27
GPIO.setup(M0,GPIO.OUT)
GPIO.setup(M1,GPIO.OUT)
GPIO.output(M0,GPIO.HIGH)
GPIO.output(M1,GPIO.HIGH)
time.sleep(1)
ser = serial.Serial("/dev/serial0", 9600)
ser.flushInput()
return ser
def gpio_cleanup():
GPIO.cleanup()
def e32_get_config():
try :
if ser.isOpen() :
ser.write(b'\xC1\xC1\xC1')
except :
if ser.isOpen() :
ser.close()
GPIO.cleanup()
received_data = ser.read(6)
sleep(0.03)
return received_data
ser = gpio_init()
received_data = e32_get_config()
print_e32_config (received_data)
gpio_cleanup()
В самом начале программа импортирует модуль RPi.GPIO, необходимый для работы с контактами разъема GPIO Raspberry Pi. Кроме этого, для работы с радиомодулем через UART потребуется модуль serial, модули time и sys:
import RPi.GPIO as GPIO
import serial
import time
import sys
from time import sleep
Далее программа инициализирует порт GPIO при помощи функции gpio_init, получает текущую конфигурацию функцией e32_get_config, выводит данные конфигурации через функцию print_e32_config, а затем сбрасывает порт GPIO:
ser = gpio_init()
received_data = e32_get_config()
print_e32_config (received_data)
gpio_cleanup()
Функция gpio_init показана ниже:
def gpio_init ():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
M0 = 22
M1 = 27
GPIO.setup(M0,GPIO.OUT)
GPIO.setup(M1,GPIO.OUT)
GPIO.output(M0,GPIO.HIGH)
GPIO.output(M1,GPIO.HIGH)
time.sleep(1)
ser = serial.Serial("/dev/serial0", 9600)
ser.flushInput()
return ser
Прежде всего, она устанавливает режим использования номеров каналов GPIO.BCM (еще можно указывать номера разъема GPIO в режиме GPIO.BOARD).
В переменные M0 и M1 мы записываем значения 22 и 27. Это номера каналов GPIO22 (вывод 15 разъема GPIO) и GPIO27 (вывод 13 разъема GPIO), контакты которых на разъеме GPIO Raspberry Pi подключены к соответствующим контактам EBYTE LoRa.
Напомним, что контакты 13 и 15 разъема GPIO мы подключили к контактам M1 и M0 радиомодуля.
Устанавливаем значения соответствующих переменных:
M0 = 22
M1 = 27
Обратите также внимание на использование функции GPIO.setwarnings с параметром False. Она отключает появление предупреждающего сообщения, если программа прервала свою работу, но вы запустили ее снова.
С помощью метода GPIO.setup функция переключает каналы M0 и M1 в режим вывода, а затем с помощью метода GPIO.output устанавливает на контактах M0 и M1 уровень напряжения 3,3 В, соответствующий логической единице:
GPIO.setup(M0,GPIO.OUT)
GPIO.setup(M1,GPIO.OUT)
GPIO.output(M0,GPIO.HIGH)
GPIO.output(M1,GPIO.HIGH)
Далее функция делает задержку в работе программы на одну секунду с помощью метода time.sleep.
На следующем шаге функция gpio_init открывает устройство serial, очищает его буфер и возвращает объект для выполнения операций ввода и вывода:
ser = serial.Serial("/dev/serial0", 9600)
ser.flushInput()
return ser
Обратите внимание, что здесь устанавливается скорость обмена 9600 bps. Эта скорость должна соответствовать скорости UART радиомодуля.
Функция e32_get_config читает конфигурацию радиомодуля и возвращает ее вызывающей программе:
def e32_get_config():
try :
if ser.isOpen() :
ser.write(b'\xC1\xC1\xC1')
except :
if ser.isOpen() :
ser.close()
GPIO.cleanup()
received_data = ser.read(6)
sleep(0.03)
return received_data
Она записывает в порт UART микрокомпьютера Raspberry Pi три байта 0xC1. По этой команде радиомодуль возвращает шесть байт конфигурации. Наша программа читает их методом ser.read и возвращает в вызывающую программу после небольшой задержки.
Полученная конфигурация передается функции print_e32_config в качестве параметра для разбора полученных данных и вывода их на консоль.
В зависимости от версии Python полученные байты конфигурации преобразуются в шестнадцатеричную текстовую строку различными способами:
if sys.version_info[0] > 2:
received_data_hex = received_data.hex();
else:
received_data_hex = received_data.encode('hex');
Чтобы извлечь значения из битов отдельных байтов конфигурации используются операции сдвига и операция логического И. Например, здесь извлекается номер радиоканала:
channel = (received_long >> 16) & 0x1F
print('Номер радиоканала:\t' + str(format(channel, '#02x')))
Программа настройки конфигурации радиомодулей E32
С помощью программы настройки конфигурации радиомодулей E32 (листинг 2) мы будем задавать адрес, номер канала, частоту и уровень мощности.
Листинг 2. Файл https://github.com/AlexandreFrolov/loranet/blob/main/py/e32-set-loranet-nodes-config.py
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import serial
import time
import sys
from time import sleep
NODE_CFG = [b'\xC0\x00\x0B\x1A\x0F\xC7', # 1: micro::bit Node 1, Address 0x0B
b'\xC0\x00\x0C\x1A\x0F\xC7', # 2: micro::bit Node 2, Address 0x0C
b'\xC0\x00\x0D\x1A\x0F\xC7', # 3: micro::bit Node 3, Address 0x0D
b'\xC0\x00\x0E\x1A\x0F\xC7'] # 4: Raspberry Pi Node, Address 0x0E
def gpio_init ():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
M0 = 22
M1 = 27
GPIO.setup(M0,GPIO.OUT)
GPIO.setup(M1,GPIO.OUT)
GPIO.output(M0,GPIO.HIGH)
GPIO.output(M1,GPIO.HIGH)
time.sleep(1)
ser = serial.Serial("/dev/serial0", 9600)
ser.flushInput()
return ser
def set_config(ser, node_id, new_cfg):
try :
if ser.isOpen() :
ser.write(new_cfg)
time.sleep(1)
except :
if ser.isOpen() :
ser.close()
GPIO.cleanup()
ser.flushInput()
try :
if ser.isOpen() :
ser.write(b'\xC1\xC1\xC1')
except :
if ser.isOpen() :
ser.close()
GPIO.cleanup()
received_data = ser.read(6)
sleep(0.03)
print('Node ' + str(node_id) + ' new config:')
if sys.version_info[0] > 2:
print(received_data.hex())
else:
print('{}'.format(received_data.encode('hex')))
def get_node_config():
if len(sys.argv) != 2 or int(sys.argv[1]) < 1 or int(sys.argv[1]) > 4 :
print("Please enter node id (1, 2, 3, 4)")
sys.exit(0)
node_id = int(sys.argv[1])
new_cfg = NODE_CFG[node_id-1]
print('Node ' + str(node_id) + ' set new config:')
if sys.version_info[0] > 2:
print(new_cfg.hex())
confirm = input("Enter 'yes' to confirm: ")
else:
print('{}'.format(new_cfg.encode('hex')))
confirm = raw_input("Enter 'yes' to confirm: ")
if confirm != 'yes' :
print("cancelled")
sys.exit(0)
return node_id, new_cfg
node_id, new_cfg = get_node_config()
ser = gpio_init()
set_config(ser, node_id, new_cfg)
В массиве NODE_CFG записаны конфигурации для всех четырех узлов нашей системы сбора данных. Отредактируйте значение частоты и уровня мощности так, чтобы это соответствовало законодательству вашей страны.
При запуске передайте программе e32-set-loranet-nodes-config.py номер узла от 1 до 4. Программа извлечет соответствующие байты конфигурации, покажет текущую конфигурацию на консоли функцией get_node_config. Далее после инициализации GPIO функцией gpio_init программа установит новую конфигурацию, вызвав функцию set_config:
node_id, new_cfg = get_node_config()
ser = gpio_init()
set_config(ser, node_id, new_cfg)
Новая конфигурация устанавливается функцией set_config при отправке в модуль LoRa через UART соответствующей строки массива NODE_CFG:
try :
if ser.isOpen() :
ser.write(new_cfg)
time.sleep(1)
except :
if ser.isOpen() :
ser.close()
GPIO.cleanup()
ser.flushInput()
Далее функция set_config показывает на консоли обновленную конфигурацию.
$ python e32-set-loranet-nodes-config.py 4
Node 4 set new config:
c0000b1a0fc7
Enter 'yes' to confirm: yes
Node 4 new config:
c0000e1a0fc7
Программа сбора данных
Программа e32-loranet-get-all.py (листинг 3) отправляет команду получения данных по очереди на каждый узел нашей системы, получает данные и сохраняет их в JSON-файле.
Листинг 3. Файл /home/pi/py/e32-loranet-get-all.py
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import RPi.GPIO as GPIO
import serial
import time
import sys
from time import sleep
import json
NODE_ADDR_CHAN = [b'\x00\x0B\x0F',
b'\x00\x0C\x0F',
b'\x00\x0D\x0F']
def gpio_init ():
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
M0 = 22
M1 = 27
GPIO.setup(M0,GPIO.OUT)
GPIO.setup(M1,GPIO.OUT)
GPIO.output(M0,GPIO.LOW)
GPIO.output(M1,GPIO.LOW)
time.sleep(1)
ser = serial.Serial("/dev/serial0", 9600, timeout=1)
ser.flushInput()
return ser
def send_cmd(node):
try :
if ser.isOpen() :
ser.write(NODE_ADDR_CHAN[node])
ser.write('getData \n'.encode())
except :
if ser.isOpen() :
ser.close()
GPIO.cleanup()
received_data = ser.readline()
sleep(0.03)
data_left = ser.inWaiting()
received_data += ser.read(data_left)
rec = received_data.decode("utf-8").strip()
node_data = rec.split(';')
return node_data
def format_node_data(node, node_data):
NODE_1_2_ITEMS = ['Температура',
'Давление',
'Влажность',
'Точка росы']
NODE_3_ITEMS = ['Температура CPU',
'Интенсивность освещения']
node_dict={}
i=0
for val in node_data:
val = str(val)
if(node == 0 or node == 1):
node_dict[NODE_1_2_ITEMS[i]]= val
else:
node_dict[NODE_3_ITEMS[i]]= val
i = i+1
return node_dict
def get_nodes_data():
nodes_dict={}
for node in 0,1,2:
node_data = send_cmd(node)
nodes_dict[node] = format_node_data(node, node_data)
return nodes_dict
def save_nodes_data_to_file(nodes_dict):
jsonString = json.dumps(nodes_dict, indent=2, ensure_ascii=False)
print(jsonString)
with open('hosts_data.json', 'w') as f:
json.dump(nodes_dict, f, indent=2, ensure_ascii=False)
ser = gpio_init()
nodes_dict = get_nodes_data()
save_nodes_data_to_file(nodes_dict)
Список адресов и каналов находится в массиве NODE_ADDR_CHAN:
NODE_ADDR_CHAN = [b'\x00\x0B\x0F',
b'\x00\x0C\x0F',
b'\x00\x0D\x0F']
После инициализации порта GPIO функцией gpio_init программа получает данные с помощью функции get_nodes_data, а затем записывает их в файл функцией save_nodes_data_to_file:
ser = gpio_init()
nodes_dict = get_nodes_data()
save_nodes_data_to_file(nodes_dict)
Отправка команды в виде текстовой строки «getData» для каждого узла выполняется функцией send_cmd с помощью функции write:
if ser.isOpen() :
ser.write(NODE_ADDR_CHAN[node])
ser.write('getData \n'.encode())
Если данные были отправлены успешно, функция получает результаты измерений от периферийного узла, декодирует их и возвращает:
received_data = ser.readline()
sleep(0.03)
data_left = ser.inWaiting()
received_data += ser.read(data_left)
rec = received_data.decode("utf-8").strip()
node_data = rec.split(';')
return node_data
Узлы передают данные в виде строки, причем значения разделены символом точка с запятой. Значения, полученных от всех периферийных узлов, записываются в файл JSON с помощью функции save_nodes_data_to_file.
Обратите внимание, что расшифровку названия параметров выполняет функция format_node_data. С ее помощью можно задать различный набор названий параметров для каждого узла.
Программа сбора данных запускается без параметров и показывает на консоли сформированные данные JSON:
$ python3 e32-loranet-get-all.py
{
"0": {
"Температура": "27",
"Давление": "976",
"Влажность": "24",
"Точка росы": "12"
},
"1": {
"Температура": "28",
"Давление": "975",
"Влажность": "0",
"Точка росы": "8"
},
"2": {
"Температура CPU": "35",
"Интенсивность освещения": "204"
}
}
Чтобы программа периодически запускала сбор данных, создайте для ее запуска задание Crontab. Для этого создайте файл crontab_lora.txt:
*/15 * * * * /usr/bin/python /home/pi/loranet/py/e32-loranet-get-all.py
Здесь обновление данных будет выполняться каждые 15 минут, вы можете задать свое значение.
Далее запустите задание:
$ crontab crontab_lora.txt
Программы для периферийных узлов
Теперь займемся программами для micro::bit для периферийных узлов. Вы можете загрузить коды программ, пригодные для Microsoft MakeCode, с сайта GitHub.
Номер в именах файлов задает периферийный узел, для которого эта программа предназначена: microbit-lora-net-host1.hex, microbit-lora-net-host2.hex, microbit-lora-net-host3.hex.
Чтобы посмотреть и отредактировать программы, откройте в браузере Chrome сайт Microsoft MakeCode. Далее перетащите мышкой один из файлов, например, microbit-lora-net-host1.hex, прямо в окно браузера. Откроется редактор MakeCode и программа будет в него уже загружена.
Периферийные узлы 1 и 2
Программы для узлов 1 и 2 отличаются только текстовой строкой, которую они записывают в OLED-дисплей в начале своей работы. Соответствующий блок при начале показан на рис. 1.
Здесь выполняется инициализация и сброс OLED-дисплея, после чего на его экран выводится строка «E32 Loranet Host 1» для первого узла и «E32 Loranet Host 2» для второго узла. Устанавливается размер буферов передачи данных для UART.
На светодиодной матрице micro::bit зажигается цифра 1 (2 для второго узла).
Для работы с дисплеем используется расширение oled-ssd1306. Если вы будете создавать программу «с нуля», установите это расширение, выбрав слева в палитре редактора MakeCode строку Расширения. Введите в строке поиска название нужного вам расширения.
Таже при создании программы установите расширение pxt-lora. Введите адрес в упомянутой выше строки поиска и подтвердите установку. Расширение pxt-lora написано автором этой статьи и поддерживает следующие модули: E30-170T20D, E32-170T30D, E32-433T20D, E32-433T27D, E32-433T30D, E32-868T20D, E32-868T30D, E32-915T20D и E32-915T30D.
Когда периферийный узел получает команду getData, он вызывает функцию getWeatherData и отправляет полученные от нее результаты измерений на адрес 14 (0xe) по 15 каналу, т.е. на узел шлюза в интернет. Соответствующий блок показан на рис. 2.
На OLED-дисплей выводится принятая команда. После успешной отправки на светодиодной матице micro::bit появляется ромбик (на короткое время).
Что касается функции getWeatherData, то ее задача заключается в получении данных от погодной станции BME-280. Эти данные преобразуются в текстовые строки и соединяются через символ точки с запятой.
Периферийный узел 3
Если вы будете создавать программу для третьего узла заново, то установите расширение для измерителя освещенности с названием BH1750. Также, конечно, нужно установить расширение pxt-lora.
Третий узел не оснащен дисплеем OLED, поэтому при начале работы программа инициализирует только радиомодуль, устанавливает размеры буферов UART, а также адрес 35 (0x23) на шине для измерителя освещенности BH1750 FVI GY-30 (рис. 4).
На светодиодной матрице micro::bit зажигается цифра 3 — номер периферийного узла.
Когда третий узел получает команду «getData», он вызывает одноименную функцию и на короткое время выводит ромбик вместо цифры 3 на светодиодную матрицу micro::bit. Данные, полученные от этой функции, отправляются в центральный шлюз по адресу 14 на канале 15 (рис. 5).
Функция getData (рис. 6) получает значение температуры процессора и освещенности в люксах, а затем преобразует в данные текстовую строку с разделителем в виде точки с запятой.
Создаем сайт на базе Express
Итак, наша система сбора данных отправляет по очереди команду getData на все периферийные узлы, а полученные значения сохраняет в виде JSON файла. Теперь на базе Node.js и Express сделаем простейший сайт, чтобы можно было наблюдать за результатами измерений через браузер.
Установка Node.js
Чтобы установить Node.js в Raspberry Pi OS Lite, вы можете воспользоваться процедурой для Debian, описанной здесь.
Установку нужно выполнять с правами пользователя root следующим образом:
$ sudo su
# curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
# apt-get install -y nodejs
# exit
После установки проверьте версии Node.js и npm:
$ node -v
v18.1.0
$ npm -v
8.8.0
Выполните инициализацию проекта:
$ mkdir lora_site
$ cd lora_site
$ npm init
При инициализации вам будет задан ряд вопросов. Вы можете выбрать здесь ответы по умолчанию или указать свои данные, например, при запросе описания приложения description, точки входа entry point и автора author.
После подтверждения введенных данных в каталоге приложения будет создан файл package.json.
Далее нужно установить Express в каталог приложения и сохранить его в списке зависимостей файла package.json:
$ npm install express --save
Создаем сайт для просмотра собранных данных
Создайте файл index.js (листинг 4) и разместите его в каталоге /home/pi/loranet/lora_site/.
Листинг 4. Файл https://github.com/AlexandreFrolov/loranet/blob/main/lora_site/index.js
const http = require("http");
const hostname = "192.168.0.19";
const port = 8000;
const fs = require('fs');
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'Application/json; charset=utf-8'});
let rawdata = fs.readFileSync('/home/pi/py/hosts_data.json');
let host_data = JSON.parse(rawdata);
console.log(host_data);
res.end(JSON.stringify(host_data));
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
})
Здесь предполагается, что сайт будет работать в локальной сети по адресу 192.168.0.19 и на порту 8000. Вам, возможно, нужно будет указать другой адрес, свободный в вашей сети.
Запустите Web-сервер такой командой:
$ node index.js
Если теперь открыть сайт по адресу http://192.168.0.19:8000, то в окне браузера появятся данные, полученные от периферийных узлов (рис. 7).
Для доступа к этому сайту из интернета настройте на своем роутере проброс порта 8000. Способ настройки зависит от модели вашего роутера, в интернете можно найти множество инструкций на эту тему.
Защищаем узел шлюза
Чтобы снизить вероятность взлома узла шлюза, рекомендуем сменить пароль пользователя pi и установить fail2ban:
$ sudo apt install fail2ban
Вы также можете настроить брандмауэр, чтобы ограничить доступ извне.
Еще будет нелишним сделать так, чтобы при использовании команды sudo нужно было вводить пароль. Для этого отредактируйте файл:
$ sudo nano /etc/sudoers.d/010_pi-nopasswd
Измените следующую строку, заменив в ней NOPASSWD на PASSWD:
pi ALL=(ALL) NOPASSWD: ALL
Должно получиться так:
pi ALL=(ALL) PASSWD: ALL
Идеи для вашего проекта STEM
Если вам удалось собрать и запустить систему сбора данных, описанную в этой статье, можете усовершенствовать или дополнить ее. Например, помимо погодной станции подключите к периферийным узлам сенсоры движения, контакты, датчики газа и воды и тому подобные устройства.
Можно дистанционно через интернет включать, например, освещение или кондиционер, или реализовывать другие элементы умного дома.
Вы даже можете попробовать сделать на ее основе охранное устройство, хотя для серьезной охраны лучше все же установить промышленную систему.
Автор: Александр Фролов.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.