Estimate energy usage based on Fan Speed or anything else

I’m making an app for connecting my Air Purifiers, and I want to estimate it’s power usage based on fanspeed, is this possible and what would be the best way to do this.
Currently I’ve got this but I’m looking for something more gradual

  "energy": {
    "approximation": {
      "usageOn": 29, 
      "usageOff": 1 
    }
  },

You can create a measure power capability and set its value within your code.

What brand of air purifier is it?

Yeah I actually just figured it out, but I thought I wasn’t mean to use that capability when it’s only an estimate, but turns out it’s fine if I just add this

    "capabilitiesOptions": {
    "measure_power": {
      "approximated": true
    }
  }

Also it’s a princess air purifier, which uses the HomeWizard Climate app, thanks for the help!

The HomeWizard Climate app? That one works almost the same as the HomeWizard Link app I built. Maybe you can use the auth logic?

You can copy everything from the pairing, but you should remove the select_link view, and instead feed that directly to the list_devices view. The endpoint will be https://fan.homewizard.com/ instead of https://.homewizard.link/

Thanks!

Definitely looks like there is a lot I could use in there as it’s indeed very similar.

I’ve already got it working but it’s quite a mess atm!

I’m currently working on a HomeWizard Cleaner app which (based on decompiling the mobile app) seems to work exactly the same as the HomeWizard Climate app, but then using the fan.homewizard.com domain instead of the cleaner.homewizard.com domain. This is the pairing logic for it (maybe replace type = cleaner with “fan“ or “climate“?)

    session.setHandler("login", async (data) => {
      try {
        const email = data.email;
        const password = data.password;
        if (!data.email || !data.password) {
          return false;
        }
        const basicAuth = "Basic " + Buffer.from(`${email}:${password}`).toString("base64");
        const response = await axios.get('https://api.homewizardeasyonline.com/v1/auth/devices', {
          headers: {
            'Authorization': `${basicAuth}`
          }
        });
        this.homey.settings.set('email', email);
        this.homey.settings.set('password', password);
        this.homey.settings.set('loggedIn', true);
        await session.showView('list_devices');
        return true;
      } catch (error) {
        if (error.response && error.response.status === 401) {
          return false;
        }
        throw new Error("Error during API key check: " + error.message);
      }
    });

    session.setHandler("list_devices", async (data) => {
      try {
        const email = this.homey.settings.get('email');
        const password = this.homey.settings.get('password');
        if (!email || !password) {
          throw new Error("Email or password not found in storage.");
        }
        const basicAuth = "Basic " + Buffer.from(`${email}:${password}`).toString("base64");
        const response = await axios.get('https://api.homewizardeasyonline.com/v1/auth/devices', {
          headers: {
            'Authorization': `${basicAuth}`
          }
        });
        const links = response.data.devices.filter(d => d.type === "cleaner");
        if (links.length === 0) {
          return [];
        }
        return links.map(link => ({
          name: link.name,
          data: {
            id: link.identifier,
          },
          store: {
            id: link.identifier,
            endpoint: link.endpoint
          },
        }));
      } catch (error) {
        throw new Error("Error while fetching links: " + error.message);
      }
    });

I don’t own the HomeWizard Cleaner device myself (it’s based on reverse engineering/modifying requests to “fake“ a HomeWizard Cleaner device with HTTP Toolkit).

The name “link“ was used as most of this is copied from my HomeWizard Link app. Device file (basic implementation of fetching status):

    const email = this.homey.settings.get('email');
    const password = this.homey.settings.get('password');
    const basicAuth = "Basic " + Buffer.from(`${email}:${password}`).toString("base64");
    const response = await axios.get('https://api.homewizardeasyonline.com/v1/auth/devices', {
      headers: {
        'Authorization': `${basicAuth}`
      }
    });
    this.log('Fetched account info from API:', response.data);      
    const tokenResponse = await axios.post('https://api.homewizardeasyonline.com/v1/auth/token', {
      device: this.getData().id
    }, {
      headers: {
        'Authorization': `${basicAuth}`
      }
    });
    this.log('Token response:', tokenResponse.data);
    const token = tokenResponse.data.token;
    const endpoint = this.getStoreValue('endpoint');
    const statusResponse = await axios.get(`${endpoint}/`, {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });

The device also exposes a Local API, but I never got discovery to work so that likely isn’t allowed on the App Store without discovery (to use local API, replace the endpoint from the deviceStoreValue with the local IP address).

The cloud endpoint is given by the API, but it’s likely https://fan.homewizard.com/<identifier (likely FAN_<MACADDRESS>)

For the Link, it would be https://<MACADDRESS>.homewizard.link/ and for the Cleaner it’s https://cleaner.homewizard.com/<CLNR_<MACADDRESS>

Edit: forgot to post the pairing view:

<!DOCTYPE html>
<html>
  <head>
    <style>
      .error {
        color: red;
        display: none;
      }
    </style>
  </head>
  <body>
    <fieldset class="homey-form-fieldset">
      <legend class="homey-form-legend">HomeWizard Cleaner</legend>
      <br>
      <div class="homey-form-group">
        <label class="homey-form-label" for="email" data-i18n="pair.email">Email address</label>
        <input class="homey-form-input" id="email" type="text" value="" />
      </div>
      <div class="homey-form-group">
        <label class="homey-form-label" for="password" data-i18n="pair.password">Password</label>
        <input class="homey-form-input" id="password" type="password" value="" />
      </div>
    </fieldset>
    <p id="failed" class="error" data-i18n="pair.failed">Invalid credentials</p>

    <button id="save" class="homey-button-primary-full" data-i18n="settings.save">Save</button>
    <script type="text/javascript">
      const emailElement = document.getElementById('email');
      const passwordElement = document.getElementById('password');
      const errorElement = document.getElementById('failed');
      const saveElement = document.getElementById('save');
      errorElement.style.display = 'none';
        saveElement.addEventListener("click", function (e) {
          setSaving();
          const email = emailElement.value;
          const password = passwordElement.value;
          Homey.emit("login", { email: email, password: password  }).then(function (result) {
            if (result === false) {
              errorElement.style.display = 'block';
              setSave();
            }
          });
        });
        function setSave() {
          saveElement.className = 'homey-button-primary-full';
          saveElement.textContent = Homey.__('pair.save');
        }

        function setSaving() {
          saveElement.className = 'homey-button-primary-full is-loading';
          saveElement.textContent = Homey.__('pair.saving');
        }
    </script>
  </body>
</html>