스마트 홈 작업을 개발하는 방법을 알아볼 수 있는 새로운 공간인 Google Home 개발자 센터에 오신 것을 환영합니다. 참고: Actions 콘솔에서 작업을 계속 만들 수 있습니다.

로컬 처리 앱 구현

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

로컬 처리를 지원하려면 다음과 같은 스마트 홈 인텐트를 처리하는 앱을 빌드해야 합니다.

  • IDENTIFY: 로컬에서 제어 가능한 스마트 기기의 검색을 지원합니다. 인텐트 핸들러는 검색 중에 스마트 기기가 반환하는 데이터를 추출하여 Google에 대한 응답으로 전송합니다.
  • EXECUTE: 명령어 실행을 지원합니다.
  • QUERY: 기기 상태 쿼리를 지원합니다.
  • REACHABLE_DEVICES: (선택사항) 허브 또는 브리지 기기 뒤에 로컬로 제어 가능한 최종 기기의 검색을 지원합니다.

이 앱은 사용자의 Google Home 또는 Google Nest 기기에서 실행되며 스마트 기기를 어시스턴트에 연결합니다. TypeScript (권장) 또는 자바스크립트를 사용하여 앱을 만들 수 있습니다.

결합을 활용하여 앱에서 반환하는 데이터가 플랫폼에서 예상하는 유형과 일치하는지 확인할 수 있으므로 TypeScript를 사용하는 것이 좋습니다.

API에 관한 자세한 내용은 Local Home SDK API 참조를 확인하세요.

다음 스니펫은 로컬 처리 앱을 초기화하고 핸들러를 연결하는 방법을 보여줍니다.

독립형
import App = smarthome.App;
const localHomeApp: App = new App("1.0.0");
localHomeApp
  .onIdentify(identifyHandler)
  .onExecute(executeHandler)
  .listen()
  .then(() => {
    console.log("Ready");
  });
허브
import App = smarthome.App;
const localHomeApp: App = new App("1.0.0");
localHomeApp
  .onIdentify(identifyHandler)
  .onReachableDevices(reachableDevicesHandler)
  .onExecute(executeHandler)
  .listen()
  .then(() => {
    console.log("Ready");
  });

프로젝트 만들기

로컬 처리 앱을 배포하려면 코드 및 모든 종속 항목을 위한 자바스크립트 번들을 빌드해야 합니다.

로컬 처리 앱 프로젝트 이니셜라이저를 사용하여 원하는 번들러 구성으로 적절한 프로젝트 구조를 부트스트랩합니다.

프로젝트 템플릿

번들러 구성을 선택하려면 다음 예시와 같이 npm init 명령어를 실행합니다.

없음

Bundler 구성이 없는 TypeScript:

npm init @google/local-home-app project-directory/ --bundler none

프로젝트 구조:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
└── serve.js

project-directory를 로컬 처리 앱 프로젝트를 포함할 새 디렉터리로 바꿉니다.

웹팩

webpack 번들러 구성이 있는 TypeScript:

npm init @google/local-home-app project-directory/ --bundler webpack

프로젝트 구조:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
├── webpack.config.web.js
├── webpack.config.node.js
└── serve.js

project-directory를 로컬 처리 앱 프로젝트를 포함할 새 디렉터리로 바꿉니다.

롤업

Rollup 번들러 구성이 있는 TypeScript:

npm init @google/local-home-app project-directory/ --bundler rollup

프로젝트 구조:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
├── rollup.config.js
└── serve.js

project-directory를 로컬 처리 앱 프로젝트를 포함할 새 디렉터리로 바꿉니다.

Parcel

Parcel 번들러 구성이 있는 TypeScript:

npm init @google/local-home-app project-directory/ --bundler parcel

프로젝트 구조:

project-directory/
├── node_modules/
├── package.json
├── .gitignore
├── index.ts
├── test.ts
├── tsconfig.json
├── tslint.json
└── serve.js

project-directory를 로컬 처리 앱 프로젝트를 포함할 새 디렉터리로 바꿉니다.

일반적인 프로젝트 수준 작업 수행

생성된 프로젝트는 다음 npm 스크립트를 지원합니다.

번들
cd project-directory/
npm run build

이 스크립트는 TypeScript 소스를 컴파일하고 앱을 dist/web 하위 디렉터리에 있는 Chrome 런타임 환경과 dist/node 하위 디렉터리에 있는 Node.js 런타임 환경에 대한 종속 항목과 함께 번들로 묶습니다.

인증
cd project-directory/
npm run lint
npm run compile
npm test

이 스크립트는 TypeScript 코드의 구문을 확인하고 dist/ 하위 디렉터리에 출력을 생성하지 않고 컴파일한 후 test.ts에서 자동 테스트를 실행합니다.

서빙
cd project-directory/
npm run start

개발 중에 이 스크립트는 Chrome 및 Node.js 런타임 환경을 위한 App Bundle을 로컬에서 제공합니다.

IDENTIFY 핸들러 구현

IDENTIFY 핸들러는 Google Home 또는 Google Nest 기기가 재부팅되고 확인되지 않은 로컬 기기 (허브에 연결된 최종 기기 포함)가 표시될 때 트리거됩니다. 로컬 홈 플랫폼은 앞서 지정한 스캔 구성 정보를 사용하여 로컬 기기를 스캔하고 스캔 결과로 IDENTIFY 핸들러를 호출합니다.

로컬 홈 플랫폼의 IdentifyRequest에는 LocalIdentifiedDevice 인스턴스의 스캔 데이터가 포함됩니다. 기기를 발견한 검사 구성을 기반으로 하나의 device 인스턴스만 채워집니다.

검색 결과가 기기와 일치하면 IDENTIFY 핸들러가 스마트 홈 메타데이터 (예: 유형, 특성, 보고 상태)가 있는 device 객체가 포함된 IdentifyResponsePayload 객체를 반환해야 합니다.

Google은 IDENTIFY 응답의 verificationIdSYNC 응답에서 반환된 otherDeviceIds 값 중 하나와 일치하면 기기 연결을 설정합니다.

다음 스니펫은 각각 독립형 기기 및 허브 통합을 위한 IDENTIFY 핸들러를 만드는 방법을 보여줍니다.

독립형
const identifyHandler = (request: IntentFlow.IdentifyRequest):
  IntentFlow.IdentifyResponse => {

    // Obtain scan data from protocol defined in your scan config
    const device = request.inputs[0].payload.device;
    if (device.udpScanData === undefined) {
      throw Error("Missing discovery response");
    }
    const scanData = device.udpScanData.data;

    // Decode scan data to obtain metadata about local device
    const verificationId = "local-device-id";

    // Return a response
    const response: IntentFlow.IdentifyResponse = {
      intent: Intents.IDENTIFY,
      requestId: request.requestId,
      payload: {
        device: {
          id: device.id || "",
          verificationId, // Must match otherDeviceIds in SYNC response
        },
      },
    };
    return response;
  };
허브
const identifyHandler = (request: IntentFlow.IdentifyRequest):
  IntentFlow.IdentifyResponse => {

    // Obtain scan data from protocol defined in your scan config
    const device = request.inputs[0].payload.device;
    if (device.udpScanData === undefined) {
      throw Error("Missing discovery response");
    }
    const scanData = device.udpScanData.data;

    // Decode scan data to obtain metadata about local device
    const proxyDeviceId = "local-hub-id";

    // Return a response
    const response: IntentFlow.IdentifyResponse = {
      intent: Intents.IDENTIFY,
      requestId: request.requestId,
      payload: {
        device: {
          id: proxyDeviceId,
          isProxy: true,     // Device can control other local devices
          isLocalOnly: true, // Device not present in `SYNC` response
        },
      },
    };
    return response;
  };

허브 뒤에 있는 기기 식별

Google은 허브 기기를 식별하면 허브를 연결된 최종 기기의 연결 장치로 취급하여 최종 기기를 확인합니다.

Google에서 허브 기기가 있는지 확인할 수 있도록 IDENTIFY 핸들러에 관한 다음 안내를 따르세요.

  • SYNC 응답이 허브에 연결된 로컬 최종 기기의 ID를 보고하는 경우 IdentifyResponsePayload에서 isProxytrue로 설정합니다.
  • SYNC 응답이 허브 기기를 보고하지 않으면 IdentifyResponsePayload에서 isLocalOnlytrue로 설정합니다.
  • device.id 필드에는 허브 기기 자체의 로컬 기기 ID가 포함됩니다.

REACHABLE_DEVICES 핸들러 구현 (허브 통합에만 해당)

REACHABLE_DEVICES 인텐트는 Google에서 전송하여 제어할 수 있는 최종 기기를 확인합니다. 이 인텐트는 허브가 온라인 상태인 것으로 감지되는 한 Google에서 검색 스캔을 실행할 때마다 (약 1분마다) 트리거됩니다.

핸들러가 로컬 프록시 (허브) 기기에 연결할 수 있는 추가 기기 ID를 수집해야 한다는 점을 제외하고 REACHABLE_DEVICES 핸들러를 IDENTIFY 핸들러와 유사하게 구현합니다. device.verificationId 필드에는 허브에 연결된 최종 기기의 로컬 기기 ID가 포함됩니다.

로컬 홈 플랫폼의 ReachableDevicesRequest에는 LocalIdentifiedDevice 인스턴스가 포함됩니다. 이 인스턴스를 통해 검사 결과에서 데이터 및 프록시 기기 ID를 가져올 수 있습니다.

REACHABLE_DEVICES 핸들러는 허브가 제어하는 최종 기기를 나타내는 verificationId 값의 배열을 포함하는 devices 객체가 포함된 ReachableDevicesPayload 객체를 반환해야 합니다. verificationId 값은 SYNC 응답의 otherDeviceIds 중 하나와 일치해야 합니다.

다음 스니펫은 REACHABLE_DEVICES 핸들러를 만드는 방법을 보여줍니다.

허브
const reachableDevicesHandler = (request: IntentFlow.ReachableDevicesRequest):
  IntentFlow.ReachableDevicesResponse => {

    // Reference to the local proxy device
    const proxyDeviceId = request.inputs[0].payload.device.id;

    // Gather additional device ids reachable by local proxy device
    // ...

    const reachableDevices = [
      // Each verificationId must match one of the otherDeviceIds
      // in the SYNC response
      { verificationId: "local-device-id-1" },
      { verificationId: "local-device-id-2" },
    ];

    // Return a response
    const response: IntentFlow.ReachableDevicesResponse = {
      intent: Intents.REACHABLE_DEVICES,
      requestId: request.requestId,
      payload: {
        devices: reachableDevices,
      },
    };
    return response;
  };

EXECUTE 핸들러 구현

앱의 EXECUTE 핸들러는 사용자 명령어를 처리하고 로컬 홈 SDK를 사용하여 기존 프로토콜을 통해 스마트 기기에 액세스합니다.

로컬 홈 플랫폼은 EXECUTE 인텐트와 동일한 입력 페이로드를 EXECUTE 핸들러 함수에 클라우드 처리에 전달합니다. 마찬가지로 EXECUTE 핸들러는 EXECUTE 인텐트를 처리할 때와 동일한 형식으로 출력 데이터를 반환합니다. 응답 생성을 간소화하려면 로컬 홈 SDK에서 제공하는 Execute.Response.Builder 클래스를 사용하면 됩니다.

앱이 기기의 IP 주소에 직접 액세스할 수 없습니다. 대신 CommandRequest 인터페이스를 사용하여 UDP, TCP 또는 HTTP 프로토콜 중 하나를 기반으로 명령어를 만듭니다. 그런 다음 deviceManager.send() 함수를 호출하여 명령어를 보냅니다.

명령어를 기기에 타겟팅할 때는 SYNC 응답의 기기 ID와 customData 필드의 매개변수 (포함된 경우)를 사용하여 기기와 통신합니다.

다음 코드 스니펫은 EXECUTE 핸들러를 만드는 방법을 보여줍니다.

독립형/허브
const executeHandler = (request: IntentFlow.ExecuteRequest):
  Promise<IntentFlow.ExecuteResponse> => {

    // Extract command(s) and device target(s) from request
    const command = request.inputs[0].payload.commands[0];
    const execution = command.execution[0];

    const response = new Execute.Response.Builder()
      .setRequestId(request.requestId);

    const result = command.devices.map((device) => {
      // Target id of the device provided in the SYNC response
      const deviceId = device.id;
      // Metadata for the device provided in the SYNC response
      // Use customData to provide additional required execution parameters
      const customData: any = device.customData;

      // Convert execution command into payload for local device
      let devicePayload: string;
      // ...

      // Construct a local device command over TCP
      const deviceCommand = new DataFlow.TcpRequestData();
      deviceCommand.requestId = request.requestId;
      deviceCommand.deviceId = deviceId;
      deviceCommand.data = devicePayload;
      deviceCommand.port = customData.port;
      deviceCommand.operation = Constants.TcpOperation.WRITE;

      // Send command to the local device
      return localHomeApp.getDeviceManager()
        .send(deviceCommand)
        .then((result) => {
          response.setSuccessState(result.deviceId, state);
        })
        .catch((err: IntentFlow.HandlerError) => {
          err.errorCode = err.errorCode || IntentFlow.ErrorCode.INVALID_REQUEST;
          response.setErrorState(device.id, err.errorCode);
        });
    });

    // Respond once all commands complete
    return Promise.all(result)
      .then(() => response.build());
  };

QUERY 핸들러 구현

앱의 QUERY 핸들러가 사용자 요청을 처리하고 로컬 홈 SDK를 사용하여 스마트 기기의 상태를 보고합니다.

로컬 홈 플랫폼은 QUERY 인텐트와 동일한 요청 페이로드를 'QUERY' 핸들러 함수에 클라우드 처리에 전달합니다. 마찬가지로 QUERY 핸들러는 QUERY 인텐트를 처리할 때와 동일한 형식으로 데이터를 반환합니다.

허브 뒤에 있는 기기로 명령어 전송

허브에 있는 최종 기기를 제어하려면 허브가 명령어를 타겟팅할 기기를 식별할 수 있도록 허브에 전송되는 프로토콜별 명령어 페이로드에 추가 정보를 제공해야 할 수 있습니다. 경우에 따라 device.id 값에서 직접 추론할 수도 있지만, 그렇지 않은 경우 이 추가 데이터를 customData 필드의 일부로 포함해야 합니다.

TypeScript를 사용하여 앱을 만든 경우 앱을 자바스크립트로 컴파일해야 합니다. 원하는 모듈 시스템을 사용하여 코드를 작성할 수 있습니다. Chrome 브라우저에서 타겟이 지원되는지 확인하세요.