[Google] Google Home 으로 PLC 제어

2022. 11. 22. 21:48개발/Google

유튜브 링크

https://www.youtube.com/watch?v=ENuUDIMJ7fs 

 

 

 

Google에서 제공하는 Google Home app을 사용해서 PLC를 제어한다.

Google Home 실행 화면

위 어플에서 원격으로 PLC를 제어할 수 있도록 연동한다.


Google Action

https://console.actions.google.com/u/0/

 

로그인 - Google 계정

이메일 또는 휴대전화

accounts.google.com

1. Google action으로 이동

 

 

2. 새 프로젝트 생성

 

3.Smart Home 선택 후 Start Building

 

4. Build 되면 Quick setup에서 'Name your Smart Home action' 클릭

 

5. Develop / Invocation 에서 이름 설정

이 이름은 Google Home 앱에서 사용할 이름

 

6. Develop / Actions 에서 Fulfillment URL 입력

https://us-central1-<project-id>.cloudfunctions.net/smarthome

<project-id> 대신 해당 프로젝트 id로 수정

 

7. Develop / Account linking 에서 인증값 입력

공식 프로젝트가 아닌 개인이 사용하는 테스트 프로젝트이므로

Auth, Token 등 인증서들을 default 값으로 설정

  • Client ID : ABC123
  • Client secret : DEF456
  • Auth URL : https://us-central1-<project-id>.cloudfunctions.net/fakeauth
  • Token URL : https://us-central1-<project-id>.cloudfunctions.net/faketoken

Google Cloud Platform

https://console.cloud.google.com/

 

Google 클라우드 플랫폼

로그인 Google 클라우드 플랫폼으로 이동

accounts.google.com

 

 

1. Google Action에서 생성했던 프로젝트를 선택

 

2. 메뉴에서 APIs & Services > Library 로 이동

 

3. Homegraph 검색 후 ENABLE 버튼 클릭

 

4. APIs & Services > Credentials 로 이동 후

인증서를 Service account로 생성

 

5. Service name을 지정 후 DONE 클릭

 

6. 생성된 Service Accounts 클릭

 

7. Keys 로 이동 후 JSON 형식의 키 생성

다운받은 키 파일명을 smart-home-key.json 로 수정


Google Home 앱

등록한 Google action을 클릭하면 연동된다.

연동이 되지 않고 오류가 발생하면

 

Google action 의 이름을 변경해주면 해결된다.


Firebase Deploy

https://github.com/choiprin/Arduino/tree/main/Google%20Smart%20Home

 

GitHub - choiprin/Arduino

Contribute to choiprin/Arduino development by creating an account on GitHub.

github.com

1. 위 github링크에 있는 폴더를 다운받은 후

functions 디렉토리에 방금 다운받은 smart-home-key.json 파일 이동

 

2. 다운받은 경로에 맞춰서 Google Smart Home/firebase/functions 디렉토리로 이동

 

3. 디렉토리에서 cmd 실행 및 명령어 순서대로 입력

npm install -g firebase-tools  
npm install  
firebase login  
firebase projects:list

4. 리스트를 확인 후

 

firebase use project-id

5. project-id 대신 사용할 프로젝트의 id를 입력 후 실행

 

firebase deploy

5. Firebase 에 배포

 

  "engines": {
    "node": "10"
  }

6. firebase deploy 단계에서 에러 발생 시

해당 디렉토리의 package.json Node버전을 10으로 변경

위와 같이 firebase 의 Realtime database에 데이터가 입력됨

 

켰을 때 Realtime Database
껐을 때 Realtime Database


Arduino

//FirebaseESP8266.h must be included before ESP8266WiFi.h
#include "FirebaseESP8266.h"
#include <ESP8266WiFi.h>
#include <SoftwareSerial.h>
#include "CRC.h"

#define FIREBASE_HOST " firebase Host 값 입력 " 
#define FIREBASE_AUTH " firebase Auth 값 입력 " // 자세한 내용은 이전영상 참고
#define WIFI_SSID " Wifi SSID 입력 "
#define WIFI_PASSWORD " Wifi PW 입력 "

//#define WIFI_SSID "mecha1203"
//#define WIFI_PASSWORD "mecha1203"

//Define FirebaseESP8266 data object
FirebaseData firebaseData1;
FirebaseData firebaseData2;
FirebaseData firebaseData;

unsigned long sendDataPrevMillis = 0;
String path = "/6/OnOff";
uint16_t count = 0;

SoftwareSerial mySerial(D7, D4); // RX,Tx 
int act=0,outPlc=0;
int Out[8]={0},In[10]={0};  // plc 입력과 출력 저장 
String inputString = "";         // 받은 문자열
String sIn="",sInPre="";  // 입력값이 달라질 때만 mqtt로 송신

unsigned long previousMillis = 0;     
const long interval = 1000;  

void doTick();
void printResult(FirebaseData &data);
void printResult(StreamData &data);
void outResult(StreamData &data);
void crd16Rtu();
void plcInData();
void serialEvent();

void streamCallback(StreamData data)
{
  Serial.println("Stream Data1 available...");
  Serial.println("STREAM PATH: " + data.streamPath());
  Serial.println("EVENT PATH: " + data.dataPath());
  Serial.println("DATA TYPE: " + data.dataType());
  Serial.println("EVENT TYPE: " + data.eventType());
  Serial.print("VALUE: ");
  printResult(data);
  Serial.println();
  outResult(data);
}

void streamTimeoutCallback(bool timeout)
{
  if (timeout)
  {
    Serial.println();
    Serial.println("Stream timeout, resume streaming...");
    Serial.println();
  }
}

void setup()
{
  Serial.begin(19200);
  mySerial.begin(19200);

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to Wi-Fi");
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(300);
  }
  Serial.println();
  Serial.print("Connected with IP: ");
  Serial.println(WiFi.localIP());
  Serial.println();

  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  Firebase.reconnectWiFi(true);

  //Set the size of WiFi rx/tx buffers in the case where we want to work with large data.
  firebaseData1.setBSSLBufferSize(1024, 1024);
  //Set the size of HTTP response buffers in the case where we want to work with large data.
  firebaseData1.setResponseSize(1024);
  //Set the size of WiFi rx/tx buffers in the case where we want to work with large data.
  firebaseData2.setBSSLBufferSize(1024, 1024);
  //Set the size of HTTP response buffers in the case where we want to work with large data.
  firebaseData2.setResponseSize(1024);

  if (!Firebase.beginStream(firebaseData1, path))
  {
    Serial.println("------------------------------------");
    Serial.println("Can't begin stream connection...");
    Serial.println("REASON: " + firebaseData1.errorReason());
    Serial.println("------------------------------------");
    Serial.println();
  }
  Firebase.setStreamCallback(firebaseData1, streamCallback, streamTimeoutCallback);
  plcInData();
}

void loop()
{
  doTick();
  serialEvent();
}

//1초 마다 실행되는 시간함수
void doTick() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you blinked the LED
    previousMillis = currentMillis;
    crd16Rtu();
  }  
}

void printResult(FirebaseData &data)
{

  if (data.dataType() == "int")
    Serial.println(data.intData());
  else if (data.dataType() == "float")
    Serial.println(data.floatData(), 5);
  else if (data.dataType() == "double")
    printf("%.9lf\n", data.doubleData());
  else if (data.dataType() == "boolean")
    Serial.println(data.boolData() == 1 ? "true" : "false");
  else if (data.dataType() == "string")
    Serial.println(data.stringData());
  else if (data.dataType() == "json")
  {
    Serial.println();
    FirebaseJson &json = data.jsonObject();
    //Print all object data
    Serial.println("Pretty printed JSON data:");
    String jsonStr;
    json.toString(jsonStr, true);
    Serial.println(jsonStr);
    Serial.println();
    Serial.println("Iterate JSON data:");
    Serial.println();
    size_t len = json.iteratorBegin();
    String key, value = "";
    int type = 0;
    for (size_t i = 0; i < len; i++)
    {
      json.iteratorGet(i, type, key, value);
      Serial.print(i);
      Serial.print(", ");
      Serial.print("Type: ");
      Serial.print(type == FirebaseJson::JSON_OBJECT ? "object" : "array");
      if (type == FirebaseJson::JSON_OBJECT)
      {
        Serial.print(", Key: ");
        Serial.print(key);
      }
      Serial.print(", Value: ");
      Serial.println(value);
    }
    json.iteratorEnd();
  }
  else if (data.dataType() == "array")
  {
    Serial.println();
    //get array data from FirebaseData using FirebaseJsonArray object
    FirebaseJsonArray &arr = data.jsonArray();
    //Print all array values
    Serial.println("Pretty printed Array:");
    String arrStr;
    arr.toString(arrStr, true);
    Serial.println(arrStr);
    Serial.println();
    Serial.println("Iterate array values:");
    Serial.println();
    for (size_t i = 0; i < arr.size(); i++)
    {
      Serial.print(i);
      Serial.print(", Value: ");

      FirebaseJsonData &jsonData = data.jsonData();
      //Get the result data from FirebaseJsonArray object
      arr.get(jsonData, i);
      if (jsonData.typeNum == FirebaseJson::JSON_BOOL)
        Serial.println(jsonData.boolValue ? "true" : "false");
      else if (jsonData.typeNum == FirebaseJson::JSON_INT)
        Serial.println(jsonData.intValue);
      else if (jsonData.typeNum == FirebaseJson::JSON_DOUBLE)
        printf("%.9lf\n", jsonData.doubleValue);
      else if (jsonData.typeNum == FirebaseJson::JSON_STRING ||
               jsonData.typeNum == FirebaseJson::JSON_NULL ||
               jsonData.typeNum == FirebaseJson::JSON_OBJECT ||
               jsonData.typeNum == FirebaseJson::JSON_ARRAY)
        Serial.println(jsonData.stringValue);
    }
  }
}

void printResult(StreamData &data)
{

  if (data.dataType() == "int")
    Serial.println(data.intData());
  else if (data.dataType() == "float")
    Serial.println(data.floatData(), 5);
  else if (data.dataType() == "double")
    printf("%.9lf\n", data.doubleData());
  else if (data.dataType() == "boolean")
    Serial.println(data.boolData() == 1 ? "true" : "false");
  else if (data.dataType() == "string")
    Serial.println(data.stringData());
  else if (data.dataType() == "json")
  {
    Serial.println();
    FirebaseJson *json = data.jsonObjectPtr();
    //Print all object data
    Serial.println("Pretty printed JSON data:");
    String jsonStr;
    json->toString(jsonStr, true);
    Serial.println(jsonStr);
    Serial.println();
    Serial.println("Iterate JSON data:");
    Serial.println();
    size_t len = json->iteratorBegin();
    String key, value = "";
    int type = 0;
    for (size_t i = 0; i < len; i++)
    {
      json->iteratorGet(i, type, key, value);
      Serial.print(i);
      Serial.print(", ");
      Serial.print("Type: ");
      Serial.print(type == FirebaseJson::JSON_OBJECT ? "object" : "array");
      if (type == FirebaseJson::JSON_OBJECT)
      {
        Serial.print(", Key: ");
        Serial.print(key);
      }
      Serial.print(", Value: ");
      Serial.println(value);
    }
    json->iteratorEnd();
  }
  else if (data.dataType() == "array")
  {
    Serial.println();
    //get array data from FirebaseData using FirebaseJsonArray object
    FirebaseJsonArray *arr = data.jsonArrayPtr();
    //Print all array values
    Serial.println("Pretty printed Array:");
    String arrStr;
    arr->toString(arrStr, true);
    Serial.println(arrStr);
    Serial.println();
    Serial.println("Iterate array values:");
    Serial.println();

    for (size_t i = 0; i < arr->size(); i++)
    {
      Serial.print(i);
      Serial.print(", Value: ");

      FirebaseJsonData *jsonData = data.jsonDataPtr();
      //Get the result data from FirebaseJsonArray object
      arr->get(*jsonData, i);
      if (jsonData->typeNum == FirebaseJson::JSON_BOOL)
        Serial.println(jsonData->boolValue ? "true" : "false");
      else if (jsonData->typeNum == FirebaseJson::JSON_INT)
        Serial.println(jsonData->intValue);
      else if (jsonData->typeNum == FirebaseJson::JSON_DOUBLE)
        printf("%.9lf\n", jsonData->doubleValue);
      else if (jsonData->typeNum == FirebaseJson::JSON_STRING ||
               jsonData->typeNum == FirebaseJson::JSON_NULL ||
               jsonData->typeNum == FirebaseJson::JSON_OBJECT ||
               jsonData->typeNum == FirebaseJson::JSON_ARRAY)
        Serial.println(jsonData->stringValue);
    }
  }
}

void outResult(StreamData &data)
{
  int onValue=1;
  int noPlc=0;
  if (data.dataType() == "int"){
    onValue=data.intData();
  }
  else if (data.dataType() == "string") {
    Serial.println("string data");
    if(data.stringData() != "1")
      onValue=0;
    Serial.println(data.stringData());
  }
  else if (data.dataType() == "json")
  {
    Serial.println("json data");
    FirebaseJson *json = data.jsonObjectPtr();
    String jsonStr;
    json->toString(jsonStr, true);

    FirebaseJsonData jsonObj;
    json->get(jsonObj,"on");
    onValue=jsonObj.intValue;
    //json->get(jsonObj,"no");
    //noPlc=jsonObj.intValue;
  }

  Serial.println("---data received---");
  //Serial.println(noPlc);
  Serial.println(onValue);
  Serial.println("-------------");
  outPlc=1;
  noPlc=1;
  Out[noPlc]=onValue;
  crd16Rtu();
}

// 아두이노에서 RS485 출력을 내보낸다.
void crd16Rtu() {
  String s;
  int si,sj,len;
  char str[24];

  if(outPlc == 1) {  //출력
    //str[24] =  {0x00,0x0f,0x00,0x00,0x00,0x0a,0x02,0xff,0x00,0x00,0x00};  //비트연속출력 len=9
    str[0]=0x01; str[1]=0x0f; str[2]=0x00; str[3]=0x40; str[4]=0x00;
    str[5]=0x0a; str[6]=0x02; str[7]=0xff; str[8]=0x00; str[9]=0x00; str[10]=0x00;
    len=9;
    str[7]=Out[0]+Out[1]*2+Out[2]*4+Out[3]*8;
    outPlc=0;
  }
  else {    //입력
    //str[24] =  {0x00,0x02,0x00,0x00,0x00,0x08,0x00,0x00}; // 비트 입력영역 읽기 len=6
    str[0]=0x01; str[1]=0x02; str[2]=0x00; str[3]=0x00; str[4]=0x00;
    str[5]=0x08; str[6]=0x00; str[7]=0x00; 
    len=6;
  }

  inputString = "";
  uint8_t * data = (uint8_t *) &str[0];
  si=crc16(data, len, 0x8005, 0xFFFF, 0x0000, true,  true  );
  sj=si&0xff;
  str[len]=sj;
  sj=si>>8;
  str[len+1]=sj;

  for(int i=0;i<len+2;i++)
    mySerial.print(str[i]);
}

void serialEvent() {
  if(mySerial.available() == false)
    return;
  while (mySerial.available()) {
    // get the new byte:
    char inChar = (char)mySerial.read();
    //Serial.print(inChar,HEX);
    // add it to the inputString:
    inputString += inChar;
  }
  //Serial.println("");
  if(outPlc!=1 && inputString.length() >= 6) {
    int b=1;
    sIn="";
    for(int i=1;i<=6;i++) {
        int c=inputString.charAt(3)&b;
        if(c!=0)
          c=0x01;
        In[i-1]=c;
        sIn+=c;
        //Serial.print(c,HEX);
        //Serial.print(" ");
        b*=2;
      }
    inputString="";
    if(sIn!=sInPre) {
      Serial.println(sIn);
      plcInData();
    }
    sInPre=sIn;
  }
}

void plcInData() {
  //FirebaseData firebaseData;
  String pathIn = "read";
  String jsonStr = "";
  FirebaseJson json1;
  FirebaseJsonData jsonObj;
  json1.set("led0", In[0]);
  json1.set("led1", In[1]);
  json1.set("led2", In[2]);
  json1.set("led3", In[3]);
  json1.set("led4", In[4]);
  json1.set("led5", In[5]);
  json1.set("led6", In[6]);
  json1.set("led7", In[7]);
  json1.toString(jsonStr, true);

  if (Firebase.set(firebaseData, pathIn, json1))
     Serial.println("Sucess");
  else
     Serial.println("FAILED");
}

 

 String path = "/6/OnOff";
 
 if (!Firebase.beginStream(firebaseData1, path))
  {
    Serial.println("------------------------------------");
    Serial.println("Can't begin stream connection...");
    Serial.println("REASON: " + firebaseData1.errorReason());
    Serial.println("------------------------------------");
    Serial.println();
  }

Firebase 를 확인해보면 6/OnOff 가 true/false 로 토글된다.

따라서 아두이노 소스에서도 같은 경로를 설정해서

값을 받아오도록 했다.

 

Google Home -> Firebase Realtime Database 값 set

Arduino -> Firebase Realtime Database 실시간 구독 

'개발 > Google' 카테고리의 다른 글

[Google] Dialogflow messenger  (0) 2022.11.19