ใช้แอป Fulfillment ในพื้นที่

หากต้องการรองรับการจำหน่ายในท้องถิ่น คุณต้องสร้างแอปที่จะจัดการกับจุดประสงค์ของสมาร์ทโฮมต่อไปนี้

  • IDENTIFY: รองรับการค้นพบอุปกรณ์อัจฉริยะที่ควบคุมในเครื่องได้ ตัวแฮนเดิล Intent จะแยกข้อมูลที่อุปกรณ์อัจฉริยะแสดงระหว่างการค้นหาและส่งข้อมูลนี้เพื่อตอบกลับไปยัง Google
  • EXECUTE: รองรับการดำเนินการตามคำสั่ง
  • QUERY: รองรับการค้นหาสถานะอุปกรณ์
  • REACHABLE_DEVICES: (ไม่บังคับ) รองรับการค้นหาอุปกรณ์ปลายทางที่ควบคุมในเครื่องได้หลังอุปกรณ์ฮับ (หรือบริดจ์)

แอปนี้ทำงานในอุปกรณ์ Google Home หรือ Google Nest ของผู้ใช้ และเชื่อมต่ออุปกรณ์อัจฉริยะกับ Assistant คุณสามารถสร้างแอปโดยใช้ TypeScript (แนะนำ) หรือ JavaScript ได้

ขอแนะนำให้ใช้ TypeScript เนื่องจากคุณสามารถใช้ประโยชน์จาก bindings เพื่อดูแลให้ข้อมูลที่แอปส่งคืนตรงกับประเภทที่แพลตฟอร์มคาดหวัง

ดูรายละเอียดเพิ่มเติมเกี่ยวกับ API ได้ที่เอกสารอ้างอิง Local Home SDK API

ตัวอย่างข้อมูลต่อไปนี้แสดงวิธีเริ่มต้นแอป Fulfillment ในเครื่องและแนบเครื่องจัดการ

สแตนด์อโลน
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 สำหรับโค้ดและทรัพยากร Dependency ทั้งหมดของโค้ดเพื่อทำให้แอป Fulfillment ภายในเครื่องใช้งานได้

ใช้โปรแกรมเริ่มต้นโปรเจ็กต์ของแอป Fulfillment ภายในเครื่องเพื่อเริ่มต้นระบบโครงสร้างโปรเจ็กต์ที่เหมาะสมด้วยการกำหนดค่า Bundler ที่คุณต้องการ

เทมเพลตโครงการ

หากต้องการเลือกการกำหนดค่า Bundler ให้เรียกใช้คำสั่ง npm init ตามที่แสดงในตัวอย่างต่อไปนี้

ไม่มี

TypeScript ที่ไม่มีการกำหนดค่า Bundler:

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 ด้วยไดเรกทอรีใหม่ที่จะมีโปรเจ็กต์แอป Fulfillment ภายในเครื่อง

Webpack

TypeScript ที่มีการกำหนดค่า Bundler webpack:

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 ด้วยไดเรกทอรีใหม่ที่จะมีโปรเจ็กต์แอป Fulfillment ภายในเครื่อง

ภาพรวม

TypeScript ที่มีการกำหนดค่า Bundler Rollup:

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 ด้วยไดเรกทอรีใหม่ที่จะมีโปรเจ็กต์แอป Fulfillment ภายในเครื่อง

พัสดุไปรษณีย์

TypeScript ที่มีการกำหนดค่าแพ็กเกจ Parcel

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 ด้วยไดเรกทอรีใหม่ที่จะมีโปรเจ็กต์แอป Fulfillment ภายในเครื่อง

ทำงานระดับโปรเจ็กต์ทั่วไป

โปรเจ็กต์ที่สร้างรองรับสคริปต์ npm ต่อไปนี้

แพ็กเกจ
cd project-directory/
npm run build

สคริปต์นี้คอมไพล์แหล่งที่มา TypeScript และรวมแอปเข้ากับทรัพยากร Dependency สำหรับสภาพแวดล้อมรันไทม์ของ Chrome ในไดเรกทอรีย่อย dist/web และสภาพแวดล้อมรันไทม์ของ Node.js ในไดเรกทอรีย่อย dist/node

ยืนยัน
cd project-directory/
npm run lint
npm run compile
npm test

สคริปต์นี้จะยืนยันไวยากรณ์ของโค้ด TypeScript ของคุณ จากนั้นคอมไพล์โดยไม่สร้างเอาต์พุตใดๆ ในไดเรกทอรีย่อย dist/ และเรียกใช้การทดสอบอัตโนมัติจาก test.ts

การแสดง
cd project-directory/
npm run start

ในระหว่างการพัฒนา สคริปต์นี้จะส่ง App Bundle สำหรับสภาพแวดล้อมรันไทม์ของ Chrome และ Node.js ภายในเครื่อง

ติดตั้งเครื่องจัดการ IDENTIFY

ตัวแฮนเดิล IDENTIFY จะทริกเกอร์เมื่ออุปกรณ์ Google Home หรือ Google Nest รีบูตและเห็นอุปกรณ์ในพื้นที่ซึ่งไม่ได้รับการยืนยัน (รวมถึงอุปกรณ์ปลายทางที่เชื่อมต่อกับฮับ) แพลตฟอร์ม Local Home จะสแกนหาอุปกรณ์ในเครื่องโดยใช้ข้อมูลการกำหนดค่าการสแกนที่คุณระบุไว้ก่อนหน้านี้และเรียกเครื่องจัดการ IDENTIFY พร้อมผลการสแกน

IdentifyRequest จากแพลตฟอร์ม Local Home มีข้อมูลการสแกนของอินสแตนซ์ LocalIdentifiedDevice ระบบจะป้อนข้อมูลอินสแตนซ์ device เพียง 1 รายการเท่านั้นตามการกำหนดค่าการสแกนที่พบอุปกรณ์

หากผลการสแกนตรงกับอุปกรณ์ของคุณ ตัวแฮนเดิล IDENTIFY ควรแสดงผลออบเจ็กต์ IdentifyResponsePayload ที่มีออบเจ็กต์ device ที่มีข้อมูลเมตาของสมาร์ทโฮม (เช่น ประเภท ลักษณะ และสถานะรายงาน)

Google จะสร้างการเชื่อมโยงอุปกรณ์หาก verificationId จากการตอบกลับ IDENTIFY ตรงกับค่าใดค่าหนึ่งใน otherDeviceIds ที่แสดงผลโดยการตอบกลับ SYNC

ตัวอย่าง

ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่คุณจะสร้างตัวแฮนเดิล 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 รายงานรหัสอุปกรณ์ปลายทางภายในที่เชื่อมต่อกับฮับ ให้ตั้งค่า isProxy เป็น true ใน IdentifyResponsePayload
  • หากการตอบสนองด้วย SYNC ไม่รายงานอุปกรณ์ฮับ ให้ตั้งค่า isLocalOnly เป็น true ใน IdentifyResponsePayload
  • ช่อง device.id ประกอบด้วยรหัสอุปกรณ์ภายในของอุปกรณ์ฮับ

ติดตั้งตัวแฮนเดิล REACHABLE_DEVICES (การผสานรวมฮับเท่านั้น)

Google จะส่ง Intent REACHABLE_DEVICES เพื่อยืนยันอุปกรณ์ปลายทางที่ควบคุมในเครื่องได้ Intent นี้จะทริกเกอร์ทุกครั้งที่ Google ทำการสแกนการค้นพบ (ประมาณทุกๆ นาที) ตราบใดที่มีการตรวจพบว่าฮับออนไลน์อยู่

คุณใช้ตัวแฮนเดิล REACHABLE_DEVICES คล้ายกับแฮนเดิล IDENTIFY เว้นแต่ว่าเครื่องจัดการต้องรวบรวมรหัสอุปกรณ์เพิ่มเติมที่อุปกรณ์พร็อกซีภายใน (ซึ่งก็คือฮับ) เข้าถึงได้ ช่อง device.verificationId มีรหัสอุปกรณ์ภายในของอุปกรณ์ปลายทางที่เชื่อมต่อกับฮับ

ReachableDevicesRequest จากแพลตฟอร์ม Local Home มีอินสแตนซ์ของ LocalIdentifiedDevice ด้วยอินสแตนซ์นี้ คุณจะได้รับรหัสอุปกรณ์พร็อกซี รวมถึงข้อมูลจากผลการสแกน

ตัวแฮนเดิล REACHABLE_DEVICES ควรแสดงผลเป็นออบเจ็กต์ ReachableDevicesPayload ที่มีออบเจ็กต์ devices ที่มีอาร์เรย์ของค่า verificationId ที่แสดงถึงอุปกรณ์ปลายทางที่ฮับควบคุม ค่า verificationId ต้องตรงกับค่าใดค่าหนึ่งใน otherDeviceIds จากการตอบกลับ SYNC

ตัวอย่างต่อไปนี้แสดงวิธีสร้างแฮนเดิล 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 เหมือนกับ Intent ของ EXECUTE สำหรับ Fulfillment ระบบคลาวด์ ในทํานองเดียวกัน ตัวแฮนเดิล EXECUTE จะแสดงผลข้อมูลเอาต์พุตในรูปแบบเดียวกับที่ประมวลผล Intent EXECUTE คุณสามารถใช้คลาส Execute.Response.Builder ที่ Local Home SDK มีให้เพื่อให้สร้างคำตอบได้ง่ายขึ้น

แอปของคุณไม่มีสิทธิ์เข้าถึงที่อยู่ IP ของอุปกรณ์โดยตรง แต่ให้ใช้อินเทอร์เฟซ CommandRequest เพื่อสร้างคำสั่งตามโปรโตคอลใดโปรโตคอลหนึ่งต่อไปนี้ ได้แก่ UDP, TCP หรือ HTTP แทน จากนั้นเรียกใช้ฟังก์ชัน deviceManager.send() เพื่อส่งคำสั่ง

เมื่อกำหนดเป้าหมายคำสั่งไปยังอุปกรณ์ ให้ใช้รหัสอุปกรณ์ (และพารามิเตอร์จากช่อง customData หากมี) จากการตอบสนองของ SYNC เพื่อสื่อสารกับอุปกรณ์

ตัวอย่าง

ข้อมูลโค้ดต่อไปนี้แสดงวิธีที่คุณสามารถสร้างเครื่องจัดการ EXECUTE

สแตนด์อโลน/Hub
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 เพื่อรายงานสถานะของอุปกรณ์อัจฉริยะ

แพลตฟอร์ม Local Home จะส่งเพย์โหลดคำขอเดียวกันไปยังฟังก์ชันตัวแฮนเดิล "QUERY" เช่นเดียวกับ Intent ของ QUERY สำหรับการ Fulfillment ระบบคลาวด์ ในทํานองเดียวกัน ตัวแฮนเดิล QUERY จะแสดงผลข้อมูลในรูปแบบเดียวกับที่ประมวลผลจาก Intent QUERY

การส่งคำสั่งไปยังอุปกรณ์ที่อยู่หลังฮับ

ในการควบคุมอุปกรณ์ปลายทางหลังฮับ คุณอาจต้องให้ข้อมูลเพิ่มเติมในเพย์โหลดคำสั่งเฉพาะโปรโตคอลที่ส่งไปยังฮับ เพื่อให้ฮับสามารถระบุอุปกรณ์ที่จะใช้คำสั่ง ในบางกรณี ค่าอาจอนุมานได้จากค่า device.id โดยตรง แต่เมื่อไม่เป็นเช่นนั้น คุณควรระบุข้อมูลเพิ่มเติมนี้ในช่อง customData

หากคุณสร้างแอปโดยใช้ TypeScript อย่าลืมคอมไพล์แอปเป็น JavaScript คุณสามารถใช้ระบบโมดูลที่ต้องการในการเขียนโค้ดได้ ตรวจสอบว่าเบราว์เซอร์ Chrome รองรับเป้าหมาย