로컬 홈 디버깅

1. 시작하기 전에

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

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

72ffb320986092c.png

스마트 홈 작업 디버깅은 프로덕션 품질로 작업을 빌드하는 중요한 단계이지만 유익하고 사용하기 쉬운 문제 해결 및 테스트 도구 없이는 어렵고 시간이 많이 소요됩니다. 스마트 홈 작업 디버깅을 용이하게 하기 위해 Google Cloud Platform (GCP) 측정항목, Logging, 스마트 홈용 테스트 모음이 작업 관련 문제를 식별하고 해결하는 데 도움이 됩니다.

기본 요건

빌드할 항목

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

학습할 내용

  • GCP 측정항목 및 로깅을 사용하여 프로덕션 문제를 식별하고 해결하는 방법입니다.
  • 테스트 모음을 사용하여 기능 및 API 문제를 식별하는 방법
  • 로컬 홈 앱을 개발할 때 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과 유사한 하위 디렉터리 및 Cloud 함수가 포함되어 있습니다. 하지만 여기서는 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에서 새로고침 ae8d3b25777a5e30.png 버튼을 클릭하여 동기화 요청을 통해 결함이 있는 세탁기 앱의 최신 기기 메타데이터로 HomeGraph를 업데이트합니다.

fa3c47f293cfe0b7.png

Google Home 앱을 열고 세탁기 기기가 'Faulty Washer'라는 새로운 이름으로 표시되는지 확인합니다. 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, 강제 로컬' 실행 전에는 작동했습니다. 로컬 경로를 통해 기기에 연결할 수 없는 경우 다시 클라우드 경로를 사용하는 것으로 대체하기 때문입니다. 하지만 '로컬 로컬 강제 실행' 후에는 클라우드 경로로 대체하는 옵션이 사용 중지됩니다.

어떤 문제가 있는지 알아보기 위해 Google Cloud Platform (GCP) 측정항목, 로깅, Chrome 개발자 도구를 활용해 보겠습니다.

5. Local Home 앱 디버그

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

Chrome 개발자 도구 연결

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

  1. Google Home 기기를 Actions 콘솔 프로젝트에 액세스할 권한이 있는 사용자에게 연결했는지 확인합니다.
  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);
}

또한 올바른 버전을 사용 중인지 확인할 수 있도록 로컬 홈 앱 버전을 변경하세요.

local/index.ts

const localHomeSdk = new App('1.0.1');

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

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

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

ecc56508ebcf9ab.png

Cloud Logging 액세스

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

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

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

고급 필터 사용

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

쿼리 표시 전환 버튼을 클릭하면 쿼리 빌더 상자로 전환됩니다. 쿼리 빌더 상자에 jsonPayload.intent="IDENTIFY"를 입력하고 쿼리 실행 버튼을 클릭합니다.

4c0b9d2828ee2447.png

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

71f2f156c6887496.png

debugString에서 로컬 기기 ID가 예상된 형식이 아님을 알 수 있습니다. Local Home 앱은 deviceid로 시작하고 뒤에 3자리 숫자가 오는 문자열로 로컬 기기 ID를 가져올 수 있지만, 여기서 로컬 기기 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);
}

또한 올바른 버전을 사용 중인지 확인할 수 있도록 로컬 홈 앱 버전을 변경하세요.

local/index.ts

const localHomeSdk = new App('1.0.2');

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

$ npm run build
$ firebase deploy --only hosting

수정사항 테스트

배포 후 업데이트된 로컬 Home 앱을 로드할 수 있도록 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가 없으므로 테스트 요청 동기화 옵션을 사용 중지합니다. 프로덕션 시스템에서는 사용자가 기기를 추가/삭제하거나 기기 이름을 변경할 때마다 동기화 요청을 트리거해야 합니다.
  6. 로컬 경로와 클라우드 경로를 모두 테스트할 것이므로 Local Home SDK 옵션을 사용 설정된 상태로 둡니다.
  7. 다음: 테스트 환경을 클릭하여 테스트 실행을 시작합니다.

67433d9190fa770e.png

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

d1ebd5cfae2a2a47.png

오류 메시지 분석

실패한 테스트 사례의 오류 메시지를 자세히 살펴봅니다. 테스트에서 예상되는 상태와 실제 상태를 알려줍니다. 이 경우 '세탁기 일시중지'의 경우 예상 상태는 isPaused: true이지만 실제 상태에서는 isPaused: false입니다. 마찬가지로 'Pause the Washer'의 경우 예상 상태는 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 {};
    }
}

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

local/index.ts

const localHomeSdk = new App('1.0.3');

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

$ npm run build
$ firebase deploy --only hosting

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

수정사항 테스트

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

b7fc8c5d3c727d8d.png

7. 축하합니다

764dbc83b95782a.png

축하합니다. 스마트 홈 및 기능 테스트 모음을 통해 로컬 홈 앱 문제를 해결하는 방법을 학습했습니다. Cloud Logging입니다.

자세히 알아보기

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

사용자에게 작업을 게시하는 인증 프로세스 등 검토를 위해 작업을 테스트하고 제출하는 방법도 자세히 알아볼 수 있습니다.