• Sky
  • Blueberry
  • Slate
  • Blackcurrant
  • Watermelon
  • Strawberry
  • Orange
  • Banana
  • Apple
  • Emerald
  • Chocolate
  • Charcoal
Sign in to follow this  
Followers 0
Don_Arturos

Управление поворотным столом через “Окей, Google!” и Omega2+

1 post in this topic

21Oq3SGSoEsk00R0f-pgYo2rJw5_ni9CbN7bQWbEZvrRY2zuQs5gqvtExrboUAewHw5xqktNUoGkRodgS-etrOw2TFqYhnuUCGLIjjBTADRSdXIYi59pfQZ4FiYLMcCuv1MWyasC

Всем привет! В данной статье речь пойдет о том, как подключить любое электронное устройство к Google Assistant, тот самый, что откликается на фразу “Окей, Google!”

Все началось с того, что я приобрел электрический стол-трансформер E2B фирмы FlexiSpot. Очень классная штука - можно менять размеры и высоту под самые разные задачи при помощи двух двигателей. Для управления используется встроенная в стол клавиатура.

1Am7YTyhar7rNOgLWizaIKTmtTwFev6Ru00s_M2f0TkHrs4IRGlNnzIod7U7-6Bjg151cyu-xjNQSvd3wXwfp3ER3XSHU_CMCLi36r_FHBeJ2o76Jpzc8W8sBq7tkpB81Mr0WJVC

В один из вечеров я подумал, а почему бы мне не использовать Google Assistant для голосового управления девайсом? Так зарождался этот проект...

На воплощение плана мне понадобилось 5 часов упорного инженерного труда. Интерес моего проекта заключается в том, что вместо стола можно взять любое другое электронное устройство и точно также настроить управление. Теперь я поделюсь решением с вами.

Внимание! Вся информация предоставляется, как говорится, на свой страх и риск. Автор статьи не несет ответственности за любые убытки или издержки, с которыми может столкнуться читатель. Я не являюсь профессионалом, но моих университетских знаний электроники мне было достаточно для создания проекта.

ЭТАП ПЕРВЫЙ: “Как это устроено?”

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

Y8Rrt-8fp74wcknBBmPyR5g89Jw4q7wfaR4zzDCLYUdDYPv61VUtoO1TDclwlQVCniBQw2c5pb3tUluGgofCAHT65kLAdVDvvdgJEVxdeAfjrhACPRpzFz1tJ7M0ni_qXZMEzGwR

Давайте посмотрим на внутренности.

mpND2dMuM9dRX3qDpgIdXyoCM0LpEeqQiESTs4QeEU0KtJpu43yPPPWJm57KKJPe9U_orYRuHYlueZ0gh9EckXb9LZceTdWYyxoHaZvTmvORwuZHDtj9fJeRGUBcuR2ysyOE1NeM

Для начала расскажу вам о каждом элементе платы.

  • UP Button и DOWN Button отвечают за поднятие или опускание стола с 70 см до 120 см (от 42,1 до 63 дюймов);

  • Кнопка Setting Modes настраивает определенный режим для стола. Можно использовать кнопки Mode 1, Mode 2, Mode 3 для сохранения настроек определенной высоты;

  • Auto Tracking Mode служит для слежения за положением человека и напоминает о том, когда пора вставать;

  • TM1650: светодиодный контроллер, отвечающий за работу дисплея слева;

  • STM8S (STM8S103F2): основные “мозги” устройства. Даташит на микроконтроллер можно посмотреть здесь.

Теперь начинается самое интересное: необходимо залезть в те самые “мозги” стола и разобраться какие сигналы с микроконтроллера отвечают за команды поворотов двигателей. Этот способ не сработал - мой паяльник был слишком большой мощности и не оставил живого места на пине №14 моего контроллера.

rFS_s_cva_YSX5y-nXTrAPnovI346nPeWhQEaBD-Uq8j9-z07Xls87GEhi0aWowUYyBMyMiKAJ3XIX8ID-w5L93GBtY1jI-rdy9KDrHYA5gM7Nr8zSVGbT811xb3OlOKoAJ6OQeN

Рисковать дальше я не стал и придумал другой, как оказалось, более простой способ. Я просто взломал кнопки M1, M2 и M3. Я уже говорил, что они хранят значения высоты и при срабатывании отдают соответствующую команду столу.

rQeZs1CNcMOEJYDkXGi5YvMVnQpHCSWEvlvNgxR6F7TQNo0XO-c8bsEj4mxPYxohFCYIpb8Bj0eT423p1bysg65qRrvrN_74o3cEqa8Uk-lSIRRKbtgOk4P4wE2gvyO3tvD0-xOH

Теперь нам нужен электронный переключатель, чтобы сымитировать нажатие кнопок.

ЭТАП ВТОРОЙ: “Ручная отладка”

Перед тем, как начать все автоматизировать, попробуем протестировать наш способ вручную. Для этого я построил NPN транзисторную схему переключателя. Он идеально подходит для низковольтных устройств постоянного тока, а значит и для нашего случая тоже. Повторюсь: наша задача смоделировать кнопку, для питания которой требуется несколько миллиампер.

Наша транзисторная схема будет выглядеть именно так:

PIfKEmOcyA62WMba09F0eKXVZWgApUS73lfO3vXGxbQ8lLS9Qk_xzlOwkznm2Cm5Hr6XoTgKgXkH1N1FFGsbAO5SkiXHNcCpz8g-vYDbyx2ru3o_sJjd31PgKVfoCQhK41Dfl1yE

Все работает, отлично! 

M37SaJ6vGEiTC8_Rf74xD6qE4dFxS4xApUQTLK3U8bbohLbQtYXPi7A_t28ts-_tczB85QWHEFzvCiTa4-YrHoZo0tmZSUN_jiR2K6BwKZMf-d1ZhcoPuoLXHtoLtTerf33pGVJb

Теперь подключим настольный контролер по этой схеме:

aNoJsDLYTBjrEhmNEs7XNaWD_RN1B2QeYfH5842roo8GgAHF4bjpgUSrD-St_n1PSUBEdPakfRuLlYgdn7uWejGBUmL8ibUu71tfhXKSg3GnizKqMO6P0V6M1iKBpehRSfY92lUI

На этой схеме J1, J2 и J3 - перемычки, которые мы совсем недавно припаяли к настольному контроллеру (к M1, M2 и M3 соответственно). На макетной плате это выглядит так:

k3nnBH9Mhy70RLd33FoWE2wPOnAgZNkRH8fz49VHkQiDKnfah83vvGunKc-FrUSzbfinl5-gUU4Zx-Z_ARKSDAEKKED2EiJ8ZYWBIir_6_rskX_dJO3A1pNOv-vyZzIPmq9yOvhj

Обратите внимание, что провода V1, V2, V3 на данный момент подключать не нужно.

ЭТАП ТРЕТИЙ: “Используем программируемый микроконтроллер”

Для этого этапа вам понадобится любая плата разработки, которая имеет поддержку Wi-Fi соединения. Arduino здесь сразу отпадает (покупать плату расширения не очень разумно), Raspberry Pi - довольно дорогой вариант, и поэтому я остановился на Omega2+ - небольшом, но мощном микроконтроллере со скромным ценником в $15. Купить его можно тут.

 

xtiVbZIk0VhucejsB5YbXnddU5wbU0HX-X5d7edhuykgO3fcpx28RX5fufUku_9fIBJAqABC7Q8gwr81OyrFG2qcrUv3D48U6Fi1ogEH3rNnjQ5_P5iwXawfmt5jthDR-Dm_IrkS

Я подключаю кнопки по схеме ниже (обратите внимание, что провода V1, V2, V3 теперь подключены к пинам 1,2, 3 соответственно).

-YynEVCxNOUq45SHdamp2IgMmpC75W2_KgQ7quCB81EGXqL9EZtS6S5y9gdjnovWwgzS_zZp9r9yp7gmXe1VPK-E_iVf6zG7nYWIDrlyM4Tnt7cz8HA8Wd5TLGoSzmonuxCaWznQ

Так выглядит макет моего проекта:

HIFmylHDrdy5ca3J86zauldmRWmyMsQMzsRG9zzWHMkW4bjiHND4owJAFyLauKpa93x24kkOKmyEfYav6NGcNKsiZ9i1eiS1bl7nxFj2yqm53mFZZk17UCpGrMR0WO0PofZcQ_Xf

Теперь нам нужно написать простую программу, которая превратит пины 1,2 и 3 в переключатели для кнопок. Например, если установить на первом пине высокий логический уровень, то он будет подавать 3,3В через транзистор, замыкая контакты  и имитируя нажатие кнопки: сигнал будет принят контроллером STM8S и двигатель выполнит определенную команду.

Первоначальный код имеет следующий вид:

const omega2gpio = require('omega2-gpio');
const gpio = new omega2gpio();
gpio.tests().then( ()  => {
  const p = g.pin({pin: 1, mode: 'output'});
  
  p.set(1); // set to HIGH
  
  setTimeout( () => {
    p.set(0); // set to LOW
  }, 800);
});

Омега2 имеет встроенную команду для управления пинами (правильнее называть их GPIO). Например, для установки пина №1 в режим OUTPUT при высоком логическом уровне нужно прописать следующее: gpioctl dirout-high 1

Но мне посчастливилось найти NPM модуль, который упрощает работу с ними.

Также нам необходимо прописать управление через WiFi, чтобы подстроить под эту систему Google Assistant:

const http = require('http');
const heartbeat = 'echo heartbeat > /sys/class/leds/omega2p\:amber\:system/trigger';

const o = require('omega2-gpio');
const g = new o();

const run = (pin, done) => {

        g.tests().then( ()  => {
                const p = g.pin({pin: pin, debug: true, mode: 'output'});
                p.set(1);
                setTimeout( () => {
                        p.set(0);
                        done();
                }, 800);
        });
};

const send = (rs, text, status) => {
        rs.writeHead(status, { 'Content-Type': 'application/json' });
        rs.end(text, 'utf-8');
};

http.createServer( (rq, rs) => {
        console.log('request: ', rq.url);

        if (rq.url === '/mode/1') {
                run(1, () => send(rs, '{"mode": 1, "status": "OK"}', 200));
        }
        else if (rq.url === '/mode/2') {
                run(2, () => send(rs, '{"mode": 2, "status": "OK"}', 200));
        }
        else if (rq.url === '/mode/3') {
                run(3, () => send(rs, '{"mode": 3, "status": "OK"}', 200));
        }
        else {
                send(rs, '404', 404);
        }


}).listen(1337, '0.0.0.0', (err) => {
        if (err) {
                console.error('Unable to start the server', err);
                return false;
        }
        console.log('Server running at http://0.0.0.0:1337');

        const exec = require('child_process').execSync(heartbeat);

});

После загрузки кода можно отправить запрос http://192.168.80.84:1337/mode/1 для установки пина 1 на HIGH в течение 800 мс, после чего обратно вернётся на уровень LOW. Аналогичные действия можно проделать и с остальными пинами.

Последним действие нужно запланировать автоматический запуск сценария во время загрузки. Для этого редактируем файл скрипта /etc/rc.local:

node /root/desk-controller-assistant-server.js &

exit 0

Разработка завершена - переходим к финальному этапу!

K8wye6bkxUBFfw-1313hM7Z90K7v5ryVP84rxUXISg2wBEYfg2EVS2xFpQ8oIOtnsCb39vawQ_rP_yW2se6Sexmg-eMf9oRZjnK4P39a_jKdfq_9r0tWgzMQLxsCUPkMRISdbkJl

ЭТАП ЧЕТВЕРТЫЙ: “Взаимодействие с Google Assistant”

Рекомендую перед выполнением данного этапа назначить статический IP-адрес для вашего устройства.

Для взаимодействия устройства с Google Assistant по локальной сети я использую ngrok. Откроем с его помощью HTTP страницу по адресу: http://198.168.80.84:1337

Для этого используем команду:

ngrok http -subdomain = wassimchegham 192.168.86.84:1337

После запуска ngrok я получил публичный URL: https://wassimchegham.ngrok.io/

И теперь я могу отправлять HTTP-запросы на мое устройство, чтобы установить нужный режим: https://wassimchegham.ngrok.io/mode/1

Внимание! ngrok выставит ваш локальный компьютер в Интернет, так что убедитесь, что выключили его, если вы его не используете.

Настройка Google Assistant. СПОСОБ ПЕРВЫЙ.

Быстрее всего настроить Google Assistant можно через печально известный IFTTT. Вот как это делал я:

ЕСЛИ...

aEbKE5Zn5rVUfh23lyIFnhG_I4-eWPw5YFBuJgb12Urx4iGkGpWczIvH2fjFDFu2Ux4I5RZNsT_8ReUiTRRSvsX5WbWXo6xtMiEbX7Ixc-RIn5KnsNRleCE570Hirzs0lhLMSEEl

ЗАТЕМ…

MpW7RRGkjGShOzT2Cl6T5nDmukevRtSZ2hCVU5OwiRPG603O1dbR5RlFfZwWkJ6D5KXQQuvxxpX_oMpj6r0u0atnn5f-wBKwVIwYRu88OtZADmSsSeD4ujKD2PLadNXFkY_fRjbI

Настройка Google Assistant. СПОСОБ ВТОРОЙ.

Рекомендую использовать именно этот способ. Он осуществляется через Smart Home API. Для начала необходимо создать действие в Smart Home (Smart Home Action )., которое синхронизирует вашего устройство с голосовым помощником.

О том, как работает данный метод, следует прочитать здесь: https://codelabs.developers.google.com/codelabs/smarthome-washer/#0

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

'use strict';

const fetch = require('node-fetch');
const functions = require('firebase-functions');
const { smarthome } = require('actions-on-google');
const util = require('util');
const admin = require('firebase-admin');
admin.initializeApp();

const firebaseRef = admin.database().ref('/');

const agentUserId = '23213213131321321321';

exports.auth = functions.https.onRequest((request, response) => {
  const responseurl = util.format(
    '%s?code=%s&state=%s',
    decodeURIComponent(request.query.redirect_uri),
    'xxxxxx',
    request.query.state
  );
  console.log(responseurl);
  return response.redirect(responseurl);
});

exports.token = functions.https.onRequest((request, response) => {
  const grantType = request.query.grant_type
    ? request.query.grant_type
    : request.body.grant_type;
  const secondsInDay = 86400; // 60 * 60 * 24
  const HTTP_STATUS_OK = 200;
  console.log(`Grant type ${grantType}`);

  let obj;
  if (grantType === 'authorization_code') {
    obj = {
      token_type: 'bearer',
      access_token: '123access',
      refresh_token: '123refresh',
      expires_in: secondsInDay
    };
  } else if (grantType === 'refresh_token') {
    obj = {
      token_type: 'bearer',
      access_token: '123access',
      expires_in: secondsInDay
    };
  }
  response.status(HTTP_STATUS_OK).json(obj);
});

let jwt;
try {
  jwt = require('./key.json');
} catch (e) {
  console.warn('Service account key is not found');
  console.warn('Report state will be unavailable');
}

const queryFirebase = deviceId =>
  firebaseRef
    .child(deviceId)
    .once('value')
    .then(snapshot => {
      const snapshotVal = snapshot.val();
      return {
        on: snapshotVal.on,
        online: snapshotVal.online,
      };
    });

const queryDevice = deviceId =>
  queryFirebase(deviceId).then(data => ({
    on: data.on
  }));

const app = smarthome({
  debug: true,
  key: jwt.key,
  jwt: jwt
});

app.onSync((body, headers) => {
  return {
    requestId: body.requestId,
    payload: {
      agentUserId,
      devices: [
        {
          id: 'standing-desk-123',
          type: 'action.devices.types.SWITCH',
          traits: ['action.devices.traits.OnOff'],
          name: {
            defaultNames: ['My Standing Desk'],
            name: 'Standing Desk',
            nicknames: ['Standing Desk']
          },
          deviceInfo: {
            manufacturer: 'Wassim Chegham',
            model: '123456789',
            hwVersion: '1.0',
            swVersion: '1.0'
          }
        }
      ]
    }
  };
});

app.onQuery(body => {
  const { requestId } = body;
  const device = body.inputs.pop().payload.devices.pop();
  const deviceId = device.id;

  return queryDevice(deviceId).then(data => {
    return {
      requestId,
      payload: {
        devices: {
          [deviceId]: data
        }
      }
    };
  });
});

app.onExecute((body, headers) => {
  const { requestId } = body;
  const commands = body.inputs.pop().payload.commands;
  const command = commands.pop();
  const device = command.devices.pop();
  const deviceId = device.id;
  const exec = command.execution.pop();
  const { params } = exec;

  console.log(body);

  firebaseRef
    .child(deviceId)
    .child('state')
    .update({
      on: params.on
    });

  return {
    requestId,
    payload: {
      commands: [
        {
          ids: [deviceId],
          status: 'SUCCESS',
          states: {
            online: true
          }
        }
      ]
    }
  };
});

app.onDisconnect((body, headers) => {
  return {};
});

exports.smarthome = functions.https.onRequest(app);

exports.requestsync = functions.https.onRequest((request, response) => {
  return app.requestSync(agentUserId)
  .then((res) => {
    console.log('Request sync was successful', res);
  })
  .catch((res) => {
    console.error('Request sync failed', res);
  });
});

/**
 * Send a REPORT STATE call to the homegraph when data for any device id
 * has been changed.
 */
exports.reportstate = functions.database.ref('{deviceId}/state').onWrite(event => {
  console.info('Firebase write event triggered this cloud function');
  if (!app.jwt) {
    console.warn('Service account key is not configured');
    console.warn('Report state is unavailable');
    return;
  }
  const snapshotVal = event.after.val();
  console.log('snapshotVal', snapshotVal);
  const mode = snapshotVal.on == true ? '3' : '1';

  return fetch(`https://wassimchegham.ngrok.io/mode/${mode}`)
    .then(res => {
      console.log(res.ok, res.status, res.statusText, res.headers.raw(), res.headers.get('content-type'));
      return res;
    })
    .then(res => (res.status == '404' ? null : res.json()))
    .then(json => {
      if (json) {
        return {
          requestId: 'xxxxxxxxxx',
          agentUserId,
          payload: {
            devices: {
              states: {
                [event.params.deviceId]: {
                  on: snapshotVal.on
                }
              }
            }
          }
        };
      } else {
        throw new Error('deviceOffline');
      }
    })
    .then(postData => app.reportState(postData))
    .then(data => {
      console.log('Report state came back');
      console.info(data);
    })
    .catch((res) => {
      return {
        requestId: 'xxxxxxxxxx',
        agentUserId,
        payload: {
          errorCode: 'deviceOffline'
        }
      };
    });
});

По сути, наш код делают следующее: мы отправляем команды в “Умный дом”, каждое текущее состояние сохраняется в базе данных Firebase Realtime. В случае изменения состояния мы отправляем HTTP запрос на наше локальное устройство через ngrok.

XegWIimeLiiR5qhZ1AV6ARoTxDLdoXtaXo_ySsP6vGXeP7M6Ti7Bd3t4tuQ_1X6xAA75PDHeAVlD28-Vb6ScCZFJPz7geKB3Okez7nS0c5T7KnMqnlq0brZFET6XZf_9XL96e9NY

Теперь нам остается подключить готовое устройство к помощнику Google и наслаждаться беззаботным настоящим:)

Данная статья является переводом: https://medium.com/google-developer-experts/hey-google-set-my-desk-to-standing-mode-b21dcc40d4b5

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0