# Импорт необходимых библиотек
import network # Для работы с Wi-Fi
import time # Для работы со временем и задержками
from machine import Pin, SoftI2C, PWM # Для работы с пинами, I2C и ШИМ
import dht # Для работы с датчиком DHT22
from umqtt.simple import MQTTClient # Для работы с MQTT
import ssd1306 # Для работы с OLED дисплеем
# --- КОНФИГУРАЦИЯ ---
MQTT_BROKER = "broker.emqx.io" # Адрес MQTT брокера
MQTT_TOPIC = "wokwi/music" # Топик для получения мелодий
DHT_PIN = 15 # Пин для датчика DHT22
TEMP_LED_PIN = 12 # Пин для светодиода температуры
HUM_LED_PIN = 13 # Пин для светодиода влажности
BUZZER_PIN = 14 # Пин для зуммера
# --- ИНИЦИАЛИЗАЦИЯ УСТРОЙСТВ ---
sensor = dht.DHT22(Pin(DHT_PIN)) # Датчик температуры/влажности
i2c = SoftI2C(scl=Pin(5), sda=Pin(4)) # I2C для дисплея
oled = ssd1306.SSD1306_I2C(128, 64, i2c) # OLED дисплей 128x64
temp_led = Pin(TEMP_LED_PIN, Pin.OUT) # Светодиод температуры
hum_led = Pin(HUM_LED_PIN, Pin.OUT) # Светодиод влажности
buzzer = PWM(Pin(BUZZER_PIN)) # Зуммер с ШИМ
# --- ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ---
melody = [] # Список нот текущей мелодии
current_note = 0 # Индекс текущей ноты
note_start = 0 # Время начала воспроизведения ноты
playing = False # Флаг воспроизведения
def connect_wifi():
"""Подключение к Wi-Fi сети"""
sta_if = network.WLAN(network.STA_IF) # Создаем интерфейс станции
sta_if.active(True) # Активируем интерфейс
if not sta_if.isconnected(): # Если не подключены
print("Connecting to WiFi...")
sta_if.connect('Wokwi-GUEST', '') # Подключаемся к Wokwi-GUEST
while not sta_if.isconnected(): # Ожидаем подключения
time.sleep(0.5)
print("Connected:", sta_if.ifconfig()[0]) # Выводим IP адрес
return sta_if
def parse_rttl(rttl):
"""Парсинг RTTL строки в список нот"""
try:
# Разбиваем строку на части: название, настройки, ноты
name, settings, notes = rttl.split(':')
# Парсим настройки (d=длительность, o=октава, b=BPM)
settings = dict(s.split('=') for s in settings.split(','))
default_duration = int(settings.get('d', 4))
default_octave = int(settings.get('o', 5))
bpm = int(settings.get('b', 100))
# Соответствие нот их частотам (4 октава)
note_map = {
'c': 261, 'c#': 277, 'd': 293, 'd#': 311,
'e': 329, 'f': 349, 'f#': 370, 'g': 392,
'g#': 415, 'a': 440, 'a#': 466, 'b': 493
}
parsed_notes = [] # Сюда будем складывать распарсенные ноты
for note in notes.split(','): # Обрабатываем каждую ноту
note = note.strip().lower() # Убираем пробелы и приводим к нижнему регистру
if not note: # Пропускаем пустые
continue
# Парсим длительность ноты (цифры в начале)
i = 0
while i < len(note) and note[i].isdigit():
i += 1
duration = int(note[:i]) if i > 0 else default_duration
# Парсим саму ноту
if note[i] == 'p': # Если это пауза
freq = 0
else:
note_name = note[i] # Буква ноты (c, d, e и т.д.)
if i+1 < len(note) and note[i+1] == '#': # Диез
note_name += '#'
i += 1
# Парсим октаву (цифра после названия ноты)
octave = int(note[i+1]) if i+1 < len(note) and note[i+1].isdigit() else default_octave
# Рассчитываем частоту с учетом октавы
freq = note_map.get(note_name, 440) * (2 ** (octave - 4))
# Рассчитываем длительность в миллисекундах
duration_ms = int((60000 / bpm) * (4 / duration))
parsed_notes.append((freq, duration_ms))
return parsed_notes
except Exception as e:
print("RTTL parse error:", e)
return []
def on_message(topic, msg):
"""Обработчик входящих MQTT сообщений"""
global melody, current_note, note_start, playing
try:
print("Received RTTL:", msg.decode()) # Декодируем сообщение
melody = parse_rttl(msg.decode()) # Парсим мелодию
if melody: # Если удачно распарсили
current_note = 0 # Начинаем с первой ноты
note_start = time.ticks_ms() # Засекаем время
playing = True # Устанавливаем флаг воспроизведения
print("Запускается мелодия")
except Exception as e:
print("Message error:", e)
def play_melody():
"""Воспроизведение текущей мелодии"""
global current_note, note_start, playing
if not playing or not melody: # Если не играем или нет мелодии
return
current_time = time.ticks_ms()
elapsed = time.ticks_diff(current_time, note_start) # Сколько времени играет текущая нота
freq, duration = melody[current_note] # Частота и длительность текущей ноты
if elapsed >= duration: # Если нота отыграла свое время
# Переходим к следующей ноте
current_note += 1
if current_note >= len(melody): # Если мелодия закончилась
playing = False
buzzer.duty_u16(0) # Выключаем зуммер
print("Хватит")
return
# Берем следующую ноту
freq, duration = melody[current_note]
note_start = current_time # Сбрасываем таймер
# Воспроизводим ноту
if freq > 0: # Если не пауза
buzzer.freq(int(freq)) # Устанавливаем частоту
buzzer.duty_u16(32768) # 50% громкость (32768/65535)
else: # Пауза
buzzer.duty_u16(0) # Выключаем звук
def main():
"""Основная функция"""
# Подключение к WiFi
wifi = connect_wifi()
if not wifi.isconnected():
print("WiFi connection failed")
return
# Настройка MQTT клиента
client = MQTTClient("weather_player", MQTT_BROKER)
client.set_callback(on_message) # Устанавливаем обработчик сообщений
try:
client.connect() # Подключаемся к брокеру
client.subscribe(MQTT_TOPIC) # Подписываемся на топик
print("Connected to MQTT")
except Exception as e:
print("MQTT error:", e)
return
# Основной цикл программы
while True:
try:
# Чтение данных с датчика
sensor.measure()
temp = sensor.temperature()
hum = sensor.humidity()
# Вывод данных на OLED дисплей
oled.fill(0) # Очищаем дисплей
oled.text(f"Temp: {temp:.1f}C", 0, 0) # Температура
oled.text(f"Hum: {hum:.1f}%", 0, 20) # Влажность
oled.text("Play: " + ("YES" if playing else "NO"), 0, 40) # Статус воспроизведения
oled.show() # Обновляем дисплей
# Управление светодиодами
# Температура вне диапазона 17-25°C
temp_led.value(temp < 17 or temp > 25)
# Влажность вне диапазона 40-60%
hum_led.value(hum < 40 or hum > 60)
# Проверка новых MQTT сообщений
client.check_msg()
# Воспроизведение мелодии
play_melody()
time.sleep(0.01) # Короткая пауза
except Exception as e:
print("Main loop error:", e)
time.sleep(1)
# Запуск программы
if __name__ == "__main__":
main()