var CARDNAME = "homeseer-wd200-status-card"; var VERSION = "2023.12.07"; var OLDMODEL = "HS-WD200+"; var MODEL = "HS-WX300"; var DESCRIPTION = "This card shows the status of the seven LEDs on the HS-WD200+ or HS-WX300 dimmer switch connected using zwave_js."; var DOC_URL = "https://github.com/rodpayne/home-assistant#lovelace-homeseer-wd200-card"; // import { dump } from "js-yaml"; // import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; // import { dump } from "https://raw.githubusercontent.com/nodeca/js-yaml/master/dist/js-yaml.min.js"; var LitElement = LitElement || Object.getPrototypeOf(customElements.get("hui-warning")); var html = LitElement.prototype.html; class HomeSeerWD200StatusCard extends HTMLElement { constructor() { super(); } // ============================================ // Get device information and update the card // ============================================ async _render(firstTime) { if (firstTime || typeof this._timeDisplayed == 'undefined') { // ----------------------------------------------------------------- // first-time initialization that had to wait until we had 'hass': // ----------------------------------------------------------------- console.debug("first-time initialization"); var entityId; var state; var friendlyName; if (typeof this._config.entity_id === 'undefined' || !this._config.entity_id) { // let message = 'You need to define an entity_id.'; // this._showError(message); // return } else { entityId = this._config.entity_id; state = this._hass.states[entityId]; if (typeof state === 'undefined') { let message = 'No entity with entity_id of "' + entityId + '" was found.'; this._showError(message); return } friendlyName = 'friendly_name' in state.attributes ? state.attributes['friendly_name'] : entityId; } this.zwave_device_id = this._config.zwave_device_id; this.zwave_node_id = this._config.zwave_node_id; var callResult; if ((typeof this.zwave_device_id === 'undefined') || (typeof this.zwave_node_id === 'undefined') || (!this.zwave_device_id) || (!this.zwave_node_id)) { // ---------------------------------------------------------------------- // Retrieve zwave_device_id and zwave_node_id from the device registry: // ---------------------------------------------------------------------- callResult = await this._hass.callWS({ type: "config/device_registry/list", }) var deviceList = callResult.filter(obj => { return (obj.model === MODEL || obj.model === OLDMODEL) && obj.identifiers[0][0] === 'zwave_js'; }) if (!deviceList || deviceList.length == 0) { this._showError("No model " + OLDMODEL + "/" + MODEL + " devices were found."); return; } console.debug("deviceList = ", deviceList); /* default to the first or only device */ var selectedDevice = deviceList[0]; this.zwave_device_id = deviceList[0].id; this.zwave_node_id = +deviceList[0].identifiers[0][1].split('-')[1]; /* look for one that matches the entity_id friendlyName */ var device = _zwaveDeviceThatMatchesName(deviceList, friendlyName) if (device) { selectedDevice = device; } else { let entityNameMismatch = friendlyName ? "No model " + OLDMODEL + "/" + MODEL + " devices with name " + friendlyName + " were found. " : ""; let entityIdSpecified = (entityId) ? "You may need to specify zwave_device_id and zwave_node_id configuration parameters " + "or rename device so that device name and entity friendly name match." : OLDMODEL + "/" + MODEL + " device with name " + selectedDevice['name_by_user'] + " was selected. " let deviceSpecifiedInConfig = (entityId || this._config.zwave_device_id || this._config.zwave_node_id) ? "" : "Specify either entity_id or zwave_device_id and zwave_node_id. "; console.warn(entityNameMismatch + entityIdSpecified + deviceSpecifiedInConfig); } console.debug("selectedDevice = ", selectedDevice); this.zwave_device_id = selectedDevice.id; this.zwave_node_id = +selectedDevice.identifiers[0][1].split('-')[1]; var title = (this._config.title) ? this._config.title : friendlyName ? friendlyName : selectedDevice['name_by_user']; this.querySelector('span').innerHTML = `
`; this.content = this.querySelector('div'); } } else if ((new Date() - this._timeDisplayed) <= 1000) { // ---- update no more frequently than every second return; } this._timeDisplayed = new Date(); // --------------------------------------------------- // Retrieve the current Z-Wave Device Configuration: // --------------------------------------------------- callResult = await this._hass.callWS({ type: "zwave_js/get_config_parameters", device_id: this.zwave_device_id, }) if (_compareEqualValues(this.previousParameterCallResult, callResult)) { return } this.previousParameterCallResult = callResult; console.debug("New zwave_js/get_config_parameters web call result =", callResult); // --------------------- // Format the Display: // --------------------- let tableForIndicators = '
'; for (let i = 7; i > 0; i--) { let colorParam = callResult[this.zwave_node_id + '-112-0-2' + i]; let f = 2 ** (i - 1); let blinkParam = callResult[this.zwave_node_id + '-112-0-31-' + f]; let indicatorHTML = '
' + this._colorHint[colorParam.value] + '
' + _overrideLabel(this._config, i, colorParam.metadata.label) + '
'; tableForIndicators += indicatorHTML; } tableForIndicators += '
'; this.content.innerHTML = tableForIndicators; // ------------------------------- // ---- functions for _render ---- // ------------------------------- function _zwaveDeviceThatMatchesName(deviceList, nameByUser) { var returnDevice = null; for (let i = 0; i < deviceList.length; i++) { let listNameByUser = deviceList[i]['name_by_user']; if ((listNameByUser === nameByUser)) { returnDevice = deviceList[i]; } } return returnDevice; } function _compareEqualValues(object1, object2) { if (typeof object1 == 'undefined' || !object1) { console.debug("Changed: previous object undefined"); return false; } // See https://dmitripavlutin.com/how-to-compare-objects-in-javascript/ const keys1 = Object.keys(object1); const keys2 = Object.keys(object2); if (keys1.length !== keys2.length) { console.debug("Changed: " + keys1.length + " vs " + keys2.length); return false; } for (let key of keys1) { if (object1[key].value !== object2[key].value) { console.debug("Changed: " + key + " = " + object2[key].value); return false; } } return true; } function _overrideLabel(config, i, deviceLabel) { return ('labels' in config && config.labels && config.labels[i - 1]) ? config.labels[i - 1] : deviceLabel; } } _showError(title) { console.debug("_showError(" + title + ")"); let dumped = undefined; if (this._config) { try { console.debug(this._config); // todo: format with dump() ? dumped = JSON.stringify(this._config, null, 4); //dumped = dump(this._config); } catch (err) { dumped = `[Error dumping ${this._config}]`; } } else { dumped = `[No this._config]`; } console.debug("dumped=" + dumped); this.querySelector('span').innerHTML = ` ${dumped ? `
${dumped}
` : ""}
    `;
    return;
  }

  // ========================================================================
  //  Whenever any state changes, a new `set` is done for the `hass` object.
  // ========================================================================

  set hass(hass) {
    this._hass = hass;
    if (typeof this._config !== "object") {
      console.debug("_config not set before _hass set");
      return;
    };
    this._render(false)
      .catch(err => {
        console.debug("error caught in _render");
        console.debug(err);
        this._showError("Exception rendering card: " + err.message);
      });
  }

  // =================================================================
  //  Accept the user supplied configuration and define the basics.
  //  Lovelace will render an error card when an exception is thrown.
  // =================================================================

  setConfig(config) {

    console.info("%c %s %c %s",
      "color: white; background: forestgreen; font-weight: 700;",
      CARDNAME.toUpperCase(),
      "color: forestgreen; background: white; font-weight: 700;",
      VERSION,
    );

    if (config.labels && config.labels.length != 7 && config.labels.length != 0) {
      throw new Error('If labels option is specified, seven labels are needed.');
    }
    this._config = config;

    // what the color values mean:
    this._colorState = { 0: "Off", 1: "Red", 2: "Green", 3: "Blue", 4: "Magenta", 5: "Yellow", 6: "Cyan", 7: "White" }
    // hints for colorblind people:
    this._colorHint = { 0: "*", 1: "R", 2: "G", 3: "B", 4: "M", 5: "Y", 6: "C", 7: "W" }

    this.innerHTML = `
    
    
    `;

    this.previousParameterCallResult = null;

    if (typeof this._hass !== "object") {
      return;
    };

    this._render(true)
      .catch(err => {
        console.debug("error caught in _render");
        console.debug(err);
        this._showError("Exception rendering card: " + err.message);
      });

  }

  // ==========================================================================
  //  Home Assistant uses this to distribute cards over the available columns.
  // ==========================================================================
  getCardSize() {
    return 7;
  }

  // =============================
  //  Display in the card editor:
  // =============================

  static getConfigElement() {
    return document.createElement(CARDNAME + "-editor");
  }

  static getStubConfig() {
    return {
      entity_id: "",
      zwave_device_id: "",
      zwave_node_id: "",
      title: "",
      labels: [],
    }
  }

}
customElements.define(CARDNAME, HomeSeerWD200StatusCard);

class HomeSeerWD200StatusCardEditor extends LitElement {
  static get properties() {
    return {
      hass: {},
      _config: {},
    };
  }

  // setConfig works the same way as for the card itself
  async setConfig(config) {
    this._config = config;
    //  this._selector = 0;

    // https://github.com/thomasloven/hass-config/wiki/PreLoading-Lovelace-Elements
    if (!customElements.get("ha-entity-picker") || !customElements.get("ha-selector-text")) {
      console.debug("preloading helpers");
      // First we get an entities card element
      const cardHelpers = await window.loadCardHelpers();
      const entitiesCard = await cardHelpers.createCardElement({ type: "entities", entities: [] }); // A valid config avoids errors
      // Then we make it load its editor through the static getConfigElement method
      entitiesCard.constructor.getConfigElement();
    }
  }

  // This function is called when the input element of the editor loses focus
  entityChanged(ev) {

    // We make a copy of the current config so we don't accidentally overwrite anything too early
    const _config = Object.assign({}, this._config);
    // Then we update the entity value with what we just got from the input field
    _config.entity_id = ev.target.value;
    // And finally write back the updated configuration all at once
    this._config = _config;

    // A config-changed event will tell lovelace we have made changed to the configuration
    // this make sure the changes are saved correctly later and will update the preview
    const event = new CustomEvent("config-changed", {
      detail: { config: _config },
      bubbles: true,
      composed: true,
    });
    this.dispatchEvent(event);
  }

  render() {
    if (!this.hass || !this._config) {
      return html``;
    }

    // @focusout below will call entityChanged when the input field loses focus (e.g. the user tabs away or clicks outside of it)
    return html`
    

${DESCRIPTION} Visit the README for more details.

`; // } } customElements.define(CARDNAME + "-editor", HomeSeerWD200StatusCardEditor); window.customCards = window.customCards || []; window.customCards.push({ type: CARDNAME, name: "HomeSeer WD200 Status Card", preview: true, // Optional - defaults to false description: DESCRIPTION, });