로컬 홈 디버깅

1. 시작하기 전에

스마트 홈 통합을 사용하면 Google 어시스턴트가 사용자 집에 있는 연결된 기기를 제어할 수 있습니다. 스마트 홈 작업을 빌드하려면 스마트 홈 인텐트를 처리할 수 있는 클라우드 웹훅 엔드포인트를 제공해야 합니다. 예를 들어 사용자가 "Hey Google, 조명 켜 줘"라고 말하면 어시스턴트가 클라우드 처리에 명령어를 전송하여 기기의 상태를 업데이트합니다.

Local Home SDK는 스마트 홈 인텐트를 Google Home 기기에 직접 라우팅하는 로컬 경로를 추가하여 스마트 홈 통합을 강화하므로, 신뢰성이 향상되고 사용자의 명령어 처리 지연 시간이 단축됩니다. Local Home SDK를 사용하면 타입스크립트 또는 자바스크립트로 로컬 처리 앱을 작성하고 배포할 수 있으며, 이 앱을 통해 기기를 식별하고 모든 Google Home 스마트 스피커나 Google Nest 스마트 디스플레이에서 명령을 실행할 수 있습니다. 그러면 앱이 기존의 표준 프로토콜을 사용하여 명령을 처리하며 근거리 통신망을 통해 사용자의 기존 스마트 기기와 직접 통신합니다.

72ffb320986092c.png

스마트 홈 작업 디버깅은 프로덕션 품질의 작업을 빌드하는 데 중요한 단계이지만, 유익하고 사용하기 쉬운 문제 해결 및 테스트 도구가 없으면 까다롭고 시간이 많이 걸립니다. 스마트 홈 작업 디버깅을 용이하게 하기 위해 Google Cloud Platform (GCP) 측정항목, 로깅, 스마트 홈용 테스트 모음을 사용하여 작업 문제를 파악하고 해결할 수 있습니다.

기본 요건

빌드할 프로그램

이 Codelab에서는 스마트 홈 작업의 로컬 처리를 빌드하고 어시스턴트에 연결한 다음 스마트 홈 및 Google Cloud Platform (GCP) 측정항목 및 로깅의 테스트 모음을 통해 로컬 홈 앱을 디버그합니다.

학습할 내용

  • GCP 측정항목 및 로깅을 사용하여 프로덕션 문제를 식별하고 해결하는 방법입니다.
  • 테스트 모음을 사용하여 기능 및 API 문제를 파악하는 방법
  • Local Home 앱을 개발할 때 Chrome 개발자 도구를 사용하는 방법

필요한 항목

  • 최신 버전의 Chrome
  • Google Home 앱이 설치된 iOS 또는 Android 기기
  • Google Home 스마트 스피커 또는 Google Nest 스마트 디스플레이
  • Node.js 버전 10.16 이상
  • Google 계정
  • Google Cloud 결제 계정

2. 세탁기 앱 실행

소스 코드 가져오기

다음 링크를 클릭하여 개발 머신에 이 Codelab의 샘플을 다운로드합니다.

명령줄에서 GitHub 저장소를 복제할 수도 있습니다.

$ git clone https://github.com/google-home/smarthome-debug-local.git

프로젝트 정보

시작 앱에는 스마트 홈 작업에 로컬 처리 사용 설정 Codelab과 유사한 하위 디렉터리와 클라우드 함수가 포함되어 있습니다. 하지만 여기서는 app-start 대신 app-faulty가 있습니다. 잘 작동하는 로컬 홈 앱부터 시작하겠습니다.

Firebase에 연결

스마트 홈 작업에 로컬 처리 사용 설정 Codelab에서 만든 것과 동일한 프로젝트를 사용하지만 이 Codelab에서 다운로드한 파일을 배포합니다.

app-faulty 디렉터리로 이동한 다음 스마트 홈 작업에 로컬 처리 사용 설정 Codelab에서 만든 작업 프로젝트로 Firebase CLI를 설정합니다.

$ cd app-faulty
$ firebase use <project-id>

Firebase에 배포하기

app-faulty/functions 폴더로 이동하고 npm를 사용하여 필요한 모든 종속 항목을 설치합니다.

$ cd functions
$ npm install

참고: 아래 메시지가 표시되면 무시하고 계속 진행할 수 있습니다. 이 경고는 일부 이전 종속 항목으로 인해 발생하며 여기에서 자세한 내용을 확인할 수 있습니다.

found 5 high severity vulnerabilities
  run `npm audit fix` to fix them, or `npm audit` for details

app-faulty/local/ 디렉터리로 이동하고 다음 명령어를 실행하여 타입스크립트 컴파일러를 다운로드하고 앱을 컴파일합니다.

$ cd ../local
$ npm install
$ npm run build

그러면 index.ts(타입스크립트) 소스가 컴파일되고 다음 콘텐츠가 app-faulty/public/local-home/ 디렉터리에 배치됩니다.

  • bundle.js: 로컬 앱과 종속 항목을 포함하는 컴파일된 자바스크립트 출력입니다.
  • index.html: 기기 내 테스트를 위한 앱을 제공하는 데 사용되는 로컬 호스팅 페이지입니다.

이제 종속 항목을 설치하고 프로젝트를 구성했으므로 앱을 처음으로 실행할 수 있습니다.

$ firebase deploy

콘솔에 다음과 같은 결과가 표시됩니다.

...

✔ Deploy complete!

Project Console: https://console.firebase.google.com/project/<project-id>/overview
Hosting URL: https://<projectcd -id>.web.app

이 명령어는 여러 Firebase용 Cloud Functions와 함께 웹 앱을 배포합니다.

HomeGraph 업데이트

브라우저(https://<project-id>.web.app)에서 호스팅 URL을 열어 웹 앱을 확인합니다. 웹 UI에서 Refresh(새로고침) ae8d3b25777a5e30.png 버튼을 클릭하여 Request Sync(동기화 요청)를 통해 결함이 있는 세탁기 앱의 최신 기기 메타데이터로 HomeGraph를 업데이트합니다.

fa3c47f293cfe0b7.png

Google Home 앱을 열고 '결함 있는 세탁기'라는 새로운 이름으로 세탁기 기기가 표시되는지 확인합니다. Nest 기기가 있는 방에 기기를 할당해야 합니다.

2a082ee11d47ad1a.png

3. 스마트 세탁기 시작

스마트 홈 작업에 로컬 처리 사용 설정 Codelab을 실행했다면 가상 스마트 세탁기가 이미 시작되었을 것입니다. 가상 기기가 중지된 경우 가상 기기를 다시 시작해야 합니다.

기기 시작

virtual-device/ 디렉터리로 이동한 다음 기기 스크립트를 실행하여 구성 매개변수를 인수로 전달합니다.

$ cd ../../virtual-device
$ npm install
$ npm start -- \
  --deviceId=deviceid123 --projectId=<project-id> \
  --discoveryPortOut=3311 --discoveryPacket=HelloLocalHomeSDK

기기 스크립트가 예측한 매개변수를 통해 실행되는지 확인합니다.

(...): UDP Server listening on 3311
(...): Device listening on port 3388
(...): Report State successful

4. Local Home 앱 테스트

Google Home 기기에 음성 명령을 통해 기기에 다음과 같은 명령을 전송합니다.

"Hey Google, 세탁기 켜 줘."

"Hey Google, 세탁기 시작해 줘."

"Hey Google, 로컬 강제 종료해 줘."

"Hey Google, 세탁기 중지해 줘."

세탁기를 제어하려고 하면 '강제 로컬 실행' 후 Google 어시스턴트가 '죄송합니다. 지금은 결함이 있는 세탁기를 사용하실 수 없습니다'라고 응답합니다.

즉, 로컬 경로를 통해서는 기기에 연결할 수 없습니다. 로컬 경로를 통해 기기에 연결할 수 없는 경우에는 클라우드 경로 사용을 다시 시작하기 때문에 'Hey Google, force local'이 실행되기 전에 작동했습니다. 하지만 '로컬 강제 적용'을 선택하면 클라우드 경로로 대체하는 옵션이 사용 중지됩니다.

문제가 무엇인지 파악하기 위해 Google Cloud Platform (GCP) 측정항목Logging, Chrome 개발자 도구를 사용해 보겠습니다.

5. Local Home 앱 디버그

다음 섹션에서는 Google에서 제공하는 도구를 사용하여 로컬 경로를 통해 기기에 연결할 수 없는 이유를 알아봅니다. Chrome 개발자 도구를 사용하여 Google Home 기기에 연결하고, 콘솔 로그를 보고, Local Home 앱을 디버그할 수 있습니다. 또한 Cloud Logging으로 맞춤 로그를 전송하여 사용자가 Local Home 앱에서 발견하는 주요 오류를 파악할 수 있습니다.

Chrome 개발자 도구 연결

디버거를 로컬 처리 앱에 연결하려면 다음 단계를 따르세요.

  1. Actions 콘솔 프로젝트에 액세스할 수 있는 권한이 있는 사용자에게 Google Home 기기를 연결했는지 확인합니다.
  2. Google Home 기기를 재부팅합니다. 그러면 Google Home HTML URL 및 개발자가 Actions 콘솔에 입력한 스캔 구성을 가져올 수 있습니다.
  3. 개발 머신에서 Chrome을 시작합니다.
  4. 새 Chrome 탭을 열고 주소 입력란에 chrome://inspect를 입력하여 검사기를 실행합니다.

페이지에 기기 목록이 표시되고 앱 URL이 Google Home 기기 이름 아래에 표시되어야 합니다.

567f97789a7d8846.png

검사기 실행

앱 URL에서 검사를 클릭하여 Chrome 개발자 도구를 실행합니다. 콘솔 탭을 선택하고 TypeScript 앱에서 출력된 IDENTIFY 인텐트의 콘텐츠를 볼 수 있는지 확인합니다.

774c460c59f9f84a.png

이 출력은 IDENTIFY 핸들러가 성공적으로 트리거되었지만 IdentifyResponse에서 반환된 verificationId가 HomeGraph의 기기와 일치하지 않음을 의미합니다. 커스텀 로그를 추가하여 이유를 알아보겠습니다.

커스텀 로그 추가

Local Home SDK에서 출력하는 DEVICE_VERIFICATION_FAILED 오류가 있지만 근본 원인을 찾는 데 별 도움이 되지 않습니다. 스캔 데이터를 올바르게 읽고 처리할 수 있도록 몇 가지 커스텀 로그를 추가해 보겠습니다. 오류가 있는 프로미스를 거부하면 오류 메시지도 실제로 Cloud Logging으로 전송됩니다.

local/index.ts

identifyHandler(request: IntentFlow.IdentifyRequest):
    Promise<IntentFlow.IdentifyResponse> {
  console.log("IDENTIFY intent: " + JSON.stringify(request, null, 2));

  const scanData = request.inputs[0].payload.device.udpScanData;
  if (!scanData) {
    const err = new IntentFlow.HandlerError(request.requestId,
        'invalid_request', 'Invalid scan data');
    return Promise.reject(err);
  }

  // In this codelab, the scan data contains only local device id.
  // Is there something wrong here?
  const localDeviceId = Buffer.from(scanData.data);
  console.log(`IDENTIFY handler: received local device id
      ${localDeviceId}`);

  // Add custom logs
  if (!localDeviceId.toString().match(/^deviceid[0-9]{3}$/gi)) {
    const err = new IntentFlow.HandlerError(request.requestId,
        'invalid_device', 'Invalid device id from scan data ' +
        localDeviceId);
    return Promise.reject(err);
  }

  const response: IntentFlow.IdentifyResponse = {
    intent: Intents.IDENTIFY,
    requestId: request.requestId,
    payload: {
      device: {
        id: 'washer',
        verificationId: localDeviceId.toString(),
      }
    }
  };
  console.log("IDENTIFY response: " + JSON.stringify(response, null, 2));

  return Promise.resolve(response);
}

또한 Google에서 올바른 버전을 사용 중인지 확인할 수 있도록 로컬 홈 앱 버전을 변경합니다.

local/index.ts

const localHomeSdk = new App('1.0.1');

커스텀 로그를 추가한 후 앱을 다시 컴파일하고 Firebase에 다시 배포해야 합니다.

$ cd ../app-faulty/local
$ npm run build
$ firebase deploy --only hosting

이제 업데이트된 로컬 홈 앱을 로드할 수 있도록 Google Home 기기를 재부팅합니다. Chrome 개발자 도구의 콘솔 로그를 확인하여 Google Home 기기가 예상 버전을 사용 중인지 확인할 수 있습니다.

ecc56508ebcf9ab.png

Cloud Logging 액세스

Cloud Logging을 사용하여 오류를 찾는 방법을 살펴보겠습니다. 프로젝트의 Cloud Logging에 액세스하려면 다음 안내를 따르세요.

  1. Cloud Platform 콘솔에서 프로젝트 페이지로 이동합니다.
  2. 스마트 홈 프로젝트를 선택합니다.
  3. 작업에서 Logging > 로그 탐색기를 선택합니다.

로깅 데이터에 대한 액세스는 작업 프로젝트 사용자의 Identity and Access Management (IAM)를 통해 관리됩니다. 로깅 데이터의 역할 및 권한에 대한 자세한 내용은 Cloud Logging 액세스 제어를 참조하세요.

고급 필터 사용하기

로컬 기기를 식별하지 못해 로컬 경로가 작동하지 않으므로 IDENTIFY 인텐트에서 오류가 발생한다는 것을 알고 있습니다. 하지만 문제가 무엇인지 정확히 알 수 있도록 먼저 IDENTIFY 핸들러에서 발생하는 오류를 필터링해 보겠습니다.

쿼리 미리보기 상자를 펼치면 쿼리 빌더 상자로 바뀝니다. 쿼리 빌더 상자에 jsonPayload.intent="IDENTIFY"를 입력하고 쿼리 실행 버튼을 클릭합니다.

4c0b9d2828ee2447.png

따라서 IDENTIFY 핸들러에서 발생하는 모든 오류 로그를 가져옵니다. 다음으로, 마지막 오류를 펼칩니다. IDENTIFY 핸들러에서 프로미스를 거부할 때 방금 설정한 errorCodedebugString를 확인할 수 있습니다.

71f2f156c6887496.png

debugString에서 로컬 기기 ID가 예상된 형식이 아님을 알 수 있습니다. Local Home 앱은 로컬 기기 ID를 deviceid로 시작하고 뒤에 3자리 숫자가 오는 문자열로 가져올 것으로 예상하지만, 여기서 로컬 기기 ID는 16진수 문자열입니다.

오류 수정

스캔 데이터에서 로컬 기기 ID를 파싱하는 소스 코드로 돌아가서, 문자열을 바이트로 변환할 때 인코딩을 제공하지 않았음을 알 수 있습니다. 스캔 데이터는 16진수 문자열로 수신되므로 Buffer.from()를 호출할 때 hex를 문자 인코딩으로 전달합니다.

local/index.ts

identifyHandler(request: IntentFlow.IdentifyRequest):
    Promise<IntentFlow.IdentifyResponse> {
  console.log("IDENTIFY intent: " + JSON.stringify(request, null, 2));

  const scanData = request.inputs[0].payload.device.udpScanData;
  if (!scanData) {
    const err = new IntentFlow.HandlerError(request.requestId,
        'invalid_request', 'Invalid scan data');
    return Promise.reject(err);
  }

  // In this codelab, the scan data contains only local device id.
  const localDeviceId = Buffer.from(scanData.data, 'hex');
  console.log(`IDENTIFY handler: received local device id
      ${localDeviceId}`);

  if (!localDeviceId.toString().match(/^deviceid[0-9]{3}$/gi)) {
    const err = new IntentFlow.HandlerError(request.requestId,
      'invalid_device', 'Invalid device id from scan data ' +
      localDeviceId);
    return Promise.reject(err);
  }

  const response: IntentFlow.IdentifyResponse = {
    intent: Intents.IDENTIFY,
    requestId: request.requestId,
    payload: {
      device: {
        id: 'washer',
        verificationId: localDeviceId.toString(),
      }
    }
  };
  console.log("IDENTIFY response: " + JSON.stringify(response, null, 2));

  return Promise.resolve(response);
}

또한 Google에서 올바른 버전을 사용 중인지 확인할 수 있도록 로컬 홈 앱 버전을 변경합니다.

local/index.ts

const localHomeSdk = new App('1.0.2');

오류를 해결한 후 앱을 컴파일하고 Firebase에 다시 배포합니다. app-faulty/local에서 다음을 실행합니다.

$ npm run build
$ firebase deploy --only hosting

수정사항 테스트

배포 후에는 업데이트된 로컬 홈 앱을 로드할 수 있도록 Google Home 기기를 재부팅합니다. 로컬 홈 앱 버전이 1.0.2인지 확인하고 이번에는 Chrome 개발자 도구 콘솔에 오류가 표시되지 않습니다.

c8456f7b5f77f894.png

이제 기기에 명령어를 다시 전송해 볼 수 있습니다.

"Hey Google, 로컬 강제 실행"

"Hey Google, 세탁기 중지해 줘."

"Hey Google, 세탁기 켜 줘."

...

"Hey Google, 기본값 강제 설정해 줘."

6. 스마트 홈 테스트 모음 실행

Google Home 앱의 터치 컨트롤이나 음성 명령을 통해 기기를 인증한 후 자동화된 스마트 홈용 테스트 모음을 사용하여 작업과 관련된 기기 유형 및 특성을 기반으로 사용 사례를 검증할 수 있습니다. 테스트 모음은 일련의 테스트를 실행하여 작업의 문제를 감지하고 실패한 테스트 사례에 관한 정보 메시지를 표시하여 이벤트 로그를 자세히 살펴보기 전에 디버깅 속도를 높입니다.

스마트 홈 테스트 모음 실행

테스트 모음으로 스마트 홈 작업을 테스트하려면 다음 안내를 따르세요.

  1. 웹브라우저에서 스마트 홈 테스트 모음을 엽니다.
  2. 오른쪽 상단의 버튼을 사용하여 Google에 로그인합니다. 이렇게 하면 테스트 모음에서 Google 어시스턴트에 명령어를 직접 전송할 수 있습니다.
  3. 프로젝트 ID 필드에 스마트 홈 작업의 프로젝트 ID를 입력합니다. 그런 다음 다음을 클릭하여 계속 진행합니다.
  4. 테스트 설정 단계의 기기 및 트레이 섹션에 결함 있는 세탁기가 표시되어야 합니다.
  5. 샘플 세탁기 앱에는 세탁기를 추가/삭제하거나 이름을 변경할 UI가 없으므로 Test Request Sync 옵션을 사용 중지합니다. 프로덕션 시스템에서는 사용자가 기기를 추가 / 삭제 / 이름을 변경할 때마다 동기화 요청을 트리거해야 합니다.
  6. 로컬 및 클라우드 경로를 모두 테스트할 것이므로 Local Home SDK 옵션을 사용 설정된 상태로 둡니다.
  7. 다음을 클릭하여 테스트 실행을 시작합니다.

67433d9190fa770e.png

테스트가 완료되면 클라우드 경로의 테스트 일시중지/재개 테스트가 통과되는 동안 로컬 경로의 테스트 일시중지/재개는 실패하는 것을 확인할 수 있습니다.

d1ebd5cfae2a2a47.png

오류 메시지 분석

실패한 테스트 사례의 오류 메시지를 자세히 살펴보세요. 테스트에서 예상되는 상태와 실제 상태를 알려줍니다. 이 경우 '와셔 일시중지'의 경우 예상되는 상태는 isPaused: true이지만 실제 상태에서는 isPaused: false가 있습니다. 마찬가지로 '와셔 일시중지'의 경우 예상 상태는 isPaused: true이지만 실제 상태에서는 isPaused: false가 있습니다.

6bfd3acef9c16b84.png

오류 메시지에서 로컬 경로에서 isPaused 상태를 반대로 설정하는 것처럼 보입니다.

오류 확인 및 수정

Local Home 앱이 기기로 실행 명령어를 전송하는 소스 코드를 찾아보겠습니다. getDataCommand()는 기기로 전송된 실행 명령어에서 payload를 설정하기 위해 executeHandler()에서 호출하는 함수입니다.

local/index.ts

getDataForCommand(command: string, params: IWasherParams): unknown {
    switch (command) {
        case 'action.devices.commands.OnOff':
            return {
                on: params.on ? true : false
            };
        case 'action.devices.commands.StartStop':
            return {
                isRunning: params.start ? true : false
            };
        case 'action.devices.commands.PauseUnpause':
            return {
                // Is there something wrong here?
                isPaused: params.pause ? false : true
            };
        default:
            console.error('Unknown command', command);
            return {};
    }
}

실제로 isPause를 반대 상태로 설정하고 있습니다. params.pausetrue이면 true로, 그렇지 않으면 false로 설정해야 합니다. 이 문제를 해결해 보겠습니다.

local/index.ts

getDataForCommand(command: string, params: IWasherParams): unknown {
    switch (command) {
        case 'action.devices.commands.OnOff':
            return {
                on: params.on ? true : false
            };
        case 'action.devices.commands.StartStop':
            return {
                isRunning: params.start ? true : false
            };
        case 'action.devices.commands.PauseUnpause':
            return {
                isPaused: params.pause ? true : false
            };
        default:
            console.error('Unknown command', command);
            return {};
    }
}

Google에서 올바른 버전을 사용 중인지 확인할 수 있도록 로컬 홈 앱 버전을 변경합니다.

local/index.ts

const localHomeSdk = new App('1.0.3');

앱을 다시 컴파일하고 Firebase에 다시 배포해야 합니다. app-faulty/local에서 다음을 실행합니다.

$ npm run build
$ firebase deploy --only hosting

이제 업데이트된 로컬 홈 앱을 로드할 수 있도록 Google Home 기기를 재부팅합니다. 로컬 홈 앱 버전이 1.0.3인지 확인하세요.

수정사항 테스트

이제 동일한 구성으로 스마트 홈용 테스트 모음을 다시 실행하면 모든 테스트 사례가 통과된 것을 확인할 수 있습니다.

b7fc8c5d3c727d8d.png

7. 축하합니다

764dbc83b95782a.png

수고하셨습니다 스마트 홈 및 클라우드 로깅용 테스트 모음을 통해 Local Home 앱의 문제를 해결하는 방법을 배웠습니다.

자세히 알아보기

다음과 같은 작업을 해볼 수 있습니다.

사용자에게 작업을 게시하기 위한 인증 프로세스를 비롯하여 검토를 위해 작업을 테스트 및 제출하는 방법도 자세히 알아보세요.