Если вы предпочитаете читать это в форме, отличной от Medium, вы также можете прочитать это в моем личном блоге.

В этом руководстве я научу вас, как можно использовать Набор для кодирования Гарри Поттера Кано для управления лампочкой Phillips Hue, используя их потрясающий API Hue и мой собственный модуль Python 3 kano wand. Учебное пособие написано для новичков в программировании, поэтому, если вы опытный пользователь, можете пропустить его до конца. Для запуска этой демонстрации вам понадобится всего несколько вещей:

ОС на базе Linux - это самое странное требование, поэтому позвольте мне объяснить. Когда я писал свой модуль палочки, я искал существующую библиотеку, чтобы упростить взаимодействие с палочкой. Лучшее, что я нашел, было bluepy и, к сожалению, доступно только в Linux. Поскольку я использую elementary OS на своем основном компьютере, для меня это не было проблемой. Прошу прощения за ограничение, я бы хотел перенести модуль на мультиплатформенный модуль python ble, если он есть!

Я начну с общего дизайна, который мы будем использовать. Я разработал модуль kano wand, чтобы он был очень гибким, позволяя пользователям использовать подкласс своей собственной палочки или добавлять динамически функции обратного вызова по мере необходимости. В этом руководстве мы воспользуемся подклассом и расширим класс Wand несколькими функциями, такими как post_connect и on_button. Нам также понадобится класс для управления объектом Bridge и реагирования на различные заклинания от палочки. Этот класс будет мигать всеми нашими лампочками и сбрасывать их в исходное состояние при отключении. Пока мы подключены к палочке, мы приказываем менеджеру мерцать в зависимости от текущего заклинания палочки. Перед основными сегментами кода я сделаю ссылку на суть с кодом шага, чтобы вы могли видеть код в этой точке, чтобы упростить выполнение.

Хорошо, давайте настроим проект. Во-первых, давайте создадим папку с названием оттенок для файлов проекта. Далее нам нужно получить копию модуля. Вы можете либо скачать и распаковать архив файлов в папку проекта, либо клонировать репо в папку hue с помощью следующей команды:

git clone https://github.com/GammaGames/kano_wand.git

Далее нам понадобится место для написания нашего кода, поэтому создайте внутри папки файл с именем «hue.py». Наконец, вам нужно будет установить несколько требований для проекта. Некоторые из них, возможно, потребуется установить с помощью sudo, поскольку bluepy требует повышения прав пользователя для поиска периферийных устройств Bluetooth с низким энергопотреблением. Вы можете установить необходимые модули с помощью следующей команды в командной строке:

pip3 install bluepy numpy qhue moosegesture

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

Для начала откройте наш hue.py файл в своем любимом редакторе кода. Мы должны импортировать все необходимые модули вверху файла со следующей [суть]:

from kano_wand.kano_wand import Shop, Wand, PATTERN
from qhue import Bridge
import moosegesture as mg
import time
import random
import math

Первая строка импортирует классы Shop и Wand и перечисление PATTERN из модуля kano_wand. Поскольку нам нужен только класс Bridge из qhue, мы импортируем только его. Мы также импортируем moosegesture как mg, чтобы было легче печатать. Мы также будем импортировать time, random и math для различных утилит в скрипте.

Давайте создадим базовый объект палочки, который будет печатать название палочки, и магазин, который будет искать палочки. Мы можем сделать это с помощью следующего кода [gist]:

class GestureWand(Wand):
    def post_connect(self):
        print(self.name)
shop = Shop(wand_class=GestureWand)
wands = []
while len(wands) == 0:
    print("Scanning...")
    wands = shop.scan(connect=True)
for wand in wands:
    wand.disconnect()

Сначала мы создаем наш собственный класс палочки. Он использует post_connect для распечатки имени палочки после подключения. Затем мы создаем Shop и передаем наш собственный класс с использованием аргумента ключевого слова wand_class. Пока наш массив жезлов пуст, мы будем сканировать сканирование, автоматически подключаясь к жезлам. После того, как мы найдем несколько жезлов, мы вырвемся из петли while и отключим их. Последние две строки на самом деле не нужны, поскольку палочки автоматически отключаются по завершении программы, но лучше быть точным при написании кода, чтобы попытаться предотвратить неожиданные ошибки.

Чтобы запустить приведенный выше код, мы должны запустить его, используя sudo. Например, вы можете использовать следующую команду в своем терминале (она запросит ваш пароль):

sudo python3 hue.py

Теперь давайте добавим жесты. Мы хотим окружить наш призыв к поиску жезлов try except, который аккуратно отключит наши жезлы, когда мы остановим программу с помощью Ctrl + C (с этого момента самый простой способ остановить скрипт). Сделайте следующую регулировку:

shop = Shop(wand_class=GestureWand)
wands = []

try:
    while len(wands) == 0:
        print("Scanning...")
        wands = shop.scan(connect=True)

except KeyboardInterrupt as e:
    for wand in wands:
        wand.disconnect()

После подключения к нашей палочке мы хотим подписаться на уведомления о положении и кнопках для хранения данных о нашем положении (умножая значение y на -1, чтобы исправить движения вверх / вниз), удерживая кнопку, и распечатать наш жест, когда мы ее отпускаем. Я заменю старую палочку на следующую [суть]:

class GestureWand(Wand):
    def post_connect(self):
        self.pressed = False
        self.positions = []
        self.subscribe_button()
        self.subscribe_position()
    def on_position(self, x, y, pitch, roll):
        if self.pressed:
            self.positions.append(tuple([x, -1 * y]))
    def on_button(self, pressed):
        self.pressed = pressed
        if not pressed:
            gesture = mg.getGesture(self.positions)
            self.positions = []
            print(gesture)

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

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

self.gestures = {
    ("DL", "R", "DL"): "stupefy",
    ("DR", "R", "UR", "D"): "wingardium_leviosa",
    ("UL", "UR"): "reducio",
    ("DR", "U", "UR", "DR", "UR"): "flipendo",
    ("R", "D"): "expelliarmus",            
    ("UR", "U", "D", "UL", "L", "DL"): "incendio",
    ("UR", "U", "DR"): "lumos",
    ("U", "D", "DR", "R", "L"): "locomotor",
    ("DR", "DL"): "engorgio",
    ("UR", "R", "DR"): "aguamenti",
    ("UR", "R", "DR", "UR", "R", "DR"): "avis",
    ("D", "R", "U"): "reducto"
}

Когда мы отпустим кнопку, мы воспользуемся функцией findClosestMatchingGesture MooseGesture с результирующим массивом позиций, чтобы получить список жестов, которые напоминают наше движение. Если мы получим совпадение, мы сохраним полученное значение жеста из словаря жестов и провибрируем жезл [gist]:

def on_button(self, pressed):
    self.pressed = pressed
   if pressed:
       self.spell = None
   else:
        gesture = mg.getGesture(self.positions)
        self.positions = []
        closest = mg.findClosestMatchingGesture(gesture, self.gestures, maxDifference=1)
        if closest != None:
            self.spell = self.gestures[closest[0]]
            self.vibrate(PATTERN.SHORT)
        print("{}: {}".format(gesture, self.spell))

Вот и все, что нужно знать о палочке! Теперь займемся световым менеджером.

Сначала вам нужно получить IP-адрес вашего моста и имя пользователя, чтобы использовать API. Вы можете сделать это с помощью Руководства по началу работы Филиппа. Мы создадим объект-менеджер, который знает IP-адрес моста и имя пользователя, имеет экземпляр объекта Bridge и массив идентификаторов источников света для управления. Мы будем использовать следующий код (в этом примере мост распечатывает состояние источника света с идентификатором 1) [gist]:

class LightManager():
    def __init__(self):
        self.bridge_ip = "192.168.1.22"
        self.username = "dBHN8d6Qkw6EJMqzEI2oI0zXJGiOdvyE2lRzFha8"
        self.bridge = Bridge(self.bridge_ip, self.username)
        self.light_ids = ["1"]
        for id in self.light_ids:
            light = self.bridge.lights[id]
            print(light()["state"])

manager = LightManager()

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

Мы сохраним наши начальные состояния в словаре, используя идентификатор источника света в качестве ключа, а при сбросе мы передадим значение ключа в функцию state источника света. После резервного копирования исходного состояния мы можем установить состояние лампочек по умолчанию, передав **self.default функции state, преобразовав словарь в аргументы ключевого слова.

В качестве состояния по умолчанию для лампочек мы можем использовать словарь, установленный на значение {"state": True, "bri": 144, "hue": 7676, "sat": 199}. bri - яркость, hue - цвет, sat - насыщенность лампы. По умолчанию используется тусклый оранжевый цвет, идеально подходящий для эффекта факела. Что касается мерцания, мы будем использовать состояние по умолчанию со случайным изменением яркости. Мы также должны передать время перехода лампочке, чтобы она мигала быстрее. Обновленный менеджер теперь выглядит так:

class LightManager():
    def __init__(self):
        self.bridge_ip = "192.168.1.22"
        self.username = "dBHN8d6Qkw6EJMqzEI2oI0zXJGiOdvyE2lRzFha8"
        self.bridge = Bridge(self.bridge_ip, self.username)
        self.light_ids = ["1"]
        self.light_states = {}
self.default = {"on": True, "bri": 144, "hue": 7676, "sat": 199}
    for id in self.light_ids:
        light = self.bridge.lights[id]
        state = self.default.copy()
        s = light()['state']
        for key in state:
        state[key] = s[key]
        self.light_states[id] = state
        light.state(**self.default)
def flicker(self, transition):
    for id in self.light_ids:
        light = self.bridge.lights[id]
        c = self.default.copy()
        c["bri"] = c["bri"] + random.randint(0, 53)
        light.state(transitiontime=transition, **c)
def reset(self):
    for id in self.light_ids:
        light = self.bridge.lights[id]
        light.state(**self.light_states[id])

Пока палочка подключена, мы вызываем функцию flicker и засыпаем, пока лампочка переключается. Philips рекомендует ограничивать количество команд до примерно 10 команд в секунду для каждого источника света, поэтому мы будем спать от 100 до 200 мс при каждом мерцании. После поиска жезлов [gist] мы добавим следующее:

wand = wands[0]
while wand.connected:
    sleep = random.uniform(0.1, 0.2)
    transition = math.ceil(sleep * 10)
    manager.flicker(transition)
    time.sleep(sleep)
manager.reset()

Теперь все, что нам осталось, это установить состояние лампочки в соответствии с текущим заклинанием жезла. Мы удалимself.default и будем использовать словарь с текущим заклинанием в качестве ключа (по умолчанию None). Выглядит это так:

self.color_values = {
    None: {"bri": 144, "hue": 7676, "sat": 199},
    "stupefy": {"hue": 0, "bri": 200, "sat": 150},
    "wingardium_leviosa": {"hue": 37810, "bri": 100, "sat": 40},
    "reducio": {"hue": 51900, "bri": 200, "sat": 200},
    "flipendo": {"hue": 37445, "bri": 150, "sat": 140},
    "expelliarmus": {"hue": 1547, "bri": 200, "sat": 200},
    "incendio": {"hue": 7063, "bri": 200, "sat": 250},
    "lumos": {"hue": 0, "bri": 204, "sat": 0},
    "locomotor": {"hue": 12324, "bri": 100, "sat": 140},
    "engorgio": {"hue": 32275, "bri": 125, "sat": 120},
    "aguamenti": {"hue": 32275, "bri": 180, "sat": 200},
    "avis": {"hue": 37445, "bri": 150, "sat": 130},
    "reducto": {"hue": 37445, "bri": 180, "sat": 200}
}

Мы получим текущее состояние, передав текущее заклинание, когда мы мерцаем, и сохраним его вself.current. Мы установим состояние источника света, используя это значение, и если «Lumos» только что был применен, мы переключим свет. Наша функция flicker теперь выглядит так:

def flicker(self, spell, transition):
    for id in self.light_ids:
        light = self.bridge.lights[id]
        on = light()['state']['on']
        self.current = self.color_values[spell]
        if spell == "lumos":
            light.state(transitiontime=transition, on=not on, **self.current)
        elif on:
            c = self.current.copy()
            c["bri"] = c["bri"] + random.randint(0, 53)
            light.state(transitiontime=transition, **c)

Нам потребуется только небольшая модификация, чтобы функция flicker использовала заклинание, просто передайте текущее заклинание жезла и установите для него значение None, чтобы предотвратить мерцание света, когда вы произносите Lumos [gist]:

manager.flicker(wand.spell, transition)
    if wand.spell == "lumos":
        wand.spell = None
    time.sleep(sleep) 

Вуаля, готово! Примерно в 100 строках кода у нас есть палочка, которая может распознавать взмахи и щелчки и изменять цвет лампочки. Палочка требует строгих движений, но магия требует дисциплины, чтобы овладеть ею! Готовый скрипт вы можете найти в моем репозитории kano-wand-demos. Спасибо за чтение :)