В предыдущей статье мы начали строить систему сбора данных о погоде на базе контроллера 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,
E32-915T30D

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.

Рис. 1. Блок инициализации узлов 1 и 2
Рис. 1. Блок инициализации узлов 1 и 2

Здесь выполняется инициализация и сброс 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 появляется ромбик (на короткое время).

Рис. 2. Блок on e32radio received узлов 1 и 2
Рис. 2. Блок on e32radio received узлов 1 и 2

Что касается функции getWeatherData, то ее задача заключается в получении данных от погодной станции BME-280. Эти данные преобразуются в текстовые строки и соединяются через символ точки с запятой.

Рис. 3.  Функция getWeatherData
Рис. 3.  Функция getWeatherData

Периферийный узел 3

Если вы будете создавать программу для третьего узла заново, то установите расширение для измерителя освещенности с названием BH1750. Также, конечно, нужно установить расширение pxt-lora.

Третий узел не оснащен дисплеем OLED, поэтому при начале работы программа инициализирует только радиомодуль, устанавливает размеры буферов UART, а также адрес 35 (0x23) на шине для измерителя освещенности BH1750 FVI GY-30 (рис. 4).

На светодиодной матрице micro::bit зажигается цифра 3 — номер периферийного узла.

Рис. 4. Блок инициализации узла 3
Рис. 4. Блок инициализации узла 3

Когда третий узел получает команду «getData», он вызывает одноименную функцию и на короткое время выводит ромбик вместо цифры 3 на светодиодную матрицу micro::bit. Данные, полученные от этой функции, отправляются в центральный шлюз по адресу 14 на канале 15 (рис. 5).

Рис. 5. Блок on e32radio received узла 3
Рис. 5. Блок on e32radio received узла 3

Функция getData (рис. 6) получает значение температуры процессора и освещенности в люксах, а затем преобразует в данные текстовую строку с разделителем в виде точки с запятой.

Рис. 6. Функция getData
Рис. 6. Функция getData

Создаем сайт на базе 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).

Рис. 7. Данные периферийных узлов в окне браузера
Рис. 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

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

Можно дистанционно через интернет включать, например, освещение или кондиционер, или реализовывать другие элементы умного дома.

Вы даже можете попробовать сделать на ее основе охранное устройство, хотя для серьезной охраны лучше все же установить промышленную систему.

Автор: Александр Фролов.

Комментарии (13)


  1. N-Cube
    03.06.2022 12:46
    +2

    Micro:Bit отличный микроконтроллер, только в данном проекте не используются никакие из его возможностей - вместо встроенных использованы внешний радиомодуль, датчик освещенности и дисплей. Зачем тогда огромный по размерам микробит вообще нужен (и плюс к нему еще плата расширения с пинами)? Любой из компактных микроконтролллеров (к примеру, на основе RPI Pico, с уменьшенным количеством выводов есть варианты размером в ноготь большого пальца) обойдутся вдесятеро дешевле и займут в сотни раз меньше места.


    1. AlexandreFrolov
      04.06.2022 12:53

      На самом деле проект демонстрирует самую важную особенность micro::bit как учебного микрокомпьютера — возможность его программирования с помощью визуального редактора Microsoft MakeCode без знаний языков программирования. И возможность применения micro::bit в относительно сложных проектах (для простых применений micro::bit и так много примеров).

      Кроме того, в проекте показано, что для Microsoft MakeCode можно создавать расширения к любым аппаратным устройствам, после чего их использование станет доступно даже детям.

      В качестве примера здесь был использован не самый простой радиомодуль LoRa EBYTE E32, изначально не имеющий поддержки в MakeCode. Создание расширений для MakeCode, конечно, требует определенных навыков в программировании. Обычно этим занимаются компании, выпускающие для micro::bit различные устройства.

      Что же касается стоимости реализации проекта и габаритные размеры конструкции (и даже потребляемая мощность), то в данном случае эти факторы не играют особой роли. Речь идет не о коммерческом, а именно об учебном STEM проекте, который можно очень легко собрать и запрограммировать (буквально за несколько часов). И это без изготовления печатных плат и монтажа микросхем с помощью пайки, без программирования на С и сложной отладки.

      А уже дальше можно этот проект развивать, насколько хватит фантазии, используя как встроенные в micro::bit устройства, так и различные внешние модули.


      1. N-Cube
        04.06.2022 14:09

        Визуальное программирование доступно и для всех или почти всех аналогов: например, есть универсальный Scratch (поддерживает, в том числе, микробит), есть специализированный инструмент для упомянутого мною выше RPI Pico https://www.raspberrypi.com/news/drag-n-drop-coding-for-raspberry-pi-pico/, есть собственный проприетарный блочный язык для конструкторов Фишертехник и так далее. Есть и сомнения про доступность - микробит почти полгода был в дефиците, да и стоимость, скажем, 4х микробитов плюс платы расширения и дисплеев это уже около сотни долларов, если к этому радиомодули внешние брать и другие датчики взамен встроенных, такой эксперимент выйдет в сотни долларов за сомнительную радость работать на лицензируемых частотах, требующих разрешения (для детей, серьезно?). По нашему опыту - встроенный радиомодуль микробит работает отлично, для связки 4х контролллеров на расстоянии десятки метров в пределах дома и вокруг ничего более и не нужно. А если нужно, то дешевый сотовый модем с управлением AT командами работает идентично wifi модулям на основе контроллера esp32 и вообще никаких расширений не требует для подключения.


        1. AlexandreFrolov
          05.06.2022 02:39

          Согласен что есть средства визуального программирования и для других микрокомпьютеров, но тут это доступно через браузер вообще без установки какого-либо ПО.

          Что касается встроенного радиомодуля, то он действительно работает хорошо, но в пределах квартиры, а здесь предлагается решение для передачи данных на существенно большее расстояние. Для совершенно других применений, когда встроенный радиомодуль не подойдет. Когда WiFi и сотовая связь недоступны.

          Описанный в статье учебный проект дает возможность познакомиться с micro::bit и LoRa, не затрачивая слишком много времени и сил, не изучая такие языки программирования, как С. Это и есть основная цель статьи, а вовсе не рассказать, как за минимальные деньги измерять данные о погоде внутри квартиры или небольшого дома (можно просто купить готовую и недорогую погодную станцию).

          Что касается доступности micro::bit, то он есть, например, на том же Алиэкспресс, и в других профильных магазинах.

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

          Когда я сам учился в школе, то собирал приемники и передатчики из обычных транзисторов и радиоламп, а уж смонтировать готовые модули на макетной плате и залить готовое ПО, на мой взгляд, вообще никакая не проблема! Даже для школьников.

          И почему бы школьникам не попробовать передачу данных на безлицензионных (а не лицензированных, как вы пишите) диапазонах, не требующих получения разрешений при малой мощности (о безлицензионных частотах написано в первой части статьи https://habr.com/ru/company/first/blog/669218/). Может быть кто-то заинтересуется и будет потом использовать промышленные решения LoRa.


          1. N-Cube
            05.06.2022 08:24

            Почему-то в статье прозвучало так, что для этого проекта микробит обладает киллер-фичами, которых нет ни у кого больше, хотя на самом деле легко можно приобрести микроконтроллер с дисплеем (не как у микробит, а нормальным), вайфай или gsm модулем и программировать его блоками (ничего не устанавливая на компьютер, посмотрите тот же scratch). При малой мощности, кстати, LoRa будет работать не намного дальше радиомодуля или блютуфа микробит, и тем более сопоставимо с копеечным esp32 микроконтоллером с внешней антенной (у вас дальность увеличивается как раз за счет антенны) - несколько сот метров на открытом пространстве. На самом же деле, микробит хорош тем, что предоставляет широкий выбор встроенных датчиков и средств связи, и самые интересные проекты именно те, которые пользуются именно встроенными средствами. Ну и уж тем более читерство добавить в проект «взрослый» RPI и установить на нем «тяжелый» софт - веб-сервер на микробит сделать легко и интересно своими руками с помощью модуля esp32 и простых текстовых AT команд.


            1. AlexandreFrolov
              05.06.2022 09:07

              Почему-то в статье прозвучало так, что для этого проекта микробит обладает киллер-фичами, которых нет ни у кого больше

              У micro::bit, разумеется, есть киллер-фичи, но статья не про это. Она в первую очередь про модули LoRa, которые будут работать, когда недоступен ни WiFi, ни gsm. Соответственно, когда не пригодится встроенный WiFi микрокомпьютера типа esp32 и AT-команды.

              Кроме того, в статье показано, что на базе micro::bit можно делать такие проекты с LoRa, которые обычно делаются по-другому (на STM32, например). Используя в том числе киллер-фичи этого микрокомпьютера в обучении.

              При малой мощности, кстати, LoRa будет работать не намного дальше радиомодуля или блютуфа

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

              На самом же деле, микробит хорош тем, что предоставляет широкий выбор встроенных датчиков и средств связи, и самые интересные проекты именно те, которые пользуются именно встроенными средствами.

              Разумеется он этим хорош, но в статье показано, что если дополнить его радиомодулем LoRa, то можно создавать системы сбора данных, работающие на обширных площадях. Да, больше подходит для учебных проектов, для реальных есть промышленные всепогодные решения LoRa.

              Ну и уж тем более читерство добавить в проект «взрослый» RPI и установить на нем «тяжелый» софт - веб-сервер на микробит сделать легко и интересно своими руками с помощью модуля esp32 и простых текстовых AT команд.

              Может кому и интересно, а мне интереснее показать, что в центральном узле такого проекта может стоять полноценный микрокомпьютер, позволяющий создавать более сложные проекты, чем esp32. И ничего там особо сложного, недоступного школьникам, не предлагается. Все можно установить с помощью инструкции из второй части статьи.

              Для микрокомпьютера esp32 есть своя ниша, разумеется, но этот проект отлично обходится и без него. Я полагаю что esp32 может стать темой для других статей.

              Вы, например, можете рассказать о своем подходе к сбору данных на площади, измеряемой в квадратных километрах, например, с использованием только esp32 и его встроенных средств. Особенно будет интересно, если на этой площади интернет есть только в одном здании, а площадь невозможно покрыть WiFi. И сотовая связь недоступна. Разумеется, такое тоже можно сделать с применением LoRa, но я считаю более пригодным для учебного проекта именно micro:bit на периферии и Raspberry Pi в центре.


            1. AlexandreFrolov
              05.06.2022 09:40

              Вот тут, например, рассказано о том, почему LoRa обеспечивает большую дальность связи:

              https://wireless-e.ru/standarty/tehnologiya-lora-v-voprosah-i-otvetah/

              " Технология модуляции LoRa (Long Range) представляет собой метод модуляции, который обеспечивает значительно бóльшую дальность связи (зону покрытия), чем другие конкурирующие с ним способы. Метод основывается на технологии модуляции с расширенным спектром и вариации линейной частотной модуляции (Chirp Spread Spectrum, CSS) с интегрированной прямой коррекцией ошибок (Forward Error Correction, FEC). Технология LoRa значительно повышает чувствительность приемника и, аналогично другим методам модуляции с расширенным спектром, использует всю ширину полосы пропускания канала для передачи сигнала, что делает его устойчивым к канальным шумам и нечувствительным к смещениям, вызванным неточностями в настройке частот при использовании недорогих опорных кварцевых резонаторов. Технология LoRa позволяет осуществлять демодуляцию сигналов с уровнями на 19,5 дБ ниже уровня шумов, притом что для правильной демодуляции большинству систем с частотной манипуляцией (Frequency Shift Keying, FSK) нужна мощность сигнала как минимум на 8-10 дБ выше уровня шума. Модуляция LoRa определяет тот физический уровень1(Physical Layer, PHY, иногда его называют слой), который может быть использован с различными протоколами и в различных вариантах сетевой архитектуры, таких как сетка (Mesh), звезда (Star), точка-к‑точке (point-to-point) и т. п. "


              1. N-Cube
                05.06.2022 12:02

                Вы очень недооцениваете современные протоколы wifi. Вот вам на амазоне вайфай модуль с дальностью 15 км и несравнимо большей скоростью передачи данных: https://www.amazon.in/Ubiquiti-Bundle-NanoStation-Outdoor-airMAX/dp/B01EBBKVDE Более того, если вас устроит скорость LoRa, то вайфай можно на кратно большем расстоянии использовать. На али за стоимость ваших модулей LoRa тоже можно найти активные вайфай антенны с большей дальностью (сам вайфай модуль стоит копейки). А уж если на базе использовать направленную антенну и в нужное время настраиваться на положение передатчиков - то и радиомодуль микробита позволит на километры устойчивое соединение получить. Да, если задачей было прорекламировать модули LoRa - тоже не очень получилось, потому что у них главные преимущества совсем не в том, о чем говорится в статье…


                1. AlexandreFrolov
                  05.06.2022 17:49

                  Вот вам на амазоне вайфай модуль с дальностью 15 км и несравнимо большей скоростью передачи данных: https://www.amazon.in/Ubiquiti-Bundle-NanoStation-Outdoor-airMAX/dp/B01EBBKVDE

                  Я правильно понимаю, что этот модуль стоит ₹21,999.00 в рупиях или около 18000 руб.? Требует ли он лицензирования?

                  А есть что-то сравнимое по по цене и дальности действия с модулями LoRa E32? Скажем, километров на 3-5, чтобы сравнивать подобное с подобным. Скорость передачи данных да, это не про LoRa. Но не везде она имеет значение.

                  Да, если задачей было прорекламировать модули LoRa - тоже не очень получилось, потому что у них главные преимущества совсем не в том, о чем говорится в статье…

                  Задачей было сделать учебный STEM проект именно с модулями LoRa и micro::bit. Просто потому что мне это было интересно, и я думаю, что будет интересно и полезно другим.

                  Наверно, можно сделать подобный проект и на современном WiFi, если такие модули доступны и не требуют лицензирования, а также действительно позволяют покрывать расстояния, исчисляемые километрами, в зоне, где вообще нет никаких сетей.

                  С удовольствием узнал бы, какие конкретно модули WiFi и с какими антеннами позволяют добиться похожих результатов в плане зоны покрытия, что и LoRa. А также в какие доступные микрокомпьютеры такие модули уже встроены, если таковые имеются.

                  Ну а если вы добавите что-нибудь про LoRa, то думаю что всем от этого будет польза.


  1. igor_carenko
    03.06.2022 13:31
    -1

    EBYTE - такое неблагозвучное название для русского языка... :)

    Почти "Colgate" в Испании...

    P.S. Ещё почему-то ещё вспомнилось про Fuckты :)


    1. AlexandreFrolov
      04.06.2022 12:57

      Китайская компания Chengdu Ebyte Electronic Technology Co.,Ltd выпускает очень широкий спектр радиомодулей и антенн, и все это доступно на сайте Алиэкспресс за рубли и по вполне приемлемым ценам.


  1. sav13
    06.06.2022 06:26

    Очень не понравились модули EBYTE работающие через UART

    Там нет возможности (или очень глубоко спрятаны) управления регистрами модема SX1276 и связаться с теми же народными RFM95W у меня не получилось.


  1. AlexandreFrolov
    06.06.2022 08:37

    Там нет возможности (или очень глубоко спрятаны) управления регистрами модема SX1276 и связаться с теми же народными RFM95W у меня не получилось.

    Если не секрет, то кроме несовместимости с RFM95W что еще не понравилось в EBYTE, и для получения каких возможностей потребовалось управлять регистрами SX1276?

    Сходу не удалось найти модуль RFM95W в конструктиве, пригодном для установки на макетную плату. Зато нашел модуль RAK811 WisDuo LoRa Module, который можно установить на макетную плату и для которого даже есть расширение к Microsoft CodeMake https://makecode.microbit.org/pkg/pisupply/pxt-iot-lora-node. Но это расширение я не тестировал.