로컬 처리 앱 구현

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

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

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

바인딩을 활용하여 앱이 반환하는 데이터가 플랫폼에서 예상하는 유형과 일치하는지 정적으로 확인할 수 있으므로 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");
  });

프로젝트 만들기

로컬 처리 앱을 배포하려면 코드와 모든 종속 항목의 JavaScript 번들을 빌드해야 합니다.

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

프로젝트 템플릿

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

없음

번들러 구성이 없는 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

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를 로컬 처리 앱 프로젝트가 포함될 새 디렉터리로 바꿉니다.

롤업

롤업 번들러 구성이 포함된 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 런타임 환경용 앱 번들을 로컬에서 제공합니다.

IDENTIFY 핸들러 구현

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

로컬 Home 플랫폼의 IdentifyRequest에는 LocalIdentifiedDevice 인스턴스의 스캔 데이터가 포함되어 있습니다. 기기를 검색한 스캔 구성에 따라 device 인스턴스 하나만 채워집니다.

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

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

다음 스니펫은 각각 독립형 기기 및 허브 통합을 위한 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분마다 한 번) 트리거됩니다.

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

Local Home 플랫폼의 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 핸들러는 사용자 명령어를 처리하고 Local Home SDK를 사용하여 기존 프로토콜을 통해 스마트 기기에 액세스합니다.

Local Home 플랫폼은 클라우드 처리에 EXECUTE 인텐트와 동일한 입력 페이로드를 EXECUTE 핸들러 함수에 전달합니다. 마찬가지로 EXECUTE 핸들러는 EXECUTE 인텐트 처리와 동일한 형식의 출력 데이터를 반환합니다. 응답 생성을 간소화하려면 Local Home 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 핸들러는 사용자 요청을 처리하고 Local Home SDK를 사용하여 스마트 기기의 상태를 보고합니다.

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

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

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

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