<commit_before>/**
 * Copyright 2019, Google, Inc.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/// <reference types="@google/local-home-sdk" />

import { decodeFirst as cborDecodeFirst } from "cbor";
import { IBrightnessAbsolute, IColorAbsolute, IOnOff,
         ILightCommand, ILightState,
         IFakecandyData } from "./types";

// TODO(proppy): add typings
const opcStream = require("opc");

// HomeApp implements IDENTIFY and EXECUTE handler for smarthome local device execution.
export class HomeApp {
  // Led count will be updated to match device UDP scan data in IDENTIFY.
  private ledCount: number = 16;

  constructor(private readonly app: smarthome.App) {
      this.app = app;
  }

  // identifyHandlers decode UDP scan data and structured device information.
  public identifyHandler = (identifyRequest: smarthome.IntentFlow.IdentifyRequest):
    Promise<smarthome.IntentFlow.IdentifyResponse> => {
    console.log("IDENTIFY request", identifyRequest);
    // TODO(proppy): handle multiple inputs.
    const device = identifyRequest.inputs[0].payload.device;
    if (device.udpScanData === undefined) {
       throw Error(`identify request is missing discovery response: ${identifyRequest}`);
    }
    // Raw discovery data are encoded as 'hex'.
    const udpScanData = Buffer.from(device.udpScanData, "hex");
    console.debug("udpScanData:", udpScanData);
    return new Promise((resolve, reject) => {
      // Device encoded discovery payload in CBOR.
      cborDecodeFirst(udpScanData, (error: Error, discoveryData: IFakecandyData) => {
        if (error != null) {
          return reject(error);
        }
        console.debug("discoveryData:", discoveryData);
        this.ledCount = discoveryData.leds;
        const identifyResponse = {
          intent: smarthome.Intents.IDENTIFY,
          requestId: identifyRequest.requestId,
          payload: {
            device: {
              id: device.id || "deviceId",
              type: "action.devices.types.LIGHT",
              deviceInfo: {
                manufacturer: "Colorful light maker",
                model: discoveryData.model,
                hwVersion: discoveryData.hw_rev,
                swVersion: discoveryData.fw_rev,
              },
              indicationMode: smarthome.IntentFlow.IndicationMode.BLINK,
              verificationId: discoveryData.id,
            },
          },
        };
        console.log("IDENTIFY response", identifyResponse);
        resolve(identifyResponse);
      });
    });
  }

  // executeHandler send openpixelcontrol messages corresponding to light device commands.
  public executeHandler = (executeRequest: smarthome.IntentFlow.ExecuteRequest):
    Promise<smarthome.IntentFlow.ExecuteResponse> => {
    console.log("EXECUTE request:", executeRequest);
    // TODO(proppy): handle multiple inputs/commands.
    const command = executeRequest.inputs[0].payload.commands[0];
    // TODO(proppy): handle multiple executions.
    const execution = command.execution[0];

    // Create execution response to capture individual command
    // success/failure for each devices.
    const executeResponse =  new smarthome.Execute.Response.Builder()
      .setRequestId(executeRequest.requestId);

    // Handle light device commands for all devices.
    return Promise.all(command.devices.map((device) => {
      const params = execution.params as IOnOff | IColorAbsolute | IBrightnessAbsolute;
      const opcMessage = opcMessageFromCommand(execution.command,
                                               execution.params as ILightCommand,
                                               this.ledCount);
      console.debug("opcMessage:", opcMessage);
      // Craft TCP request.
      const deviceCommand = new smarthome.DataFlow.TcpRequestData();
      deviceCommand.requestId = executeRequest.requestId;
      deviceCommand.deviceId = device.id;
      // TCP request data is encoded as 'hex'.
      deviceCommand.data = opcMessage.toString("hex");
      deviceCommand.port = 7890;
      deviceCommand.isSecure = false;
      deviceCommand.operation = smarthome.Constants.TcpOperation.WRITE;
      console.debug("TcpRequestData:", deviceCommand);
      // TODO(proppy): handle send error to surface additional context.
      return this.app.getDeviceManager()
        .send(deviceCommand)
        .then((result: smarthome.DataFlow.CommandSuccess) => {
          const state: ILightState = {
            ...params,
            online: true,
          };
          executeResponse.setSuccessState(result.deviceId, state);
        })
        .catch((e: smarthome.IntentFlow.HandlerError) => {
          executeResponse.setErrorState(device.id, e.errorCode);
        });
    })).then(() => {
      console.log("EXECUTE response", executeResponse);
      // Return execution response to smarthome infrastructure.
      return executeResponse.build();
    });
  }
}

export function opcMessageFromCommand(command: string, params: ILightCommand,
                                      ledCount: number): Buffer {
  const stream = opcStream();
  switch (command) {
    case "action.devices.commands.OnOff": {
      // Convert OnOff to Fadecandy color correction message
      // with brightness 0 or 1.
      const brightness = (params as IOnOff).on ? 1 : 0;<commit_msg>smarthome/local: remove obsolete TODO<commit_after>/**
 * Copyright 2019, Google, Inc.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/// <reference types="@google/local-home-sdk" />

import { decodeFirst as cborDecodeFirst } from "cbor";
import { IBrightnessAbsolute, IColorAbsolute, IOnOff,
         ILightCommand, ILightState,
         IFakecandyData } from "./types";

// TODO(proppy): add typings
const opcStream = require("opc");

// HomeApp implements IDENTIFY and EXECUTE handler for smarthome local device execution.
export class HomeApp {
  // Led count will be updated to match device UDP scan data in IDENTIFY.
  private ledCount: number = 16;

  constructor(private readonly app: smarthome.App) {
      this.app = app;
  }

  // identifyHandlers decode UDP scan data and structured device information.
  public identifyHandler = (identifyRequest: smarthome.IntentFlow.IdentifyRequest):
    Promise<smarthome.IntentFlow.IdentifyResponse> => {
    console.log("IDENTIFY request", identifyRequest);
    // TODO(proppy): handle multiple inputs.
    const device = identifyRequest.inputs[0].payload.device;
    if (device.udpScanData === undefined) {
       throw Error(`identify request is missing discovery response: ${identifyRequest}`);
    }
    // Raw discovery data are encoded as 'hex'.
    const udpScanData = Buffer.from(device.udpScanData, "hex");
    console.debug("udpScanData:", udpScanData);
    return new Promise((resolve, reject) => {
      // Device encoded discovery payload in CBOR.
      cborDecodeFirst(udpScanData, (error: Error, discoveryData: IFakecandyData) => {
        if (error != null) {
          return reject(error);
        }
        console.debug("discoveryData:", discoveryData);
        this.ledCount = discoveryData.leds;
        const identifyResponse = {
          intent: smarthome.Intents.IDENTIFY,
          requestId: identifyRequest.requestId,
          payload: {
            device: {
              id: device.id || "deviceId",
              type: "action.devices.types.LIGHT",
              deviceInfo: {
                manufacturer: "Colorful light maker",
                model: discoveryData.model,
                hwVersion: discoveryData.hw_rev,
                swVersion: discoveryData.fw_rev,
              },
              indicationMode: smarthome.IntentFlow.IndicationMode.BLINK,
              verificationId: discoveryData.id,
            },
          },
        };
        console.log("IDENTIFY response", identifyResponse);
        resolve(identifyResponse);
      });
    });
  }

  // executeHandler send openpixelcontrol messages corresponding to light device commands.
  public executeHandler = (executeRequest: smarthome.IntentFlow.ExecuteRequest):
    Promise<smarthome.IntentFlow.ExecuteResponse> => {
    console.log("EXECUTE request:", executeRequest);
    // TODO(proppy): handle multiple inputs/commands.
    const command = executeRequest.inputs[0].payload.commands[0];
    // TODO(proppy): handle multiple executions.
    const execution = command.execution[0];

    // Create execution response to capture individual command
    // success/failure for each devices.
    const executeResponse =  new smarthome.Execute.Response.Builder()
      .setRequestId(executeRequest.requestId);

    // Handle light device commands for all devices.
    return Promise.all(command.devices.map((device) => {
      const params = execution.params as IOnOff | IColorAbsolute | IBrightnessAbsolute;
      const opcMessage = opcMessageFromCommand(execution.command,
                                               execution.params as ILightCommand,
                                               this.ledCount);
      console.debug("opcMessage:", opcMessage);
      // Craft TCP request.
      const deviceCommand = new smarthome.DataFlow.TcpRequestData();
      deviceCommand.requestId = executeRequest.requestId;
      deviceCommand.deviceId = device.id;
      // TCP request data is encoded as 'hex'.
      deviceCommand.data = opcMessage.toString("hex");
      deviceCommand.port = 7890;
      deviceCommand.isSecure = false;
      deviceCommand.operation = smarthome.Constants.TcpOperation.WRITE;
      console.debug("TcpRequestData:", deviceCommand);
      return this.app.getDeviceManager()
        .send(deviceCommand)
        .then((result: smarthome.DataFlow.CommandSuccess) => {
          const state: ILightState = {
            ...params,
            online: true,
          };
          executeResponse.setSuccessState(result.deviceId, state);
        })
        .catch((e: smarthome.IntentFlow.HandlerError) => {
          executeResponse.setErrorState(device.id, e.errorCode);
        });
    })).then(() => {
      console.log("EXECUTE response", executeResponse);
      // Return execution response to smarthome infrastructure.
      return executeResponse.build();
    });
  }
}

export function opcMessageFromCommand(command: string, params: ILightCommand,
                                      ledCount: number): Buffer {
  const stream = opcStream();
  switch (command) {
    case "action.devices.commands.OnOff": {
      // Convert OnOff to Fadecandy color correction message
      // with brightness 0 or 1.
      const brightness = (params as IOnOff).on ? 1 : 0;
{'fraction_non_alphanumeric': 0.09944655828433069, 'fraction_numerical': 0.0038049117952265654, 'mean_word_length': 2.7390882638215324, 'pattern_counts': {'":': 2, '<': 9, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2670065', 'n_tokens_mistral': 3017, 'n_tokens_neox': 2945, 'n_words': 832}