Делаем LED контроллер Matter

23 февраля, 2026

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

Я не буду описывать как сделать Matter реле или PWM на стандартных GPIO ESP32 так как это описано в официальной документации Tasmota. Мы будем делать на 16 канальной микросхеме PWM PCA9685, в стандартной прошивке она отсутствует, я собирал её сам с включенными параметрами в файле my_user_config.h

#defineUSE_PCA9685_V2
#defineUSE_PCA9685_ADDR0x40
#defineUSE_PCA9685_FREQ1000

Частота по умолчанию 50 хорошо подходит для сервоприводов, но нам нужно управлять светодиодами и установим частоту 1000 по умолчанию, через консоль или скрипты эту частоту можно поменять. Скомпилированную прошивку можно скачать тут:

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

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

Реализовать возможно только одноцветный, двухцветный (CT) и трехцветный (RGB)

Так как у нас Matter endpoint используют имена то надо чтобы в скрипте можно было обращаться по имени, для этого включим опцию SetOption83 1 выполнив в консоли:

SetOption83 1

создадим файл с именем autoexec.be в меню Управление — Управление файловой системой, со следующим содержимым:

import matter
import persist

# ======= Единый конфиг =======
# tp: "dimmer", "ct", "rgb"
# channels: для dimmer - одно число, для ct - {warm, cold}, для rgb - {r, g, b}
var devices = [
  {"name": "Led01", "tp": "dimmer", "channels": 0},
  {"name": "Led02", "tp": "dimmer", "channels": 1},
  {"name": "Led03", "tp": "dimmer", "channels": 2},
  {"name": "Led04", "tp": "dimmer", "channels": 3},
  {"name": "Led05", "tp": "dimmer", "channels": 4},
  {"name": "Led06", "tp": "rgb",    "channels": {"r": 5, "g": 6, "b": 7}},
  {"name": "Led07", "tp": "ct",     "channels": {"warm": 8, "cold": 9}},
  # Чтобы добавить новое устройство - просто добавь строку:
  # {"name": "Led08", "tp": "dimmer", "channels": 10},
  # {"name": "Led09", "tp": "ct",     "channels": {"warm": 11, "cold": 12}},
  # {"name": "Led10", "tp": "rgb",    "channels": {"r": 13, "g": 14, "b": 15}},
]

# ======= Инициализация persist =======
def init_persist()
  if !persist.has("pwm_values")   persist.pwm_values   = {} end
  if !persist.has("bri_values")   persist.bri_values   = {} end
  if !persist.has("power_values") persist.power_values = {} end
  if !persist.has("rgb_values")   persist.rgb_values   = {} end
  if !persist.has("ct_values")    persist.ct_values    = {} end

  for dev: devices
    var n = dev["name"]
    var tp = dev["tp"]
    if !persist.pwm_values.contains(n)   persist.pwm_values[n]   = 4096 end
    if !persist.bri_values.contains(n)   persist.bri_values[n]   = 254  end
    if !persist.power_values.contains(n) persist.power_values[n] = 0    end
    if tp == "rgb"
      if !persist.rgb_values.contains(n) persist.rgb_values[n] = {"r": 4096, "g": 4096, "b": 4096} end
    end
    if tp == "ct"
      if !persist.ct_values.contains(n) persist.ct_values[n] = 326 end  # нейтральный белый
    end
  end
  persist.save()
end

# ======= Вспомогательные функции =======
def bri_to_pwm(bri)
  return int(bri * 4096 / 254)
end

# CT: 153=холодный, 500=тёплый
def ct_to_warm_cold(ct, pwm)
  var warm = int(pwm * (ct - 153) / (500 - 153))
  var cold = pwm - warm
  return [warm, cold]
end

def pwm_off(channels, tp)
  if tp == "dimmer"
    tasmota.cmd("driver15 pwm," + str(channels) + ",0")
  elif tp == "rgb"
    tasmota.cmd("driver15 pwm," + str(channels["r"]) + ",0")
    tasmota.cmd("driver15 pwm," + str(channels["g"]) + ",0")
    tasmota.cmd("driver15 pwm," + str(channels["b"]) + ",0")
  elif tp == "ct"
    tasmota.cmd("driver15 pwm," + str(channels["warm"]) + ",0")
    tasmota.cmd("driver15 pwm," + str(channels["cold"]) + ",0")
  end
end

def pwm_on(name, channels, tp)
  var pwm = persist.pwm_values[name]
  if tp == "dimmer"
    tasmota.cmd("driver15 pwm," + str(channels) + "," + str(pwm))
  elif tp == "rgb"
    var rgb = persist.rgb_values[name]
    var scale = pwm / 4096.0
    tasmota.cmd("driver15 pwm," + str(channels["r"]) + "," + str(int(rgb["r"] * scale)))
    tasmota.cmd("driver15 pwm," + str(channels["g"]) + "," + str(int(rgb["g"] * scale)))
    tasmota.cmd("driver15 pwm," + str(channels["b"]) + "," + str(int(rgb["b"] * scale)))
  elif tp == "ct"
    var wc = ct_to_warm_cold(persist.ct_values[name], pwm)
    tasmota.cmd("driver15 pwm," + str(channels["warm"]) + "," + str(wc[0]))
    tasmota.cmd("driver15 pwm," + str(channels["cold"]) + "," + str(wc[1]))
  end
end

# ======= Регистрация правил =======
def handle_devices()
  for dev: devices
    var n = dev["name"]
    var tp = dev["tp"]
    var ch = dev["channels"]

    # Power
    tasmota.add_rule("mtrreceived#" + n + "#power",
      def(value)
        persist.power_values[n] = int(value)
        persist.save()
        if value == 1
          pwm_on(n, ch, tp)
          print(n + " Power ON")
        else
          pwm_off(ch, tp)
          print(n + " Power OFF")
        end
      end
    )

    # Brightness
    tasmota.add_rule("mtrreceived#" + n + "#bri",
      def(brightness)
        var pwm_value = bri_to_pwm(brightness)
        persist.pwm_values[n] = pwm_value
        persist.bri_values[n] = int(brightness)
        persist.save()
        if tp == "dimmer"
          tasmota.cmd("driver15 pwm," + str(ch) + "," + str(pwm_value))
        elif tp == "rgb"
          var rgb = persist.rgb_values[n]
          var scale = brightness / 254.0
          tasmota.cmd("driver15 pwm," + str(ch["r"]) + "," + str(int(rgb["r"] * scale)))
          tasmota.cmd("driver15 pwm," + str(ch["g"]) + "," + str(int(rgb["g"] * scale)))
          tasmota.cmd("driver15 pwm," + str(ch["b"]) + "," + str(int(rgb["b"] * scale)))
        elif tp == "ct"
          var wc = ct_to_warm_cold(persist.ct_values[n], pwm_value)
          tasmota.cmd("driver15 pwm," + str(ch["warm"]) + "," + str(wc[0]))
          tasmota.cmd("driver15 pwm," + str(ch["cold"]) + "," + str(wc[1]))
        end
        print(n + " Bri: " + str(pwm_value))
      end
    )

    # RGB цвет
    if tp == "rgb"
      tasmota.add_rule("mtrreceived#" + n + "#rgb",
        def(rgb_str)
          var r = int("0x" + rgb_str[0..1])
          var g = int("0x" + rgb_str[2..3])
          var b = int("0x" + rgb_str[4..5])
          var r_pwm = int(r * 4096 / 255)
          var g_pwm = int(g * 4096 / 255)
          var b_pwm = int(b * 4096 / 255)
          persist.rgb_values[n] = {"r": r_pwm, "g": g_pwm, "b": b_pwm}
          persist.save()
          var scale = persist.bri_values[n] / 254.0
          tasmota.cmd("driver15 pwm," + str(ch["r"]) + "," + str(int(r_pwm * scale)))
          tasmota.cmd("driver15 pwm," + str(ch["g"]) + "," + str(int(g_pwm * scale)))
          tasmota.cmd("driver15 pwm," + str(ch["b"]) + "," + str(int(b_pwm * scale)))
          print(n + " RGB: " + rgb_str)
        end
      )
    end

    # CT цветовая температура
    if tp == "ct"
      tasmota.add_rule("mtrreceived#" + n + "#ct",
        def(ct)
          persist.ct_values[n] = int(ct)
          persist.save()
          var wc = ct_to_warm_cold(int(ct), persist.pwm_values[n])
          tasmota.cmd("driver15 pwm," + str(ch["warm"]) + "," + str(wc[0]))
          tasmota.cmd("driver15 pwm," + str(ch["cold"]) + "," + str(wc[1]))
          print(n + " CT: " + str(ct) + " warm:" + str(wc[0]) + " cold:" + str(wc[1]))
        end
      )
    end
  end
end

# ======= Восстановление состояния =======
def restore_state()
  for dev: devices
    var n = dev["name"]
    var tp = dev["tp"]
    var ch = dev["channels"]
    var power = persist.power_values[n]
    var bri = persist.bri_values[n]

    if power == 1
      pwm_on(n, ch, tp)
    else
      pwm_off(ch, tp)
    end

    var cmd = '{"Name":"' + n + '","Power":' + str(power) + ',"Bri":' + str(bri)
    if tp == "ct"
      cmd += ',"CT":' + str(persist.ct_values[n])
    end
    cmd += '}'
    tasmota.cmd("MtrUpdate " + cmd)
    print("Restore " + n + ": " + cmd)
  end
end

init_persist()
tasmota.set_timer(0, handle_devices)
tasmota.set_timer(5000, restore_state)

отредактируйте файл изменив выводы к которым у Вас подключены Led в массиве var devices, номера соответствуют номерам микросхемы PCA9685. Cохраняем файл, перезагружаем например командой в консоли

Restart 1

Теперь можно добавить Matter устройство в Вашу систему умного дома.