В этой статье я покажу, как я превратил свой
стол с ручной регулировкой высоты в автоматизированный стол с
Интернетом вещей. Я расскажу, как подобрать размеры и запустить
моторы, а также как подключить ваше устройство IoT к Google при
помощи Heroku в качестве публичного интерфейса.
Если коротко, у этого проекта две особенности. Первое: стол
подключается из 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 долларов в
месяц вы можете перейти на
тарифный план Herokus 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, если захочу хостить что-то сам.
Наконец, сейчас вся электроника лежит прямо на моём столе. Я
планирую организовать устройства так, чтобы все было красиво и
аккуратно!
Спасибо, что прочитали статью! Для удобства даю все
ссылки.