Урок 10. Музыкальный сервер

А теперь перейдём к созданию музыкального сервера. На этот раз не будем пользоваться сторонними библиотеками, а воспользуемся встроенными возможностями. Также задействуем созданный в прошлом уроке модуль.

Для начала сохраним наш прошлый скрипт как melodies.py на компьютере. А затем с помощью встроенного в IDE файлового менеджера скопируем этот скрипт на контроллер. После этого мы сможем вызывать функции elochka() и antoshka() из других модулей.

Вы уже знаете что на контроллере присутствует файл boot.py. Изменим содержимое этого файла. В нём будем производить подключение к сети WiFi.

Помимо этого для создания сервера нам понадобится модуль socket. Из документации по языку указано что библиотеку лучше подключать следующим способом:

try:
  import usocket as socket
except:
  import socket

Затем укажем что нам не нужны отладочные сообщения платы:

import esp
esp.osdebug(None)

Далее оставим сборщик мусора, который уже присутствует в скрипте загрузки:

import gc
gc.collect()

И подключим необходимые для работы модули. Из модуля machine импортируем класс Pin, который отвечает за выводы контроллера. Также импортируем модуль, отвечающий за работу со временем time и не забудем про модуль для работы с подключением к сети network.

from machine import Pin
import time
import network

Выведем отладочное сообщение чтобы понимать порядок запуска процедур и функций:

print("Это загрузочный модуль")

Подключение к сети практически не отличается от уже изученных нами уроков:

# параметры сети
wifi_name = "qwerty"
wifi_pass = "12345678"
 
# соединение с wifi в качестве клиента
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
if not wifi.isconnected():
    wifi.connect(wifi_name, wifi_pass)
    while not wifi.isconnected():
        print("Попытка подключения...")
        time.sleep(1)
 
# вывод IP-адреса
IP = wifi.ifconfig()[0]
print("Успешно подключились. IP адрес: {IP}".format(IP=IP))

На этом закончим редактирование скрипта boot.py. Полностью его код будет выглядеть следующим образом:

try:
  import usocket as socket
except:
  import socket
 
import esp
esp.osdebug(None)
 
import gc
gc.collect()
 
from machine import Pin
import time
import network
 
print("Это загрузочный модуль")
 
# параметры сети
wifi_name = "qwerty"
wifi_pass = "12345678"
 
# соединение с wifi в качестве клиента
wifi = network.WLAN(network.STA_IF)
wifi.active(True)
if not wifi.isconnected():
    wifi.connect(wifi_name, wifi_pass)
    while not wifi.isconnected():
        print("Попытка подключения...")
        time.sleep(1)
 
# вывод IP-адреса
IP = wifi.ifconfig()[0]
print("Успешно подключились. IP адрес: {IP}".format(IP=IP))

Нам осталось лишь описать функции сервера.

Для начала подключим наш модуль с мелодиями, и импортируем оттуда две функции, отвечающие за воспроизведение мелодий:

# подключение модуля с мелодиями
from melodies import elochka, antoshka

Немного скорректируем интернет-страницу из прошлого проекта:

# функция возвращает интернет-страницу
def html_page():
 
    html_page = """
    <!DOCTYPE html>
    <html>
	<head>
		<meta charset="utf-8">
		<title>Музыкальный веб-сервер Гиккон</title>
	</head>
	<body>
		<h1>Выберите музыку для воспроизведения:</h1>
		<p><a href="/antoshka">Антошка</a></p>
           <p><a href="/elochka">Ёлочка</a></p>
	</body>
    </html>
    """
 
    return html_page

И опишем как сервер будет понимать запросы с сайта. Мы будем пользоваться библиотекой socket, которую подключали в скрипте boot.py. Socket в переводе с английского - гнездо, розетка, порт. По сути мы должны создать порт, который будем прослушивать. Создадим переменную порта, и укажем служебные параметры, в них мы углубляться не будем, они очень специфичные и используются всегда для такого типа соединений.

# переменная для создания прослушиваемого порта
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

Если на этот порт поступает какая-то информация (запросы), мы её обрабатываем и в зависимости от того что пришло принимаем решение, какие действия будут выполняться. Мы будем прослушивать порт 80 на входящие сообщения. После этого мы переведём порт в режим прослушки (приёма) входящих соединений:

s.bind(("", 80))
s.listen(5)

После этого в цикле, когда получаем какое-то соединение, функция accept нам возвращает кортеж из объекта соединения (conn) и адреса (откуда пришло соединение, с какого IP-адреса).

    while True:
        conn, addr = s.accept()
        print("Получено соединение от IP-адреса %s" % str(addr))

Затем мы получаем из объекта соединения данные и проверяем, пришли ли они вообще. Если их нет, соединение закрываем.

        data = conn.recv(1024)
 
        if not data:  # данные не пришли
            conn.close()
            continue  # не обрабатываем

Далее требуется расшифровать данные, ведь они имеют тип byte, который неудобен для чтения и строкового представления. После этого, расшифрованные данные (строку) разделим и получим три строки, которые содержат вызванный метод, строку соединения и описание протокола. Из этого нас интересует только строка, но для отладки выведем всё что получили.

        udata = data.decode("utf-8")
        # нужна только первая строка
        udata = udata.split("\r\n", 1)[0]
        print("Первая строка данных: %s" % udata)
        # разделить строку по пробелам
        method, string, protocol = udata.split(" ", 2)
        # вывести полученные метод, строку и протокол
        print(method, string, protocol)

Если полученная строка - это команда с выведенного сайта, то выполняем действия. Если команда “ёлочка”, воспроизведём мелодию Ёлочка. Если команда “антошка”, то воспроизведём мелодию Антошка.

        # если строка - это команда "/turn_led", тогда переключить светодиод
        if string == "/antoshka":
            antoshka()
        elif string == "/elochka":
            elochka()

После того как запрос обработан, нам нужно отправить ответ. В ответ вернём нашу интернет страницу, перед этим передав служебную информацию о соединении, затем соединение можно закрыть.

        response = html_page()
        conn.send("HTTP/1.1 200 OK\n")
        conn.send("Content-Type: text/html\n")
        conn.send("Connection: close\n\n")
        conn.sendall(response)
        conn.close()

На этом функция main() завершена. Мы создали порт прослушки, указали как обработать полученные данные и вернули ответ.

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

if __name__ == "__main__":
    print("Это главный модуль")
    main()

Целиком код модуля будет выглядеть следующим образом:

# подключение модуля с мелодиями
from melodies import elochka, antoshka
 
# функция возвращает интернет-страницу
def html_page():
 
    html_page = """
    <!DOCTYPE html>
    <html>
	<head>
		<meta charset="utf-8">
		<title>Музыкальный веб-сервер Гиккон</title>
	</head>
	<body>
		<h1>Выберите музыку для воспроизведения:</h1>
		<p><a href="/antoshka">Антошка</a></p>
        <p><a href="/elochka">Ёлочка</a></p>
	</body>
    </html>
    """
 
    return html_page
 
 
def main():
 
    # переменная для создания прослушиваемого порта
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(("", 80))
    s.listen(5)
 
    while True:
        conn, addr = s.accept()
        print("Получено соединение от IP-адреса %s" % str(addr))
        data = conn.recv(1024)
 
        if not data:  # данные не пришли
            conn.close()
            continue  # не обрабатываем
 
        udata = data.decode("utf-8")
        # нужна только первая строка
        udata = udata.split("\r\n", 1)[0]
        print("Первая строка данных: %s" % udata)
        # разделить строку по пробелам
        method, string, protocol = udata.split(" ", 2)
        # вывести полученные метод, строку и протокол
        print(method, string, protocol)
        # если строка - это команда "/turn_led", тогда переключить светодиод
        if string == "/antoshka":
            antoshka()
        elif string == "/elochka":
            elochka()
 
        response = html_page()
        conn.send("HTTP/1.1 200 OK\n")
        conn.send("Content-Type: text/html\n")
        conn.send("Connection: close\n\n")
        conn.sendall(response)
        conn.close()
 
if __name__ == "__main__":
    print("Это главный модуль")
    main()

Наш сервер готов. Далее нам нужно все три модуля загрузить на контроллер. Модуль boot.py мы загрузим на контроллер и заместим уже имеющийся там модуль boot.py. Модуль melodies.py, который отвечает за воспроизведение музыки загрузим как есть, с тем же именем. И последний модуль загрузим и сделаем его главным. После этого можно перезагрузить контроллер и, зайдя на сайт модуля (посмотреть IP-адрес сайта можно в отладочных сообщениях, которые будут видны если включить режим REPL).

Мы создали веб-сервер без использования сторонних библиотек. Когда мы использовали стороннюю библиотеку micropyserver, она по сути делала все шаги по созданию порта и его прослушки за нас, программирование получалось легче. Однако, если Вам потребуется более гибко настраивать порты и прочие параметры работы с портами, можно воспользоваться библиотекой socket. В любом случае решение остаётся за Вами.

Предыдущий урок

Далее