How to listen events for launch my device application

hi, all
I need to configure the application capabilities, and increase or decrease the capabilities according to the parameter settings. How to listen events for launch my device application,then Redraw the user UI.

How to capture the event of long pressing the “T7E_ZG …” icon.


Homey apps cannot react to UI events directly.

Thks, My main problem is that I need to use the removecapability method to remove the relevant capabilities, but the app becomes unavailable.
Need to return to re-enter the application to be normal. Is there any way to solve this?

Can you explain what exactly causes your app to have to remove a capability? This is an expensive operation that shouldn’t be done often, and is really only meant to be used as part of a “device migration” between different app versions (where a previous version of the app used an old capability that shouldn’t be used anymore, for instance). The same applies to adding capabilities, too.

Yes, I understand.
My device needs to support [thermostat mode / regulator heating]. After the user switches between different modes, the app UI needs to add or remove thermostat / regulator。

The following screenshot shows:
(1)User change mode

(2) thermostat mode UI ,Capability UI to be removed for (3)

(3) or regulator heating mode UI, Capability UI to be removed for (2)

Can you share you sourcecode?

I have it all fully functional in DeviceCapabilities (test).
See [APP][Pro] Device Capabilities - Enhance the capabilities of devices
(Down the bodem.)

So it absolutely can work.

enenen …

This is CODE block. APP runing, then run the CODE " removeCapability " ,and add other capablility,and THEN to error: “Unavailable” appears at this time

Sorry, i need more code, like in what function is this executed?
This code itself looks correct, i don’t see anything wrong with it.

But the unavailable means a error in the codelines, not a error in adding or removing capabilities itself (if you have no capabilities, it shows an All works perfect screen.

Is the function/method itself async ?
What does the log show? What is the error? which error line?

What do you use to develop? Visual Code?
Please share the output of the Terminal of the last few lines (after trying to execute the code above)

'use strict';
 
const { ZigBeeDevice } = require("homey-zigbeedriver");
const { CLUSTER, Cluster } = require('zigbee-clusters')

 
const appkit = require('./lib/'); 
 
const destructConstProps = function({
  ID, NAME, ATTRIBUTES, COMMANDS,
}) {
  return Object.freeze({
    ID, NAME, ATTRIBUTES, COMMANDS,
  });
}

const HzcThermostatCluster = require('../../lib/HzcThermostatCluster')  
const HzcThermostatUserInterfaceConfigurationCluster = 
  require('../../lib/HzcThermostatUserInterfaceConfigurationCluster')  
Cluster.addCluster(HzcThermostatCluster)  
Cluster.addCluster(HzcThermostatUserInterfaceConfigurationCluster)
CLUSTER['THERMOSTAT_USER_INTERFACE_CONFIGURATION'] =  
  destructConstProps(HzcThermostatUserInterfaceConfigurationCluster)

const getInt16 = function(number) {
  const int16 = new Int16Array(1)
  int16[0] = number
  return int16[0]
} 

const { 
  getOptBaseTime, TIP_CHANGED
}                               = require('./lib/devices/utils');


class thermostat_t7e_zg_thermostat extends ZigBeeDevice {

  onEndDeviceAnnounce(){
    this.log('@@@thermostat_t7e_zg_thermostat.onEndDeviceAnnounce')
  }
       
  async onNodeInit ({ zclNode, node }) {
    super.onNodeInit({ zclNode: zclNode, node: node })  
    this.enableDebug();
    //this.printNode();  
    this._start()    
  };   

  async _start(){ 
    this._thermostatCluster().   
    readAttributes(['sensorMode']).
    then(value => {
     this.log(`++++++ thermostat sensorMode = `, value) 

     if (value.hasOwnProperty('sensorMode')){
      let sensorMode = value['sensorMode'] || 'a'
      if (sensorMode === 'p'){
        this.setStoreValue('regulator_mode', "1")
      }
      else {
        this.setStoreValue('regulator_mode', "0")
      } 

      this._restartApp();  
     } 

   }).
   catch(err => {
    this.error('Error: App read sensor mode failed', err);
    this.setUnavailable('App read sensor mode failed, Please try again.')
    this.setStoreValue('zb_first_init', true) 

    setTimeout(() => {
      this._start()
    }, 3000)
   })

  }  

  async _restartApp(){

    //this.onDeleted()
    //super.onDeleted()

    this.setUnavailable('Device initializing ...')
    
    this.showMessage(TIP_CHANGED)
 

    try{
      let reg_mode = this.getStoreValue('regulator_mode') || '0';
      console.log('restartApp->w:', reg_mode);

      if (!this.hasCapability('onoff')){ await this.addCapability("onoff");   } 
      if (!this.hasCapability('measure_power')){ await this.addCapability("measure_power"); }
      if (!this.hasCapability('meter_power')) { await this.addCapability("meter_power");   }
      if (!this.hasCapability('t7e_zg_sensor_mode')){ await  this.addCapability('t7e_zg_sensor_mode') }
      if (!this.hasCapability('t7e_zg_window_state')) { await this.addCapability('t7e_zg_window_state'); }
      if (!this.hasCapability('t7e_zg_fault')) { await this.addCapability('t7e_zg_fault') }
 
      if (!this.hasCapability('child_lock')) { await this.addCapability("child_lock"); }
 
      if (reg_mode === '1') {   
        
        //remove
        if (this.hasCapability('target_temperature')) {  await this.removeCapability('target_temperature') }
        if (this.hasCapability('measure_temperature')) {  await this.removeCapability('measure_temperature') }
        if (this.hasCapability('t7e_zg_thermostat_mode')){  await this.removeCapability('t7e_zg_thermostat_mode') }
        if (this.hasCapability('eco_mode')){  await this.removeCapability('eco_mode') }
        if (this.hasCapability('frost')){   await this.removeCapability('frost')   } 

        //add
        if (!this.hasCapability('t7e_zg_regulator_percentage')){ await this.addCapability('t7e_zg_regulator_percentage'); }  
 
        this.setSettings({ 
          sensor_mode: 'p',
          thermostat_regulator_mode: '1',
        });  

        this.setCapabilityValue('t7e_zg_sensor_mode', 'p')

        let rp = this.getStoreValue('t7e_zg_regulator_percentage') || 20;
        this.setCapabilityValue('t7e_zg_regulator_percentage', rp);
        
      }
      else{

        //remove
        if (this.hasCapability('t7e_zg_regulator_percentage')){ await this.removeCapability('t7e_zg_regulator_percentage')  }
  
        //add
        if (!this.hasCapability('t7e_zg_thermostat_mode')){ await this.addCapability('t7e_zg_thermostat_mode');  }
        if (!this.hasCapability('target_temperature')){ await this.addCapability("target_temperature");  }
        if (!this.hasCapability('measure_temperature')){  await this.addCapability("measure_temperature");  }  
        if (!this.hasCapability('eco_mode')){  await this.addCapability("eco_mode"); } 
        if (!this.hasCapability('frost')){  await this.addCapability("frost");   }  
 
        let settings = this.getSettings();
        let mode1 = settings.sensor_mode; 
        if (mode1 === 'p'){
          mode1 = 'a';
           this.setSettings({ 
            sensor_mode: mode1,
          }); 
        }  
        this.setCapabilityValue('t7e_zg_sensor_mode', mode1)
        this.setSettings({ 
          thermostat_regulator_mode: '0',
        }); 

      } 
      
    }catch(err){
      console.log('restartApp ERROR', err);
    }

    this._initCapabilityAndListener();
  } 

  _initCapabilityAndListener(){  
    this._setUpSystemCapabilities().catch(this.error)
    this._setUpMeasureTemperatureCapability() 
    this._setUpTargetTemperatureCapability() 
    this._setUpModesCapability() 

    this._setUpWindowOpenFlagCapability() 

    appkit.regulator_percentage.init(this)

    appkit.eco_mode.init(this)
    appkit.child_lock.init(this)
    appkit.frost.init(this)

    appkit.sensor_mode.init(this)  

    appkit.fault.init(this)

    this._getAttributes();  
  
    this.setStoreValue('regulator_mode_changed', false);

    this.log('+++++ setAvailable AND ready ')
    this.setAvailable()
  
    this._onHandlerReport() 
  } 



  async showMessage(msg){ 
    await this.setWarning(msg);  
    await this.unsetWarning();
  }
   
 

  //设备状态上报通知
  onStatusChangeNotificationReport(payload){
    this.log('@@@ onStatusChangeNotificationReport ', payload)
  }
  async _onHandlerReport(){
 
		// onStatusChangeNotification 
		this.zclNode.endpoints[1].clusters[CLUSTER.THERMOSTAT.NAME]
      .onStatusChangeNotification = payload => {
        this.onStatusChangeNotificationReport(payload);
    }  
    return;

 
    this.configureAttributeReporting([
      {
        cluster: CLUSTER.THERMOSTAT,
        attributeName: 'localTemperature',  
        minInterval: 0,
        maxInterval: 300,
        minChange: 1
      },
      {
        cluster: CLUSTER.THERMOSTAT,
        attributeName: 'occupiedHeatingSetpoint',  
        minInterval: 0,
        maxInterval: 300,
        minChange: 1
      },
      {
        cluster: CLUSTER.THERMOSTAT_USER_INTERFACE_CONFIGURATION,
        attributeName: 'keypadLockout',
        minInterval: 0,
        maxInterval: 300,
        minChange: 1
      } 
    ]) 
    this.zclNode.endpoints[1].clusters[CLUSTER.THERMOSTAT.NAME].on('attr.localTemperature', this.onLocalTemperatureReport.bind(this))  
    this.zclNode.endpoints[1].clusters[CLUSTER.THERMOSTAT.NAME].on('attr.occupiedHeatingSetpoint', this.onOccupiedHeatingSetpointReport.bind(this))  
    this.zclNode.endpoints[1].clusters['thermostatUserInterfaceConfiguration'].on('attr.keypadLockout', this.onKeypadLockoutReport.bind(this))
  }
  
  onOccupiedHeatingSetpointReport(value) {
    this.log(`@@@ onOccupiedHeatingSetpointReport `, value)
    const temp = parseFloat(
      (value['occupiedHeatingSetpoint'] / 100).toFixed(1))
    return this.setCapabilityValue('target_temperature', temp)
  }

  onLocalTemperatureReport(value) {
    this.log('@@@ onLocalTemperatureReport ', value)
     
  }

  onKeypadLockoutReport(value){
    this.log('@@@ onKeypadLockoutReport ', value)
  }


  


  _thermostatCluster() { return this.zclNode.endpoints[1].clusters.thermostat }

  //child lock
  _thermostatUserInterfaceConfiguration () {
    return this.zclNode.endpoints[1].clusters.thermostatUserInterfaceConfiguration
  } 

  async _setUpSystemCapabilities () {
  
    // onoff 
    this.registerCapabilityListener('onoff', async isOn => {
        this.log('==========onoff: ', isOn)

        let regModeChanged = this.getStoreValue('regulator_mode_changed') || false  
        if (regModeChanged){ 
          //this._start()  
          //return;
        }   
        
        let lastSystemMode = this.getStoreValue('last_system_mode') || 'heat'
        this.log('.......onoff set ', lastSystemMode)
        this._thermostatCluster().writeAttributes({ systemMode: isOn ? lastSystemMode : 'off', })
    }) 

    // meter_power
    if (this.hasCapability('meter_power')) {

      const {
        multiplier, divisor,
      } = await this.zclNode.endpoints[this.getClusterEndpoint(
        CLUSTER.METERING)].clusters[CLUSTER.METERING.NAME].readAttributes(
        'multiplier', 'divisor').catch(this.error)

      this.log('multiplier-divisor ', multiplier, divisor)

      let md = 0.1;
      if (multiplier && divisor){
        md = multiplier / divisor;
      } 

      this.registerCapability('meter_power', CLUSTER.METERING, {
        get: 'currentSummationDelivered',
        report: 'currentSummationDelivered',
        reportParser: value => {

          this.log(`currentSummationDelivered `, value)
          return value * md 
        },
        getOpts: {
          getOnStart: true, pollInterval: getOptBaseTime,  
        },
        reportOpts: {
          configureAttributeReporting: {
            minInterval: 10,  
            maxInterval: 60000,  
            minChange: 10,
          },
        },
      })
    }

    // measure_power
    if (this.hasCapability('measure_power')) {

      this.registerCapability('measure_power', CLUSTER.ELECTRICAL_MEASUREMENT, {
        get: 'activePower', report: 'activePower', reportParser: value => {

          return value / 10
        }, getOpts: {
          getOnStart: true, pollInterval: getOptBaseTime,
        }, reportOpts: {
          configureAttributeReporting: {
            minInterval: 10,  
            maxInterval: 60000,  
            minChange: 10,
          },
        },
      })
    }

  }

  _setUpMeasureTemperatureCapability () {
    
    if (!this.hasCapability('measure_temperature')) return
    
    this.registerCapability('measure_temperature', CLUSTER.THERMOSTAT, {
      get: 'localTemperature',
      report: 'localTemperature',
      reportParser: value => {

        let temp = parseFloat((getInt16(value) / 100).toFixed(1))
        this.log(`localTemperature report `, value, temp)
        if (temp < -10) temp = -10
        if (temp > 60) temp = 60
        return temp
      },
      getOpts: {
        getOnStart: true, pollInterval: getOptBaseTime,  
        getOnOnline: true 
      },
      reportOpts: {
        configureAttributeReporting: {
          minInterval: 0, 
          maxInterval: 600, 
          minChange: 5,
        },
      },
    })
    
  }

  _setUpTargetTemperatureCapability () {

    if (!this.hasCapability('target_temperature')) return
 
    this.registerCapability('target_temperature', CLUSTER.THERMOSTAT, {
      get: 'occupiedHeatingSetpoint',
      report: 'occupiedHeatingSetpoint',
      reportParser: value => {

        if (this.getCapabilityValue('t7e_zg_thermostat_mode') === 'off') {

          this._thermostatCluster().
            readAttributes('systemMode').
            then(value => {

              let mode = value.systemMode
              let isOn = mode !== 'off'
              this.setCapabilityValue('onoff', isOn).catch(this.error)
              this.setCapabilityValue('t7e_zg_thermostat_mode', mode).catch(this.error)

              this.log(`systemMode after occupiedHeatingSetpoint `, value, mode,
                isOn)
            }).
            catch(this.error)
        }

        let temp = parseFloat((getInt16(value) / 100).toFixed(1))
        this.log(`occupiedHeatingSetpoint report `, value)
        return temp
      },
      getOpts: {
        getOnStart: true, pollInterval: getOptBaseTime, 
      },
      reportOpts: {
        configureAttributeReporting: {
          minInterval: 0,  
          maxInterval: 600, 
          minChange: 5,
        },
      },
    })

    this.registerCapabilityListener('target_temperature', async value => {

      this.log(`occupiedHeatingSetpoint setParser `, value)
      let payload = {
        occupiedHeatingSetpoint: value * 100,
      }
      return this._thermostatCluster().writeAttributes(payload)
    })
   
    
  }

  _setUpWindowOpenFlagCapability() {

    if (!this.hasCapability('t7e_zg_window_state')) return 

    this.registerCapability('t7e_zg_window_state', CLUSTER.THERMOSTAT, {
      get: 'windowState', report: 'windowState', reportParser: value => {

        this.log(`windowState report `, value, value === 'opened')
        return value === 'opened' ? true : false
      }, 
      getOpts: {
        getOnStart: true, pollInterval: getOptBaseTime,  
        getOnOnline: true,
      },
      reportOpts: {
        configureAttributeReporting: {
          minInterval: 10,  
          maxInterval: 600, 
          minChange: 1,
        },
      },
    })
  } 

  _setUpModesCapability() {
  
    if (!this.hasCapability('t7e_zg_thermostat_mode')) return

    this.registerCapability('t7e_zg_thermostat_mode', CLUSTER.THERMOSTAT, {
      get: 'systemMode',
      getOpts: {
        getOnStart: true, getOnOnline: true, pollInterval: getOptBaseTime,
      },
      report: 'systemMode',
      reportParser: value => {

        // Refresh onoff
        let isOn = value != 'off'
        this.setCapabilityValue('onoff', isOn).catch(this.error)

        if (isOn === false) {

          this.setCapabilityValue('target_temperature', 5)

        } else {

          // Refresh heating setpoint
          this._thermostatCluster().
            readAttributes('occupiedHeatingSetpoint').
            then(value => {

              this.log(`occupiedHeatingSetpoint after mode `, value)
              const temp = parseFloat(
                (value['occupiedHeatingSetpoint'] / 100).toFixed(1))
              return this.setCapabilityValue('target_temperature', temp)
            }).
            catch(this.error)
        }

        this.log(`systemMode report `, value)
        return value
      },
      getOpts: {
        getOnStart: true, pollInterval: getOptBaseTime,  
        getOnOnline: true,
      },
      reportOpts: {
        configureAttributeReporting: {
          minInterval: 10,  
          maxInterval: 600, 
          minChange: 1,
        },
      },
    })

    this.registerCapabilityListener('t7e_zg_thermostat_mode', async value => {

      this.log(`systemMode set `, value)
      let payload = {
        systemMode: value,
      }
      this.setStoreValue('last_system_mode', value);
      return this._thermostatCluster().writeAttributes(payload)
    })
  } 
 
    

  async onSettings({ oldSettings, newSettings, changedKeys }) {
    // run when the user has changed the device's settings in Homey.
    // changedKeysArr contains an array of keys that have been changed
    // if the settings must not be saved for whatever reason:
    // throw new Error('Your error message');
 
    this._setDeviceSettings(newSettings, changedKeys);

  } 
  async _setDeviceSettings (newSettings, changedKeys) {
    
    this.log('+++++ settings : ', newSettings, changedKeys);

    changedKeys.forEach(element => {
      console.log("");console.log("");console.log("");
      console.log("-----------------------config:", element);
      
      let o = appkit[element];
      if (o != undefined){
        if (o['setConfig']){
          o.setConfig(this, newSettings[element]);
        } 
      }
    }) 
 
  }
 

  _getAttributes () { 
    this.log('+++++++++++++++++++ Refresh state from device.')

    this._thermostatUserInterfaceConfiguration().
      readAttributes(['keypadLockout']).
      then(value => {
        this.log(`+++++++ child lock = `, value)
        if (value.hasOwnProperty('keypadLockout')){
          let isOpen = value['keypadLockout'] === 'level1Lockout'
          this.setCapabilityValue('child_lock', isOpen ? true : false)
        }
      }).
      catch(this.error)
     

    this._thermostatCluster().
      readAttributes(['windowState']).
      then(value => {
        this.log(`++++++ thermostat windowState = `, value)

        if (value.hasOwnProperty('windowState')) {
          let isOpen = value['windowState'] === 'opened'
          this.setCapabilityValue('t7e_zg_window_state', isOpen ? true : false)
        }

      }).
      catch(this.error) 

    // target_temperature
    if (this.hasCapability('target_temperature')){
        this._thermostatCluster().
            readAttributes('occupiedHeatingSetpoint').
            then(value => {

              this.log(`+++++++ occupiedHeatingSetpoint after mode `, value)
              const temp = parseFloat(
                (value['occupiedHeatingSetpoint'] / 100).toFixed(1))
              return this.setCapabilityValue('target_temperature', temp)
            }).
            catch(this.error)
    }

      
    //measure_temperature
    if (this.hasCapability('measure_temperature')){
        this._thermostatCluster().
            readAttributes('localTemperature').
            then(value => {

              this.log(`++++++++++ localTemperature`, value)
              const temp = parseFloat(
                (value['localTemperature'] / 100).toFixed(1))
              return this.setCapabilityValue('measure_temperature', temp)
            }).
            catch(this.error)
    } 


      
    if (this.hasCapability('frost')){
        this._thermostatCluster().
          readAttributes(['frost']).
          then(value => {
            this.log(`++++++ thermostat frost = `, value)

            if (value.hasOwnProperty('frost')){
              let isOpen = value['frost'] === 'opened'
              this.setCapabilityValue('frost', isOpen ? true : false)
            }

          }).
          catch(this.error) 
    }

    //t7e_zg_regulator_percentage
    if (this.hasCapability('t7e_zg_regulator_percentage')){
        this._thermostatCluster().
          readAttributes(['pIHeatingDemand']).
          then(value => {
            this.log(`++++++ thermostat pIHeatingDemand = `, value)

            if (value.hasOwnProperty('pIHeatingDemand')){ 
              this.setCapabilityValue('t7e_zg_regulator_percentage', value['pIHeatingDemand'])
            }

          }).
          catch(this.error) 
    }
    
    //lcd backlight
    this._thermostatCluster().readAttributes(['backlight']).then(value =>{
      this.log('+++++++++ lcd backlight report: ', value)
      if (value.hasOwnProperty('backlight')) {
        this.setSettings({ backlight: value['backlight'] })
      }
    })

    //fault
    if (this.hasCapability('t7e_zg_fault')){
      this._thermostatCluster().readAttributes(["fault"]).then(value =>{
        this.log('++++++++++ fault report ', value)
        if (value.hasOwnProperty('fault')){
          this.setCapabilityValue('t7e_zg_fault', value)
        }
      })
    }
    
      
  }
}

module.exports = thermostat_t7e_zg_thermostat;

**CHANGE run mode,then call _start() method. **
image

Where does the “Please go back and WAIT for reinitializaing […]” text comes from? I don’t see it anywhere?

And could you please explain what you want exactly?

Because as far is i can see, you puth the device on Unavailable yourself, but you set it back to Available.
Only, you don’t (a)wait for it?

This might be the issue:

async _restartApp(){
[....]
    this.setUnavailable('Device initializing ...')    

You dont wait for it to be complete, so it might be that this.SetAvailable is executed while the setUnavailable is not yet done.

So, before doing anything else, i advice you to go through your code again, and make sure that everywhere/in-the-_restartApp, all async methods are (a)waited for:

async _restartApp(){
[....]
    await this.setUnavailable('Device initializing ...') ;

Check Device - Homey Apps SDK v3 for methods that have async in front of it:

If all is written correctly, it should work by putting it unavailable and available again:
image
This works fine.

If it still doesn’t work correct after putting await in front of all methods that are async, let me know.

Thks, i try …

I need more time to test. Put it down for the time being.