Если коротко, у этого проекта две особенности. Первое: стол подключается из Google Smart Home к Heroku с помощью голосовых команд, и второе: Heroku и собственно стол общаются по протоколу Интернета вещей MQTT. MQTT — хорошее решение для Интернета вещей, а также для преодоления некоторых других препятствий, с которыми нам придётся столкнуться.
Прежде всего скажу, что я делал этот проект, просто чтобы поразвлечься. Надеюсь, вы найдёте статью занимательной и она послужит вам мотивацией, чтобы найти время и сделать что-то своё.
Тот самый столикАппаратная часть
Первая и, вероятно, самая трудная часть работы — переделать стол. В прошлой жизни у стола была отсоединяемая ручка, она располагалась у края столешницы. Сначала я подумал о том, чтобы прикрепить что-то к отверстию ручки, не пришлось бы вмешиваться в конструкцию стола. Я приобрёл несколько приводов, чтобы понять, как к столу прикрепить мотор, но безрезультатно. Затем появилась идея: стержень, работающий по длине всего стола, который соединял бы его ножки так, что они опускались и поднимались бы одновременно. Если закрепить подходящий к стержню привод, тогда я смогу использовать ремень для соединения стержня и мотора. Также можно было бы оснастить стол мотором, не сильно вмешиваясь в его конструкцию.
Важность крутящего момента
Заказав нужные привод и ремень, я начал искать на Amazon мотор с высоким крутящим моментом. И — о, чудо! — я нашёл много подходящих двигателей! Или мне так показалось… Купив маленький моторчик, около месяца я ждал его прибытия из Китая. Я был так взволнован, когда моторчик, наконец, приехал! Не мог дождаться выходных, чтобы, наконец, собрать всё воедино и получить мой моторизованный стол.
Всё пошло не по плану. Я провёл день, вырезая дыру для стержня в металлической обшивке стола. В тот момент у меня были только ручные инструменты, поэтому процесс занял больше времени, чем я рассчитывал. Ближе к концу дня я закончил сборку стола и был готов его опробовать.
Я включил моторчик, напряжение на своём настольном источнике питания и… ничего не произошло. Несколькими мгновениями позже мотор начал вращаться и скрежетать зубцами приобретённого ремня. Из этого я извлёк два урока: ремень, очевидно, не справляется со своей работой, а надпись «Мотор в высоком крутящим моментом» не означает «Я могу поднять всё на свете». Второй урок: надо смотреть, какого размера моторчик в сравнении с пальцами. Мой оказался крошечным!
Слева на фото мотор и ремень. Вверху справа — прикреплённый к столу мотор (позже вы увидите больше о происходящем). Внизу справа мотор в положении на столе.
Подходящий мотор
Чтобы выбрать подходящий мотор, нужно было рассчитать, какой крутящий момент требуется для поднятия столешницы. Я был удивлён тем, насколько просто сделать это.
Крутящий момент — это сила, умноженная на длину плеча рычага.
Что ж, плечо рычага у меня было (это рукоятка стола), нужно было только рассчитать силу, которая легко повернула бы плечо рычага. Я нагрузил стол, привязав к ручке кувшин для молока и постепенно добавлял в кувшин воду, пока рычаг не начал вращаться. Повернув ручку кверху с наполненным кувшином я убедился, что вес легко поворачивает ручку. Я выяснил, что длина плеча рычага составляет 11 см, а требуемая сила — 4 фунта. Подставив эти цифры в формулу, я выяснил, что мотор должен выдавать крутящий момент не менее 19,95 кг/см. И начал искать его.
Я решил переделать стол необратимым образом. Мне было известно, что проходящий через середину стола стержень полый. Поискав двухвальный мотор, я мог разрезать стержень и пересобрать его с мотором в середине. Купив два мотора с крутящим моментом 20 кг/см, я гарантировал, что крутящего момента для поднятия стола достаточно.
В очередную прекрасную субботу я разобрал свой стол на четыре части, подпилив валы двигателей так, чтобы их можно было использовать при сборке стержня. Я проделал больше дырок в металле, чтобы поместить в них моторы. Ремня в этот раз не было: моторы соединялись напрямую со стержнем, дыры были достаточно большими. Пока наступал вечер, я пересобрал стол и загрузил его офисными принадлежностями.
Два верхних фото — полностью установленные на стол моторы. Два нижних фото —- интегрированный стержень, с помощью моторов проходящий по длине стола.
Я подключил моторы и соединил их с источником питания. Включив питание, я увидел движение стола! На этот раз я был увереннее, потому что правильно подобрал размеры моторов. Я удвоил мощность двигателей ради уверенности, но потрясающе было видеть их движение!
Однако позвольте уточнить, что стол был медленным. Я снял видео, чтобы показать другу, как работает стол, но ему пришлось включить ускорение времени на видео, чтобы не смотреть 5 минут, как стол меняет положение столешницы.
Обороты важны. Окончательная версия
Наконец я понял, что всё сводится к двум вещам: крутящему моменту и оборотам. Нужно было найти мотор с достаточным количеством оборотов при уже известном крутящем моменте.
Это было не так сложно. Хотя я не нашёл двухвалового мотора, зато нашёл прямоугольный редуктор, которая превращает мотор с одним валом в двухвальный мотор.
Короче говоря, следующий месяц был месяцем ожидания коробки передач из Китая, а следующей после ожиданий субботой у меня был стол, который двигался с нужной скоростью.
Последний мотор сам по себе слева, а установленный — справа. Немного аппаратного обеспечения и много программного обеспечения.
Я не был доволен огромным блоком питания на моем столе, лежащем только ради того, чтобы управлять высотой столешницы. Кроме того, чтобы изменять положение стола от одного к другому и обратно, я менял местами провода. Небольшая проблема, но проект делался, чтобы в идеале просто нажимать кнопку и иметь несколько предустановок высоты.
Bluetooth
Первым решением было добавить к столу Bluetooth. В конце концов, похоже на то, что почти каждое устройство в доме имеет Nluetooth, а телефон представляется удобным интерфейсом управления чего-то подобного моему столу.
Итак, теперь я приобрёл плату контроллера мотора, плату для Bluetooth Nordic NRF52, сенсоры для измерения расстояния и начал возиться с прошивкой контроллера.
В конце статьи я оставлю ссылки на софт и прошивку, которые написал для проекта. Не стесняйтесь комментировать код: я не занимаюсь прошивками профессионально и хотел бы получить какие-то указания.
В качестве краткого введения: ESP32 написана на C++ с помощью библиотек Arduino для взаимодействия с приложением BLE Terminal на телефоне. Установка и конфигурирование BLE довольно сложны. Для начала нужно создать все характеристики для значений, которые вы хотели бы контролировать через BLE. Думайте о характеристике, как о переменной в вашем коде. BLE оборачивает переменную во множество обработчиков для получения и установки значения этой переменной.
Затем характеристики упаковываются в сервис с собственным UUID, который делает сервис уникальным и идентифицируемым из приложения. Наконец, вы должны добавить этот сервис к полезной нагрузке объявления, чтобы ваш сервис мог быть обнаружен устройством. Когда удалённое устройство соединяется с вашим сервисом и отправляет данные через характеристики, стол распознаёт, что пользователь хочет отрегулировать высоту до другой предустановки и начинает движение.
Для регулировки высоты столешница имеет определяющий текущую высоту встроенный в нижнюю часть сенсор TFMini-S LiDAR. Это забавный сенсор: он называется LiDAR, хотя на самом деле это лазер. Он использует оптику и светодиод, чтобы определить время полёта ИК-излучения. Так или иначе, сенсор определяет высоту стола. Затем контрольная плата определяет разницу между текущей высотой и запрашиваемой высотой и запускает мотор, который вращается в нужном направлении. Некоторые основные части кода приведены ниже, но вы можете увидеть весь файл здесь.
void setup()
{
Serial.begin(115200);
Serial2.begin(TFMINIS_BAUDRATE);
EEPROM.begin(3); // used for saving the height presets between reboots
tfminis.begin(&Serial2);
tfminis.setFrameRate(0);
ledcSetup(UP_PWM_CHANNEL, PWM_FREQUENCY, PWM_RESOLUTION);
ledcAttachPin(UP_PWM_PIN, UP_PWM_CHANNEL);
ledcSetup(DOWN_PWM_CHANNEL, PWM_FREQUENCY, PWM_RESOLUTION);
ledcAttachPin(DOWN_PWM_PIN, DOWN_PWM_CHANNEL);
state_machine = new StateMachine();
state_machine->begin(*t_desk_height, UP_PWM_CHANNEL, DOWN_PWM_CHANNEL);
BLEDevice::init("ESP32_Desk");
...
BLEServer *p_server = BLEDevice::createServer();
BLEService *p_service = p_server->createService(BLEUUID(SERVICE_UUID), 20);
/* ------------------- SET HEIGHT TO PRESET CHARACTERISTIC -------------------------------------- */
BLECharacteristic *p_set_height_to_preset_characteristic = p_service->createCharacteristic(...);
p_set_height_to_preset_characteristic->setCallbacks(new SetHeightToPresetCallbacks());
/* ------------------- MOVE DESK UP CHARACTERISTIC ---------------------------------------------- */
BLECharacteristic *p_move_desk_up_characteristic = p_service->createCharacteristic(...);
p_move_desk_up_characteristic->setCallbacks(new MoveDeskUpCallbacks());
/* ------------------- MOVE DESK UP CHARACTERISTIC ---------------------------------------------- */
BLECharacteristic *p_move_desk_down_characteristic = p_service->createCharacteristic(...);
p_move_desk_down_characteristic->setCallbacks(new MoveDeskDownCallbacks());
/* ------------------- GET/SET HEIGHT 1 CHARACTERISTIC ------------------------------------------ */
BLECharacteristic *p_get_height_1_characteristic = p_service->createCharacteristic(...);
p_get_height_1_characteristic->setValue(state_machine->getHeightPreset1(), 1);
BLECharacteristic *p_save_current_height_as_height_1_characteristic = p_service->createCharacteristic(...);
p_save_current_height_as_height_1_characteristic->setCallbacks(new SaveCurrentHeightAsHeight1Callbacks());
/* ------------------- GET/SET HEIGHT 2 CHARACTERISTIC ------------------------------------------ */
...
/* ------------------- GET/SET HEIGHT 3 CHARACTERISTIC ------------------------------------------ */
...
/* ------------------- END CHARACTERISTIC DEFINITIONS ------------------------------------------ */
p_service->start();
BLEAdvertising *p_advertising = p_server->getAdvertising();
p_advertising->start();
xTaskCreate(
updateDeskHeight, // Function that should be called
"Update Desk Height", // Name of the task (for debugging)
1024, // Stack size
NULL, // Parameter to pass
5, // Task priority
NULL // Task handle
);
}
В файле происходит гораздо больше, но контекста у этого кода достаточно, чтобы понять происходящее. Обратите внимание, что мы создаём и конфигурируем все обратные вызовы BLE для всех характеристик, включая ручное движение, установку и получение значений пресета и, самое важное, выравнивает стол согласно предустановке.
Изображение ниже показывает взаимодействие с характеристиками для регулировки высоты стола. Последний элемент головоломки — машина состояний, которой известна текущая высота стола, требуема пользователем высота, и работает с этими двумя значениями.
Итак, наконец у меня был стол, который делал всё, что я хотел. Я мог сохранить высоту в пресеты и извлечь высоты из памяти, чтобы установить стол в мои любимые положения. Я применял BLE Terminal на моём телефоне и компьютере, так я мог посылать сообщения в сыром виде моему столу и контролировать его позицию. Это работало, но я знал, что битва с BLE только начинается.
Голый интерфейс bluetooth… Всё, что оставалось на данный момент, — научиться писать приложения под iOS…
После всего этого моя жена сказала кое-что, что изменило весь проект: «А что, если сделать управление твоим голосом?».
Кроме крутости и добавления нового устройства к списку Google Assistant отпала необходимость писать приложение под iOS, чтобы управлять столом. И больше не нужно было доставать телефон, чтобы отрегулировать высоту. Ещё одна маленькая победа!
Добавление Интернета вещей
Теперь поговорим об апгрейде стола до управления голосом через Google Smart Home и как подружить его с Wi-Fi.
Добавить Wi-Fi было достаточно просто. Я заменил микроконтроллер Nordic NRF52 на ESP32 со встроенным WiFi. Большая часть софта была переносимой, потому что была написана на C++, а оба устройства могли программироваться с помощью Platform.IO и библиотек Arduino, включая tfmini-s, написанную мной для измерения текущей высоты стола.
Ниже показана архитектура системы взаимодействия стола с Google Smart Home. Давайте поговорим о взаимодействии между мной и Гуглом.
Итак, Bluetooth был включён. Пришло время выяснить, как взаимодействовать с Google Smart Home. Эта технология контролировала дом с помощью Smart Home Actions. В её действиях интересно то, что сервис действует как сервер OAuth2, а не как клиент. Большая часть проделанной с сервером работы была связана с реализацией приложения OAuth2 Node.js Express, которое добирается до Heroku и взаимодействует как прокси между Google и моим столом.
Мне повезло: была достойная реализация сервера с помощью двух библиотек. Первая библиотека — node-oauth2-server, была найдена здесь. Вторая библиотека express-oauth-server для подключения Express была найдена здесь.
const { Pool } = require("pg");
const crypto = require("crypto");
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
module.exports.pool = pool;
module.exports.getAccessToken = (bearerToken) => {...};
module.exports.getClient = (clientId, clientSecret) => {...};
module.exports.getRefreshToken = (bearerToken) => {...};
module.exports.getUser = (email, password) => {...};
module.exports.getUserFromAccessToken = (token) => {...};
module.exports.getDevicesFromUserId = (userId) => {...};
module.exports.getDevicesByUserIdAndIds = (userId, deviceIds) => {...};
module.exports.setDeviceHeight = (userId, deviceId, newCurrentHeight) => {...};
module.exports.createUser = (email, password) => {...};
module.exports.saveToken = (token, client, user) => {...};
module.exports.saveAuthorizationCode = (code, client, user) => {...};
module.exports.getAuthorizationCode = (code) => {...};
module.exports.revokeAuthorizationCode = (code) => {...};
module.exports.revokeToken = (code) => {...};
Далее идет настройка самого приложения Express. Ниже приведены конечные точки, необходимые для сервера OAuth, но вы можете прочитать полный файл здесь.
const express = require("express");
const OAuth2Server = require("express-oauth-server");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const flash = require("express-flash-2");
const session = require("express-session");
const pgSession = require("connect-pg-simple")(session);
const morgan = require("morgan");
const { google_actions_app } = require("./google_actions");
const model = require("./model");
const { getVariablesForAuthorization, getQueryStringForLogin } = require("./util");
const port = process.env.PORT || 3000;
// Create an Express application.
const app = express();
app.set("view engine", "pug");
app.use(morgan("dev"));
// Add OAuth server.
app.oauth = new OAuth2Server({
model,
debug: true,
});
// Add body parser.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static("public"));
// initialize cookie-parser to allow us access the cookies stored in the browser.
app.use(cookieParser(process.env.APP_KEY));
// initialize express-session to allow us track the logged-in user across sessions.
app.use(session({...}));
app.use(flash());
// This middleware will check if user's cookie is still saved in browser and user is not set, then automatically log the user out.
// This usually happens when you stop your express server after login, your cookie still remains saved in the browser.
app.use((req, res, next) => {...});
// Post token.
app.post("/oauth/token", app.oauth.token());
// Get authorization.
app.get("/oauth/authorize", (req, res, next) => {...}, app.oauth.authorize({...}));
// Post authorization.
app.post("/oauth/authorize", function (req, res) {...});
app.get("/log-in", (req, res) => {...});
app.post("/log-in", async (req, res) => {...});
app.get("/log-out", (req, res) => {...});
app.get("/sign-up", async (req, res) => {...});
app.post("/sign-up", async (req, res) => {...});
app.post("/gaction/fulfillment", app.oauth.authenticate(), google_actions_app);
app.get('/healthz', ((req, res) => {...}));
app.listen(port, () => {
console.log(`Example app listening at port ${port}`);
});
Кода довольно много, но я объясню основные моменты. Два используемых для сервера маршрута OAuth2, — это /oauth/token и /oauth/authorize. Они применяются для получения нового токена или обновления истекших токенов. Далее нужно заставить сервер реагировать на действие Google. Вы заметите, что конечная точка /gaction/fulfillment указывает на объект
google_actions_app
.
Google отправляет запросы на ваш сервер в определённом формате и предоставляет библиотеку, помогающую обработать эти запросы. Ниже приведены функции, необходимые для связи с Google, а весь файл целиком лежит здесь. Наконец, есть конечная точка /healthz, о которой я расскажу в конце статьи.
Конечная точка /gaction/fulfillment использует промежуточное ПО под названием app.oauth.authenticate(), тяжёлая работа по обеспечению работы сервера OAuth2 была направлена на то, чтобы работало это промежуточное ПО. Оно проверяет, что токен-носитель, предоставленный нам Google, ссылается на существующего пользователя и не истёк. Далее маршрут отправляет запрос и ответ объекту google_actions_app
.
Google отправляет запросы на ваш сервер в определённом формате и предоставляет библиотеку, помогающую анализировать и обрабатывать эти запросы. Ниже приведены функции, необходимые для связи с Google, но вы можете просмотреть весь файл целиком здесь.
const { smarthome } = require('actions-on-google');
const mqtt = require('mqtt');
const mqtt_client = mqtt.connect(process.env.CLOUDMQTT_URL);
const model = require('./model');
const { getTokenFromHeader } = require('./util');
mqtt_client.on('connect', () => {
console.log('Connected to mqtt');
});
const updateHeight = {
"preset one": (deviceId) => {
mqtt_client.publish(`/esp32_iot_desk/${deviceId}/command`, "1");
},
"preset two": (deviceId) => {
mqtt_client.publish(`/esp32_iot_desk/${deviceId}/command`, "2");
},
"preset three": (deviceId) => {
mqtt_client.publish(`/esp32_iot_desk/${deviceId}/command`, "3");
},
};
const google_actions_app = smarthome({...});
google_actions_app.onSync(async (body, headers) => {...});
google_actions_app.onQuery(async (body, headers) => {...});
google_actions_app.onExecute(async (body, headers) => {...});
module.exports = { google_actions_app };
Когда вы добавите интеллектуальное действие в свой аккаунт Google, Google выполнит запрос на синхронизацию. Этот запрос позволяет узнать, какие устройства доступны из аккаунта. Далее происходит опрашивающий запрос: Google запрашивает ваши устройства, чтобы определить их текущее состояние.
Когда вы впервые добавляете действие Google в свой аккаунт Smart Home, вы заметите, что Google сначала отправляет запрос синхронизации, а затем опрашивающий запрос, чтобы получить целостное представление о ваших устройствах. Последний — запрос это запрос на выполнение, который Google сообщает вашим устройствам, чтобы они что-то делали.
«Особенности» (trait) устройства Google Smart Home
Google использует особенности устройства для предоставления элементов пользовательского интерфейса управления вашими устройствами в Google, а также для создания коммуникационных шаблонов голосового управления. Некоторые из особенностей включают в себя следующие настройки: ColorSetting, Modes, OnOff, and StartStop. Мне потребовалось некоторое время, чтобы решить, какая особенность будет лучше всего работать в моём приложения, но позже я выбрал режимы.
Вы можете думать о режимах как о выпадающем списке, где выбирается одно из N предопределённых значений или, в моём случае, предустановки высоты. Я назвал свой режим «высота», а возможные значения — «предустановка один», «предустановка два» и «предустановка три». Это позволяет мне управлять своим столом, говоря: «Эй, Google, установите высоту моего стола в предустановку один», — и Google отправит соответствующий запрос на выполнение в мою систему. Вы можете прочитать больше об особенностях устройств Google здесь.
Проект в деле
Наконец, Google Smart Home и мой компьютер начали общаться. До этого для локального запуска сервера Express я использовал ngrok. Теперь, когда мой сервер наконец заработал достаточно хорошо, пришло время сделать его доступным для Google в любое время. Значит, нужно было разместить приложение на Heroku — это поставщик PaaS, упрощающий развёртывание и управление приложениями.
Одно из главных преимуществ Heroku — режим дополнений. С дополнениями очень просто добавить CloudMQTT и сервер Postgres для приложения. Ещё одно преимущество использования Heroku — простота сборки и развёртывания. Heroku автоматически определяет, какой код вы используете, и создаёт/развёртывает его для вас. Вы можете найти более подробную информацию об этом, прочитав о Heroku Buildpacks. В моём случае всякий раз, когда я отправляю код в git remote Heroku, он устанавливает все мои пакеты, удаляет все зависимости разработки и развёртывает приложение, и это всё простой командой «git push heroku main».
Всего в несколько кликов CloudMQTT и Postgres стали доступны моему приложению, и мне нужно было использовать только несколько переменных среды, чтобы интегрировать эти сервисы с моим приложением. Heroku не потребовал денег. Однако CloudMQTT — сторонним дополнение за $5 в месяц.
Я считаю, что необходимость в Postgres не нуждается в комментариях, но CloudMQTT заслуживает больше внимания.
Из Интернета в частную сеть. Сложный способ
Есть несколько способов предоставить доступ к приложению или, в моём случае, устройству Интернета вещей. Первый — открыть порт в моей домашней сети, чтобы вывести устройство в Интернет. В этом случае моё приложение Heroku Express отправит запрос на моё устройство, используя публичный IP-адрес. Это потребовало бы от меня иметь публичный статический IP-адрес, а также статический IP-адрес для ESP32. ESP32 также должен был бы действовать как HTTP-сервер и всё время слушать инструкции от Heroku. Это большие накладные расходы для устройства, получающего инструкции несколько раз в день.
Второй способ называется «дырокол». С ним вы можете задействовать сторонний внешний сервер для доступа устройства в Интернет без необходимости переадресации портов. Ваше устройство в основном подключается к серверу, который устанавливает открытый порт. Затем другая служба может подключиться непосредственно к вашему внутреннему устройству, получив открытый порт от внешнего сервера. Наконец, он подключается непосредственно к устройству, используя этот открытый порт. Подход может быть правильным или не совсем правильным: я прочитал о нём только часть статьи.
Внутри «дырокола» происходит многое, и я не до конца понимаю происходящее. Однако, если вам интересно, есть несколько интересных статей, объясняющих больше. Вот две статьи, которые я прочитал, чтобы лучше понять «дырокол»: Википедия и статья из MIT, написанная Брайаном Фордом и другими.
Из Интернета в частную сеть через IoT
Я не был очень доволен этими решениями. Я подключил много смарт-устройств в дом, и мне никогда не приходилось открывать порт на маршрутизаторе, так что переадресации портов не было. Кроме того, пробивка дыр кажется гораздо более сложной, чем то, что я ищу, и лучше подходит для сетей P2P. В результате дальнейших исследований я обнаружил MQTT и узнал, что это протокол для IoT. Он обладает некоторыми преимуществами, такими как низкое энергопотребление, настраиваемая отказоустойчивость, и не требует переадресации портов. MQTT — протокол типа издатель/подписчик, это означает, что стол — подписчик определённого топика, а приложение Heroku — издатель этого топика.
Таким образом, Google связывается с Heroku, этот запрос анализируется, чтобы определить запрошенное устройство и его новое состояние или режим. Затем приложение Heroku публикует сообщение на сервере CloudMQTT, развёрнутом как надстройка на Heroku, с указанием столу перейти к новой предустановке. Наконец, стол подписывается на топик и получает сообщение, опубликованное приложением Heroku, наконец, стол настраивает свою высоту в соответствии с запросом! В файле googleactionsapp вы заметите, что есть функция updateHeight, которая публикует один номер в топике MQTT для определённого идентификатора устройства. Вот так приложение Heroku публикует в MQTT запрос на перемещение стола.
Последний шаг ё получение сообщения на ESP32 и перемещение стола. Я покажу некоторые основные моменты кода для стола ниже, а весь исходный код находится здесь.
void setup()
{
Serial.begin(115200);
...
tfminis.begin(&Serial2);
tfminis.setFrameRate(0);
...
state_machine = new StateMachine();
state_machine->begin(*t_desk_height, UP_PWM_CHANNEL, DOWN_PWM_CHANNEL);
setup_wifi();
client.setServer(MQTT_SERVER_DOMAIN, MQTT_SERVER_PORT);
client.setCallback(callback);
...
}
Когда стол загружается, мы сначала запускаем связь между TFMini-S — датчиком расстояния — чтобы получить текущую высоту стола. Затем мы настраиваем конечный автомат для движения стола. Конечный автомат получает команды через MQTT, а затем отвечает за согласование запроса пользователя с фактической высотой стола, считываемой датчиком расстояния. Наконец, мы подключаемся к сети Wi-Fi, подключаемся к серверу MQTT и настраиваем обратный вызов для любых данных, получаемых по теме MQTT, на которую мы подписаны. Ниже я покажу функцию обратного вызова.
void callback(char *topic, byte *message, unsigned int length)
{
...
String messageTemp;
for (int i = 0; i < length; i++)
{
messageTemp += (char)message[i];
}
if (messageTemp == "1") {
state_machine->requestStateChange(ADJUST_TO_PRESET_1_HEIGHT_STATE);
}
if (messageTemp == "2") {
state_machine->requestStateChange(ADJUST_TO_PRESET_2_HEIGHT_STATE);
}
if (messageTemp == "3") {
state_machine->requestStateChange(ADJUST_TO_PRESET_3_HEIGHT_STATE);
}
...
}
Конечный автомат регистрирует изменение состояния, полученное в теме MQTT. Затем он в основном цикле обрабатывает новое состояние.
void loop()
{
if (!client.connected())
{
reconnect();
}
client.loop();
state_machine->processCurrentState();
}
Основной цикл выполняет несколько задач: во-первых, он повторно подключается к серверу MQTT, если еще не подключён. Затем обрабатывает все данные, полученные через топик MQTT. Наконец, код отрабатывает, перемещая столешницу в нужное место, запрошенное в топике MQTT.
Вот и всё! Стол полностью управляется голосом и обменивается данными с Google для получения команд!
Последние заметки
Последняя конечная точка, о которой я не упоминал, — конечная точка / healthz. Это связано с тем, что Google ожидает, довольно быстрого ответа, и загрузка приложения Heroku при каждом запросе в моём случае не работает. Я установил службу ping для проверки связи с конечной точкой /healthz каждую минуту, чтобы служба оставалась работоспособной и была готова ответить. Если вы планируете сделать что-то подобное, помните, что на это будут израсходованы все бесплатные часы на стенде. Сейчас всё нормально: это единственное используемое на Heroku приложение. Кроме того, за 7 долларов в месяц вы можете перейти на тарифный план Heroku’s Hobby с поддержкой постоянной работы приложения.
Создание устройства IoT связано с большими накладными расходами в начале. Я сконструировал оборудование, построил схему управления, настроил сервер MQTT, написал сервер Express OAuth2 и научился взаимодействовать с Google Smart Home через действия. Первоначальные накладные расходы были огромными, но я чувствую, что многого добился! Не говоря уже о том, что сервер MQTT, сервер приложений Express OAuth2 и Google Smart Home Actions можно использовать для другого проекта. Умные дома мне интересны, и я могу попытаться расширить свой репертуар IoT-устройств, включив в него датчики, отслеживающие происходящее вокруг моего дома и сообщающее об этом через MQTT. Датчики для мониторинга почвы, температуры и датчики света будет очень интересно мониторить и анализировать.
Что дальше?
Высота столешницы сейчас измеряется в лучшем случае ненадёжно. Я использую в целом работающим инфракрасным датчиком расстояния TFMini-S. Замечено, что высота стола немного меняется в течение дня, когда меняется окружающее освещение в комнате. Я заказал датчик угла поворота, чтобы подсчитать обороты стержня, проходящего через стол. Это должно дать мне движения точнее в любое время дня. У меня также есть доступ к серверу, который я размещаю в подвале. На нём я могу исследовать собственный сервер Mosquitto MQTT, Node-RED и Express-приложения OAuth2, если захочу хостить что-то сам. Наконец, сейчас вся электроника лежит прямо на моём столе. Я планирую организовать устройства так, чтобы все было красиво и аккуратно!
Комментариев нет:
Отправить комментарий