Koenkk / zigbee2mqtt

Zigbee 🐝 to MQTT bridge 🌉, get rid of your proprietary Zigbee bridges 🔨
https://www.zigbee2mqtt.io
GNU General Public License v3.0
11.87k stars 1.65k forks source link

[New device support]: Aqara Cube T1 Pro support request #15652

Closed dabrahim closed 1 year ago

dabrahim commented 1 year ago

Link

https://www.aqara.com/en/product/cube-t1-pro

Database entry

{"id":5,"type":"EndDevice","ieeeAddr":"0x54ef44100062e05a","nwkAddr":25105,"manufId":4447,"manufName":"LUMI","powerSource":"Battery","modelId":"lumi.remote.cagl02","epList":[1,2,3],"endpoints":{"1":{"profId":260,"epId":1,"devId":259,"inClusterList":[0,3,6],"outClusterList":[0,3],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}},"2":{"epId":2,"inClusterList":[],"outClusterList":[],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}},"3":{"epId":3,"inClusterList":[],"outClusterList":[],"clusters":{},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":25,"stackVersion":2,"hwVersion":1,"dateCode":"20220602","swBuildId":"2019\u0000www.","zclVersion":3,"interviewCompleted":true,"meta":{},"lastSeen":1671404743897,"defaultSendRequestWhen":"immediate"}

Comments

I tried creating a custom converter using the one already exisiting for the older model MFKZQ01LM but I was stuck at some point, I didn't know how to properly fill the fromZigbee array in.

External converter

const fz = {...require('zigbee-herdsman-converters/converters/fromZigbee'), legacy: require('zigbee-herdsman-converters/lib/legacy').fromZigbee};
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;

const definition = {
    zigbeeModel: ['lumi.remote.cagl02'],
    model: 'CTP-R01',
    vendor: 'Xiaomi'
    description: 'Aqara Cube T1 Pro',
    fromZigbee: [fz.xiaomi_basic],
    toZigbee: [], // Should be empty, unless device can be controlled (e.g. lights, switches).
    exposes: [e.battery(), e.battery_voltage(), e.angle('action_angle'), e.device_temperature(), e.power_outage_count(false), e.cube_side('action_from_side'), e.cube_side('action_side'), e.cube_side('action_to_side'), e.cube_side('side'), e.action(['shake', 'wakeup', 'fall', 'tap', 'slide', 'flip180', 'flip90', 'rotate_left', 'rotate_right'])],
};

module.exports = definition;

Supported color modes

No response

Color temperature range

No response

martindell commented 1 year ago

+1 from me - just purchased a Cube T1 Pro, not realising it's not yet supported. I'm happy to help with figuring out how to integrate it - but creating a custom converter is beyond my (very limited) abilities. It's really difficult to test too, because some cube actions seem to trigger every zigbee device on the network

yschen5812 commented 1 year ago

+1!

dabrahim commented 1 year ago

+1 from me - just purchased a Cube T1 Pro, not realising it's not yet supported. I'm happy to help with figuring out how to integrate it - but creating a custom converter is beyond my (very limited) abilities. It's really difficult to test too, because some cube actions seem to trigger every zigbee device on the network

From what I understand, the most important parts are the "fromZigbee" key (to process the input) and the "exposes" key (to output the entities that are displayed on HA). On the older version, they've created custom methods for the zigbee input processing. Maybe taking inspiration from there could lead us somewhere.

Koenkk commented 1 year ago

What is logged to the debug log when triggering actions on this device?

See https://www.zigbee2mqtt.io/guide/usage/debug.html on how to enable debug logging.

martindell commented 1 year ago

I couldn't get it to log anything at all when I enabled debug logging - I'm bound to be doing something wrong. Part of the problem is that even without any external converter, the cube does weird 'stuff', messing with every other zigbee device - so there's a lot of noise to contend with. In response to @dabrahim 's comment - I played with the converter you specified at the top of this post, but couldn't get the device to show any values in Zigbee2MQTT

Screenshot 2022-12-29 at 10 36 47
JJPro commented 1 year ago

T1 Pro is a lot different than the classic cube. It can operate in two modes:

  1. Scene Mode (default mode, this is new)
    • rotate
    • shake
    • hold
    • side up (which side is facing up when put down)
    • 1 min inactivity
  2. Action Mode (default mode, works like the classic cube)
    • slide
    • rotate
    • tap
    • flip90, flip180
    • shake
    • 1 min inactivity (new feature, but I personally didn't see any responses after 1min delay, I suppose it has to be manually turned on via Aqara Home app)

~~I'm new to HA, and only get to know z2m literally two weeks ago. So totally novice on coding converters. But I fiddled with MFKZQ01LM's code a bit, and able to get shake, rotation, hold, and side up working. able to get action mode working (slide, rotate, tap, flip90, flip180, shake).~~

~~Those satisfies most of my needs. It'll be really awesome if tap and flip could work too, but that requires switching operation mode to Action Mode, and I can't figure out how, to be precise, I don't know what cluster attribute controls the operation mode.~~

~Looks like action mode is the default operation mode, I haven't figured out how to switch mode yet.~


UPDATE 1/3

Pressing the LINK/PAIRING button 5 times can toggle between the two modes.

Some folks reporting my converter code doesn't work, and they run into debug messages like this:

Debug 2023-01-03 21:52:45 - Received Zigbee message from '0x54ef44100062c386', type 'commandToggle', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0
Debug 2023-01-03 21:52:45 - No converter available for 'CTP-R01' with cluster 'genOnOff' and type 'commandToggle' and data '{}'

It never occurred to me, I guess it could be because the cube needs the Aqara Home app for initial configuration, and I happen to have an Aqara hub and played with that first before pairing to Z2M and coding my converter.

Anyways, Here's my code, feel free to take it:

/**
  # Two Modes

  ## Scene Mode
   - rotate
   - shake
   - hold
   - side up
   - trigger after one-min inactivity

  ## Action Mode
   - slide
   - rotate
   - tap twice
   - flip90, flip180
   - shake
   - trigger after one-min inactivity

  # Clusters (Scene Mode): 

  ## Endpoint 2: 

  | Cluster            | Data                      | Description                   |
  | ------------------ | ------------------------- | ----------------------------- |
  | aqaraopple         | {329: 0-5}                | i side facing up              |
  | genMultistateInput | {presentValue: 0}         | action: shake                 |
  | genMultistateInput | {presentValue: 4}         | action: hold                  |
  | genMultistateInput | {presentValue: 2}         | action: wakeup                |
  | genMultistateInput | {presentValue: 1024-1029} | action: fall with ith side up |

  ## Endpoint 3: 

  | Cluster   | Data                                  | Desc                                       |
  | --------- | ------------------------------------- | ------------------------------------------ |
  | genAnalog | {267: 500, 329: 3, presentValue: -51} | 267: NA, 329: side up, presentValue: angle |

 */

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const xiaomi = require('zigbee-herdsman-converters/lib/xiaomi');
const e = exposes.presets;
const ea = exposes.access;

const OPS_MODE = {
  ACTION: 0,
  SCENE: 1,
};
const ops_mode_lookup = { 0: 'action mode', 1: 'scene mode' };

const aqara_opple = {
  cluster: 'aqaraOpple',
  type: ['attributeReport', 'readResponse'],
  options: (definition) => [
    ...xiaomi.numericAttributes2Options(definition),
    exposes.enum('operation_mode', ea.STATE, ['scene mode', 'action mode']),
  ],
  convert: (model, msg, publish, options, meta) => {
    // console.log('>>>> ops mode', meta.state.operationMode);
    if (msg.data.hasOwnProperty('155') || msg.data.hasOwnProperty('328')) {
      const ops_mode = msg.data['155'] || msg.data['328'];
      meta.state.operationMode = ops_mode;
    }

    const operation_mode = ops_mode_lookup[meta.state.operationMode];

    return {
      ...xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data),
      operation_mode,
      action: 'side_up',
      side_up: msg.data['329'],
    };
  },
};

const action_multistate = {
  ...fz.MFKZQ01LM_action_multistate,
  convert: (model, msg, publish, options, meta) => {
    if (meta.state.operationMode === OPS_MODE.ACTION) {
      return fz.MFKZQ01LM_action_multistate.convert(
        model,
        msg,
        publish,
        options,
        meta
      );
    } else {
      const value = msg.data['presentValue'];
      let scene_action_multistate;
      if (value === 0) scene_action_multistate = { action: 'shake' };
      else if (value === 2) scene_action_multistate = { action: 'wakeup' };
      else if (value === 4) scene_action_multistate = { action: 'hold' };
      else if (value >= 1024)
        scene_action_multistate = { action: 'side_up', side_up: value - 1024 };

      return scene_action_multistate;
    }
  },
};

const definition = {
  zigbeeModel: ['lumi.remote.cagl02'],
  model: 'CTP-R01',
  vendor: 'Lumi',
  description: 'Aqara cube T1 Pro',
  meta: { battery: { voltageToPercentage: '3V_2850_3000' } },
  fromZigbee: [aqara_opple, action_multistate, fz.MFKZQ01LM_action_analog],
  toZigbee: [],
  exposes: [
    /* Device Info */
    e.battery(),
    e.battery_voltage(),
    e.device_temperature(),
    e.power_outage_count(false),
    exposes
      .enum('operation_mode', ea.STATE, ['scene mode', 'action mode'])
      .withDescription(
        'Press LINK button 5 times to toggle between action mode and scene mode'
      ),
    /* Actions */
    e.angle('action_angle'),
    e.cube_side('action_from_side'),
    e.cube_side('action_side'),
    e.cube_side('action_to_side'),
    e.cube_side('side').withDescription('Destination side of action'),
    e.cube_side('side_up').withDescription('Upfacing side of current scene'),
    e.action([
      'shake',
      'wakeup',
      'fall',
      'tap',
      'slide',
      'flip180',
      'flip90',
      'hold',
      'side_up',
      'rotate_left',
      'rotate_right',
    ]),
  ],
};

module.exports = definition;
filipeaparicio91 commented 1 year ago

I tried using the external converter code provided by @JJPro and although the cube is now correctly identified by Z2M (including sensor, configuration and diagnostic entities), they all report N/A or Unknown states. Not only that, but when I shake the cube, it toggles all of by Zigbee lights on/off, despite not having any automation set up.

JJPro commented 1 year ago

they all report N/A or Unknown states

Do you mean the following:

It's normal you see null values, cause they only contain values while in motion, and change very fast and won't get a chance to show in z2m frontend. If you take a look at the z2m logs or node-red debug console, you'll see their values being logged.

Not only that, but when I shake the cube, it toggles all of by Zigbee lights on/off, despite not having any automation set up.

I don't have this issue. Could it be the gateway you use? T1 Pro requires Zigbee 3.0. I'm using SONOFF Dongle Plus-E.

filipeaparicio91 commented 1 year ago

@JJPro, thank you for the reply.

My logs are not reporting any activity from the Cube T1 Pro.

As for the dongle, I'm using the SONOFF Dongle Plus-P, which supports Zigbee 3.0 as well.

The configuration is correct, so I'm not really sure why this is happening.

sjohannsen1 commented 1 year ago

I have a similar problem. The only activity i see in my logs is from endpoint 1 cluster "genOnOff". These messages are empty but are toggling all of my devices on/off, despite having no automation set up.

JJPro commented 1 year ago

@filipeaparicio91 @sjohannsen1

I've updated code in my original comment.

Last night, I reset my cube and found all my actions failing to work. Then I figured the cube is now in action mode (which works exactly like the classic cube).

Most likely during my last experiment, I have switched its operation mode to scene mode in Aqara Home app before pairing to Z2M, and the mode was saved. I wasn't aware of the situation, and assumed that to be the default operation mode.

Now the code has been updated, it should pull data in correctly, and work like the classic cube. I still haven't figured how to switch between modes yet.


For your issue randomly toggling other devices on/off, it yet haven't occurred to me. I have a hypothesis, could it be like the cube is paired to a router instead of the coordinator? and that router is not fully compatible with zigbee 3.0? I'm still new to HA and Z2M, just random thoughts. Then I suggest try repairing it directly to your main coordinator. (See Map view in Z2M frontend to confirm)

image
filipeaparicio91 commented 1 year ago

@JJPro, I tried your updated code and the behavior is the same. I was able to retrieve some debug log entries from Z2M, which say this:

Debug 2023-01-03 21:52:45 - Received Zigbee message from '0x54ef44100062c386', type 'commandToggle', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0
Debug 2023-01-03 21:52:45 - No converter available for 'CTP-R01' with cluster 'genOnOff' and type 'commandToggle' and data '{}'

I keep getting these entries when I shake the cube and occasionally when I rotate it on a random side.

I continue not getting any values reported in Z2M, even in the info logs.

As for the connection, it is currently connected through a router (which supports Zigbee 3.0), but the same behavior was observed when connected directly to the coordinator.

jzee923 commented 1 year ago

I have a similar problem. The only activity i see in my logs is from endpoint 1 cluster "genOnOff". These messages are empty but are toggling all of my devices on/off, despite having no automation set up.

I'm having this exact same issue.

I have noticed this behavior Flip: Received Zigbee message from '0x54ef4410006a47c0', type 'commandOn', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0

Rotate: Received Zigbee message from '0x54ef4410006a47c0', type 'commandOff', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0

Tap: Received Zigbee message from '0x54ef4410006a47c0', type 'commandToggle', cluster 'genOnOff', data '{}' from endpoint 1 with groupID 0

Attempt to switch modes by pressing the reset button 5x: Received Zigbee message from '0x54ef4410006a47c0', type 'attributeReport', cluster 'aqaraOpple', data '{"328":0}' from endpoint 1 with groupID 0 This last one goes back and forth from data '{"328":0}' to data '{"328":1}'

JJPro commented 1 year ago

Hey, I updated my code again in original comment, Now it's working on both scene mode and action mode. Pressing the LINK button 5 times to toggle between them.

I never saw the genOnOff/commandToggle message probably because the cube needs the Aqara Home app for initial configuration, and I happen to have an Aqara hub and played with that first before pairing to Z2M and started coding my converter. I have no idea.

I only have one cube and can't start from fresh to cover the genOnOff cluster personally. I don't see an option to reset the cube within Aqara Home app. and long pressing the LINK button and repairing doesn't give me that message either.

jzee923 commented 1 year ago

So your comment about the device being configured when you first connected it to the aqara hub got me thinking and I think I'm onto something. I added a configure: section

Here's a snippet from your code with the added configure section

const definition = {
    zigbeeModel: ['lumi.remote.cagl02'],
    model: 'CTP-R01',
    vendor: 'Lumi',
    description: 'Aqara cube T1 Pro',
    meta: { battery: { voltageToPercentage: '3V_2850_3000' } },
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await endpoint.write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f});
        await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff','genPowerCfg','genMultistateInput']);
    },
    fromZigbee: [aqara_opple, action_multistate, fz.MFKZQ01LM_action_analog],

I'm not sure if this is complete and fully configured but I am finally starting to see the proper actions.

2023-01-04 01:00:02Received Zigbee message from '0x54ef4410006a47c0', type 'attributeReport', cluster 'genAnalogInput', data '{"267":500,"329":2,"presentValue":-58.40999984741211}' from endpoint 3 with groupID 0
Info 2023-01-04 01:00:02MQTT publish: topic 'zigbee2mqtt/0x54ef4410006a47c0', payload '{"action":"rotate_left","action_angle":-58.41,"action_from_side":null,"action_side":null,"action_to_side":null,"battery":null,"device_temperature":null,"linkquality":15,"operationMode":1,"operation_mode":"scene mode","power_outage_count":null,"side":null,"side_up":2,"voltage":null}'
Info 2023-01-04 01:00:02MQTT publish: topic 'zigbee2mqtt/0x54ef4410006a47c0', payload '{"action":"","action_angle":null,"action_from_side":null,"action_side":null,"action_to_side":null,"battery":null,"device_temperature":null,"linkquality":15,"operationMode":1,"operation_mode":"scene mode","power_outage_count":null,"side":null,"side_up":2,"voltage":null}'
Info 2023-01-04 01:00:02MQTT publish: topic 'zigbee2mqtt/0x54ef4410006a47c0/action', payload 'rotate_left'

I also needed to comment out these lines (55 and 68): ...xiaomi.numericAttributes2Options(definition), ...xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data),

filipeaparicio91 commented 1 year ago

@jzee923 & @JJPro

You two are getting somewhere! Adding the configure block at first gave me a configuration error, but after switching between action and scene modes a few times, if correctly configured the cube and started reporting payloads correctly.

Commenting lines 55 & 68 was not required for my cube to start behaving correctly.

I have not tried any automation yet, but the action mode is showing some inconsistent behaviours. For example, occasionally it becomes stuck on one of the sides and does not update until a rotate or shake action happens. Using the scene mode is much more reliable, and accurately reports the side facing up every time.

Z2M logs also report rotate_left and rotate_right in both scene and action mode, but the HA action sensor only reports rotation when the cube is set to action mode. EDIT: After changing modes back and forth, HA is now picking up rotation for scenes as well! I'm going to start testing some automations.

Nevertheless, this is a massive improvement and I can finally start testing the cube without having my lights flashing all the time.

sjohannsen1 commented 1 year ago

Thank you @JJPro & @jzee923! The cube is finally reporting correctly with the addition of your code. Just like @filipeaparicio91 has commented, I got a configuration error that could be resolved by changing the mode a few times. Both modes behave consistently. However, I noticed that the reported side was always off. After looking into the old "action_multistate"- converter, i noticed that assignment of numbers to sides has changed. While the old cube was numbered 0-5, the new one is numbered 1-6.

        Source: https://github.com/kirovilya/ioBroker.zigbee
            +---+
            | 3 |
        +---+---+---+
        | 5 | 1 | 2 |
        +---+---+---+
            | 4 |
            +---+
            | 6 |
            +---+
        Side 1 is with the MI logo, side 6 contains the battery door.

adapted from "zigbee-herdsman-converters/converters/fromZigbee"

I added:

const utils = require('zigbee-herdsman-converters/lib/utils');

and changed @JJPro converters by adding the old converter with updated sides:

const aqara_opple = {
    cluster: 'aqaraOpple',
    type: ['attributeReport', 'readResponse'],
    options: (definition) => [
      ...xiaomi.numericAttributes2Options(definition),
      exposes.enum('operation_mode', ea.STATE, ['scene mode', 'action mode']),
    ],
    convert: (model, msg, publish, options, meta) => {

      if (msg.data.hasOwnProperty('155') || msg.data.hasOwnProperty('328')) {
        const ops_mode = msg.data['155'] || msg.data['328'];
        meta.state.operationMode = ops_mode;
      };

      const operation_mode = ops_mode_lookup[meta.state.operationMode];

      return {
        ...xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data),
        operation_mode,
        action: 'side_up',
        side_up: msg.data['329']+1,
      };
    },
  };

const action_multistate = {
  cluster: 'genMultistateInput',
  type: ['attributeReport', 'readResponse'],
  options: [exposes.options.legacy()],
  convert: (model, msg, publish, options, meta) => {
    const value = msg.data['presentValue'];
    let result;
    if (value === 0) result = { action: 'shake' };
    else if (value === 2) result = { action: 'wakeup' };
    else if (value === 4) result = { action: 'hold' };
    else if (value >= 512) result = { action: 'tap', side: value - 511 };
    else if (value >= 256) result = { action: 'slide', side: value - 255 };
    else if (value >= 128) result = { action: 'flip180', side: value - 127 };
    else if (value >= 64) result = { action: 'flip90', action_from_side: Math.floor((value - 64) / 8) + 1, action_to_side: (value % 8) + 1, action_side: (value % 8) + 1, from_side: Math.floor((value - 64) / 8) + 1, to_side: (value % 8) + 1, side: (value % 8) + 1 };
    else if (value >= 1024) result = { action: 'side_up', side_up: value - 1023 };
    if (result && !utils.isLegacyEnabled(options)) { delete result.to_side, delete result.from_side };
    return result ? result : null;
  },
};

Now everything works consistently and as it should for me. The only thing missing is a way to change modes without accessing the reset button.

filipeaparicio91 commented 1 year ago

@sjohannsen1 this works!

I had to reformat some of the else statements as Z2M was not starting, and it's now reporting the correct side every time.

Here is the full working code:

/**
  # Two Modes

  ## Scene Mode
    - rotate
    - shake
    - hold
    - side up
    - trigger after one-min inactivity

  ## Action Mode
    - slide
    - rotate
    - tap twice
    - flip90, flip180
    - shake
    - trigger after one-min inactivity

  # Clusters (Scene Mode): 

  ## Endpoint 2: 

  | Cluster            | Data                      | Description                   |
  | ------------------ | ------------------------- | ----------------------------- |
  | aqaraopple         | {329: 0-5}                | i side facing up              |
  | genMultistateInput | {presentValue: 0}         | action: shake                 |
  | genMultistateInput | {presentValue: 4}         | action: hold                  |
  | genMultistateInput | {presentValue: 2}         | action: wakeup                |
  | genMultistateInput | {presentValue: 1024-1029} | action: fall with ith side up |

  ## Endpoint 3: 

  | Cluster   | Data                                  | Desc                                       |
  | --------- | ------------------------------------- | ------------------------------------------ |
  | genAnalog | {267: 500, 329: 3, presentValue: -51} | 267: NA, 329: side up, presentValue: angle |

 */

  const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
  const exposes = require('zigbee-herdsman-converters/lib/exposes');
  const xiaomi = require('zigbee-herdsman-converters/lib/xiaomi');
  const utils = require('zigbee-herdsman-converters/lib/utils');
  const e = exposes.presets;
  const ea = exposes.access;

  const OPS_MODE = {
    ACTION: 0,
    SCENE: 1,
  };
  const ops_mode_lookup = { 0: 'action mode', 1: 'scene mode' };

  const aqara_opple = {
    cluster: 'aqaraOpple',
    type: ['attributeReport', 'readResponse'],
    options: (definition) => [
      ...xiaomi.numericAttributes2Options(definition),
      exposes.enum('operation_mode', ea.STATE, ['scene mode', 'action mode']),
    ],
    convert: (model, msg, publish, options, meta) => {

      if (msg.data.hasOwnProperty('155') || msg.data.hasOwnProperty('328')) {
        const ops_mode = msg.data['155'] || msg.data['328'];
        meta.state.operationMode = ops_mode;
      };

      const operation_mode = ops_mode_lookup[meta.state.operationMode];

      return {
        ...xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data),
        operation_mode,
        action: 'side_up',
        side_up: msg.data['329']+1,
      };
    },
  };

  const action_multistate = {
    cluster: 'genMultistateInput',
    type: ['attributeReport', 'readResponse'],
    options: [exposes.options.legacy()],
    convert: (model, msg, publish, options, meta) => {
        const value = msg.data['presentValue'];
        let result;
        if (value === 0) result = { action: 'shake' };
        else if (value === 2) result = { action: 'wakeup' };
        else if (value === 4) result = { action: 'hold' };
        else if (value >= 512) result = {action: 'tap', side: value-511};
        else if (value >= 256) result = {action: 'slide', side: value-255};
        else if (value >= 128) result = {action: 'flip180', side: value-127};
        else if (value >= 64) result = {action: 'flip90', action_from_side: Math.floor((value-64) / 8)+1, action_to_side: (value % 8)+1, action_side: (value % 8)+1, from_side: Math.floor((value-64) / 8)+1, to_side: (value % 8)+1, side: (value % 8)+1};
        else if (value >= 1024) result = { action: 'side_up', side_up: value - 1023 };
        if (result && !utils.isLegacyEnabled(options)) {delete result.to_side, delete result.from_side};
      return result ? result : null;
    },
  };

  const definition = {
    zigbeeModel: ['lumi.remote.cagl02'],
    model: 'CTP-R01',
    vendor: 'Lumi',
    description: 'Aqara cube T1 Pro',
    meta: { battery: { voltageToPercentage: '3V_2850_3000' } },
    configure: async (device, coordinatorEndpoint, logger) => {
      const endpoint = device.getEndpoint(1);
      await endpoint.write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f});
      await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff','genPowerCfg','genMultistateInput']);
    },
    fromZigbee: [aqara_opple, action_multistate, fz.MFKZQ01LM_action_analog],
    toZigbee: [],
    exposes: [
      /* Device Info */
      e.battery(),
      e.battery_voltage(),
      e.device_temperature(),
      e.power_outage_count(false),
      exposes
        .enum('operation_mode', ea.STATE, ['scene mode', 'action mode'])
        .withDescription(
          'Press LINK button 5 times to toggle between action mode and scene mode'
        ),
      /* Actions */
      e.angle('action_angle'),
      e.cube_side('action_from_side'),
      e.cube_side('action_side'),
      e.cube_side('action_to_side'),
      e.cube_side('side').withDescription('Destination side of action'),
      e.cube_side('side_up').withDescription('Upfacing side of current scene'),
      e.action([
        'shake',
        'wakeup',
        'fall',
        'tap',
        'slide',
        'flip180',
        'flip90',
        'hold',
        'side_up',
        'rotate_left',
        'rotate_right',
      ]),
    ],
  };

  module.exports = definition;

EDIT: I just tried automating the cube while in "scene mode" and everything runs smoothly. Using MQTT as the trigger to listen to "zigbee2mqtt/[YOUR_CUBE_NAME]/action", I can choose between "tap", "side_up", "rotate_left", "rotate_right", "hold", "shake" and "wakeup" payloads in combination with the "Side up" sensor. I have not tried doing any automations using the "action mode" yet, but I assume it will work as well.

As previously mentioned, I think the last missing piece is being able to change the Operation mode directly on HA or Z2M without having to open the cube.

jzee923 commented 1 year ago

As previously mentioned, I think the last missing piece is being able to change the Operation mode directly on HA or Z2M without having to open the cube.

I'm fairly certain we need to create or use an existing converter in toZigbee in order for this to work. I tried a few things but have made no progress. I'm not entirely sure where to go for this one.

JJPro commented 1 year ago

@CrazyCoder no, ... is spread operator in JS.

dabrahim commented 1 year ago

Thank you all for your support ! I was finally able to set up the cube and trigger a few events ! The only event I'm failing to trigger so far is the "double tap" one. Aside from that everything works flawlessly on my side.

jzee923 commented 1 year ago

Been working on getting a toZigbee converter working in order to change the operating mode in the z2m ui.

image

I was able to get it showing up as a toggle button but it doesn't actually function

const tzLocal = {
    MFCZQ12LM_scene_mode: {
        key: ['operation_mode'],
        convertSet: async (entity, key, value, meta) => {
            // Support existing syntax of a nested object just for the state field. Though it's quite silly IMO.
            const targetValue = value.hasOwnProperty('state') ? value.state : value;
            // Switches using aqaraOpple 0x0200 on the same endpoints as the onOff clusters.
            const lookupState = {"action": 0, "scene": 1};
            await entity.write('aqaraOpple', {0x0148: {value: lookupState[targetValue], type: 0x20}}, {manufacturerCode: 0x115f});
        },
        convertGet: async (entity, key, meta) => {
            await entity.read('aqaraOpple', [0x0148], {manufacturerCode: 0x115f});
        },
    },
}

I'm having issues with this line here await entity.write('aqaraOpple', {0x0148: {value: lookupState[targetValue], type: 0x20}}, {manufacturerCode: 0x115f});

If anyone has a sniffer they can use to get the correct values, I think we'll be one step closer to a fully functional device

I've also cleaned up the code a bit so that it better matches the samples provided in the docs. Most of this has just been a lot of trial and error so i'm not sure if everything is correct.

/**
# Two Modes

## Scene Mode
    - rotate
    - shake
    - hold
    - side up
    - trigger after one-min inactivity

## Action Mode
    - slide
    - rotate
    - tap twice
    - flip90, flip180
    - shake
    - trigger after one-min inactivity

# Clusters (Scene Mode): 

## Endpoint 2: 

| Cluster            | Data                      | Description                   |
| ------------------ | ------------------------- | ----------------------------- |
| aqaraopple         | {329: 0-5}                | i side facing up              |
| genMultistateInput | {presentValue: 0}         | action: shake                 |
| genMultistateInput | {presentValue: 4}         | action: hold                  |
| genMultistateInput | {presentValue: 2}         | action: wakeup                |
| genMultistateInput | {presentValue: 1024-1029} | action: fall with ith side up |

## Endpoint 3: 

| Cluster   | Data                                  | Desc                                       |
| --------- | ------------------------------------- | ------------------------------------------ |
| genAnalog | {267: 500, 329: 3, presentValue: -51} | 267: NA, 329: side up, presentValue: angle |

*/

const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const xiaomi = require('zigbee-herdsman-converters/lib/xiaomi');
const e = exposes.presets;
const ea = exposes.access;

const fzLocal = {
    MFCZQ12LM_action_multistate: {
        cluster: 'genMultistateInput',
        type: ['attributeReport', 'readResponse'],
        options: [exposes.options.legacy()],
        convert: (model, msg, publish, options, meta) => {
            /*
            Source: https://github.com/kirovilya/ioBroker.zigbee
                +---+
                | 4 |
            +---+---+---+
            | 3 | 0 | 2 |
            +---+---+---+
                | 1 |
                +---+
                | 5 |
                +---+
            Side 0 is with the Aqara logo, side 5 contains the battery door.
            presentValue = 0 = shake
            presentValue = 2 = wakeup
            presentValue = 3 = fly/fall
            presentValue = y + x * 8 + 64 = 90º Flip from side x on top to side y on top
            presentValue = x + 128 = 180º flip to side x on top
            presentValue = x + 256 = push/slide cube while side x is on top
            presentValue = x + 512 = double tap while side x is on top
            */
            const value = msg.data['presentValue'];
            let result = null;
            if (meta.state.operation_mode === "action") {
                if (value === 0) result = {action: 'shake'};
                else if (value === 2) result = {action: 'wakeup'};
                else if (value === 3) result = {action: 'fall'};
                else if (value >= 512) result = {action: 'tap', side: value-512+1};
                else if (value >= 256) result = {action: 'slide', side: value-256+1};
                else if (value >= 128) result = {action: 'flip180', side: value-128+1};
                else if (value >= 64) {
                    result = {
                        action: 'flip90', 
                        action_from_side: Math.floor((value-64) / 8) + 1, 
                        action_to_side: (value % 8) + 1, 
                        action_side: (value % 8) + 1,
                        from_side: Math.floor((value-64) / 8) + 1, 
                        to_side: (value % 8) + 1, 
                        side: (value % 8) + 1,
                    };
                }

                return result ? result : null;
            } else {
                if (value === 0) result = { action: 'shake' };
                else if (value === 2) result = { action: 'wakeup' };
                else if (value === 4) result = { action: 'hold' };
                else if (value >= 1024)
                    result = { action: 'side_up', side_up: value - 1024 + 1 };

                return result;
            }
        },
    },
    aqara_opple: {
        cluster: 'aqaraOpple',
        type: ['attributeReport', 'readResponse'],
        options: xiaomi.numericAttributes2Options,
        convert: (model, msg, publish, options, meta) => {
            let payload = {};
            payload = xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data);
            if( msg.data.hasOwnProperty(328) ){
                const opmode = {0: "action", 1: "scene"}
                payload.operation_mode = opmode[msg.data[328]]
            }
            if( msg.data.hasOwnProperty(329) ){
                payload.side = msg.data[329] + 1;
            }
            return payload;
        },
    },
}

const tzLocal = {
    MFCZQ12LM_scene_mode: {
        key: ['operation_mode'],
        convertSet: async (entity, key, value, meta) => {
            // Support existing syntax of a nested object just for the state field. Though it's quite silly IMO.
            const targetValue = value.hasOwnProperty('state') ? value.state : value;
            // Switches using aqaraOpple 0x0200 on the same endpoints as the onOff clusters.
            const lookupState = {"action": 0, "scene": 1};
            await entity.write('aqaraOpple', {0x0148: {value: lookupState[targetValue], type: 0x20}}, {manufacturerCode: 0x115f});
        },
        convertGet: async (entity, key, meta) => {
            await entity.read('aqaraOpple', [0x0148], {manufacturerCode: 0x115f});
        },
    },
}

const definition = {
    zigbeeModel: ['lumi.remote.cagl02'],
    model: 'MFCZQ12LM',
    vendor: 'Xiaomi',
    description: 'Aqara Cube T1 Pro',
    meta: { battery: { voltageToPercentage: '3V_2850_3000' } },
    configure: async (device, coordinatorEndpoint, logger) => {
        const endpoint = device.getEndpoint(1);
        await endpoint.write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f});
        await reporting.bind(endpoint, coordinatorEndpoint, ['genBasic','genOnOff','genPowerCfg','genMultistateInput']);
        await endpoint.read('genPowerCfg', ['batteryVoltage']);
        await endpoint.read('aqaraOpple', [0x0148], {manufacturerCode: 0x115f});
        await endpoint.read('aqaraOpple', [0x0149], {manufacturerCode: 0x115f});
    },
    fromZigbee: [fz.xiaomi_basic,fzLocal.aqara_opple, fzLocal.MFCZQ12LM_action_multistate, fz.MFKZQ01LM_action_analog],
    toZigbee: [tzLocal.MFCZQ12LM_scene_mode],
    exposes: [
        /* Device Info */
        e.battery(),
        e.battery_voltage(),
        e.device_temperature(),
        e.power_outage_count(false),
        exposes
            .enum('operation_mode', ea.STATE_SET, ['scene', 'action'])
            .withDescription(
                'Press LINK button 5 times to toggle between action mode and scene mode'
            ),
        /* Actions */
        e.angle('action_angle'),
        e.cube_side('action_from_side'),
        e.cube_side('action_side'),
        e.cube_side('action_to_side'),
        e.cube_side('side').withDescription('Destination side of action'),
        e.cube_side('side_up').withDescription('Upfacing side of current scene'),
        e.action([
            'shake',
            'wakeup',
            'fall',
            'tap',
            'slide',
            'flip180',
            'flip90',
            'hold',
            'side_up',
            'rotate_left',
            'rotate_right',
        ]),
    ],
};

module.exports = definition;
JJPro commented 1 year ago

@jzee923

await entity.write('aqaraOpple', {0x0148: {value: lookupState[targetValue], type: 0x20}}, {manufacturerCode: 0x115f});

How do you find the data points (0x0148, 0x20) to overwrite? I'm still new to z2m, there's a lot to learn. Thanks~

jzee923 commented 1 year ago

0x0148 is just 328 in hex. (the value we were seeing for changing modes) I'm brand new to this too so I've just been fumbling through all of this. I took a look at the logs when I set the mode using the 5 clicks.

zigbee-herdsman:controller:log Received 'zcl' data '{"frame":{"Header":{"frameControl":{"frameType":0,"manufacturerSpecific":false,"direction":1,"disableDefaultResponse":true,"reservedBits":0},"transactionSequenceNumber":60,"manufacturerCode":null,"commandIdentifier":10},"Payload":[{"attrId":328,"dataType":32,"attrData":0}],"Command":{"ID":10,"name":"report","parameters":[{"name":"attrId","type":33},{"name":"dataType","type":32},{"name":"attrData","type":1000}]}},"address":13477,"endpoint":1,"linkquality":36,"groupID":0,"wasBroadcast":false,"destinationEndpoint":1}'

I saw this in there and just gave it a shot "Payload":[{"attrId":328,"dataType":32,"attrData":0}],

So datatype: 32 translates to 0x20 in hex. But this wasn't working. I'm not sure how to get these values without a sniffer.

Ekinox09 commented 1 year ago

Hi, thanks all for your great job. I've started a thread (#15956) yesterday for this device but your job is much more advanced. I've tested your code on a external converter and it works perfectly (plugin zigbee2MQTT on Jeedom software). Here are my suggestion and observation:

Ekinox09 commented 1 year ago
            //if( msg.data.hasOwnProperty(329) ){
                //payload.side = msg.data[329] + 1;
            //}

I would suggest not to comment this part (or to rework it) because , for me, this "side_up" information is the most reliable on in the "scene mode".

jzee923 commented 1 year ago

That data is duplicated in aqaraOpple and the multistate. I'm not sure why, but that's the reason i commented out the aqaraopple one.

jzee923 commented 1 year ago

The sides are all adjusted so that they should report properly. I only labeled it as 0-5 in the comments so that the math in the documentation worked out properly. Take note that technically in "action" mode the cube doesn't support "side_up" and the original cube converter uses "side" for this value along with the flip90 or flip180 actions

JJPro commented 1 year ago

That data is duplicated in aqaraOpple and the multistate. I'm not sure why, but that's the reason i commented out the aqaraopple one.

aqaraOpple is mainly for accurately detecting side_up in scene mode when there is no obvious action. E.g. when you hold the cube in hand for a while and then rest it down on table super super gently. In this case, aqaraOpple cluster value will change, but multistate won't. While multistate is for situation there is noticible action (fliping, put down with noticible movement).

In fact, hold for a while and rest down gently is not considered an action for the old cube either, and it won't detect it if you e.g flip 180 this way.

To me, cluster multistate is carried over from classic cube, and cluster aqaraOpple is specifically created for scene mode.

My experiment tells me that aqaraOpple and multistate clusters are from different sensors inside, aqaraOpple is a bit (like half second) slower than multistate in reporting side_up, but more accurately.

That's why I kept side_up action detection code in both places, they are not duplicate. multistate will report a result immediately, and aqaraOpple will report soon after it. This is very helpful in case multistate fails to report a change, as in the gently rest situation.

jzee923 commented 1 year ago

Ok yeah that makes a lot of sense.

JJPro commented 1 year ago

@Ekinox09 You can try my original converter code above Others' code are improvements upon that.

It has been been working flawlessly for me, and I barely experienced missed actions.

The only thing missing in my code is the configuration part. But since your cube is already set up and working, it doesn't need the initial configuration process any more, so should be working fine without it.

Give it a try.

Ekinox09 commented 1 year ago

@JJPro

JJPro commented 1 year ago

@Ekinox09

After some tests, I miss some actions if I handle the cude "too gently" (which can happen if you look few seconds to the face you want to have on, and land the device softly).

That's normal in action mode, the classic cube behaves the same way. Gentle movement won't be treated as a valid action. I think that's by design, in order to avoid false positives when people are having fun fiddling with the cube in hand. Yeah I am this person like to hold my cube from time to time for fun and dont want it to trigger anything. haha. If I indeed want to trigger an action I put it down with noticeable movement; otherwise I put it down gently.

That's for action mode.

As for scene mode, the purpose of scene mode is to simply tell which side is up and you keep it that way to stay with a specific scene (scene as in HomeKit or HA), so the side_up is always accurate, even with super subtle resting move.

JJPro commented 1 year ago

BTW, I didn't integrate other's configuration code to my converter, simply because I don't understand that code. Like what it's doing, why the binding with the coordinator, ...stuff like that.

I've been learning about zigbee protocol recently.

jzee923 commented 1 year ago

Yeah, that was stuff I was trying in order to get my cube configured properly. Without pairing to an aqara hub like you did the cube was not behaving properly. It feels like we've gotten about as far as we can unless someone with a sniffer can come and help us with all the proper data. Like i said before, most of this was trial and error. The documentation is pretty lacking on what all of this does.

Ekinox09 commented 1 year ago

Without pairing to an aqara hub like you did the cube was not behaving properly.

Hello, I don't have any hub. I'm using the cube with an USB Conbee II controller on a debian VM. I do my test "directly" with Z2M in debug mode; once everything's OK, I use the config files it with my domotic software (Jeedom with zigbee2MQTT plugin). And, without any hub, the cube behave perfectly for me (now that I've undestood that action_mode need some "rough" movements to be triggered). I noticed that after a pairing, I have some strange behaviour; switching from one mode to another (5 clics) makes everything right. Hope it helps.

jzee923 commented 1 year ago

I was referring to this issue that a few of us were having. I outlined the data I was getting here before the cube was properly configured. I'm not sure how you got past this but I had to add a configure section in my definitions in order to get the cube to configure and start sending the correct actions. Part of trying to get this working was binding some of the clusters. And again, I'm not sure which of these are actually required, but it was the first step required for me to get the cube working. So basically, I wasn't able to use @JJPro's code. I have everything working with my config (i think) except being able to change the operation mode in the z2m gui. It seems like it should work but it keeps timing out.

Ekinox09 commented 1 year ago

Hello all, I would suggest not to update the "action" with "side_up" in the scene mode. Using the cube in the scene mode, my final trigger is the side change (dedicated message sent after all movements done, very reliable). During the cube movements, "action" can switch to "hold" or "shake" BUT the content of this action is systematically replaced by "side_up" once the cube is back to calm. I would be interested to let this "action" set to "shake", "hold" in order to be able to have specific action depending on the final face (shake + final face 6 = 1 scenario, etc....). I have changed the code for my own usage but this might be helpful for other (will multiplicate the actions "hold" and "shake" usage by 6). Let me know your point of view regarding this. Bye. [UPDATE] In the scene mode, the default action should be "side_up" except if an "hold" or "shake" movement is detected, in my opinion. I know that these info comes from 2 different cluster but that would be great to implement it.

jzee923 commented 1 year ago

This sounds like a perfect use case for the aqaraOpple cluster 329. It reports back which side is up regardless of your operation mode. I was worried that this would cause conflicts with the existing "side" or "side_up" which is why I had originally commented it out. Right now in my code it updates "side" Following your example I pick up the cube with side 6 on top, I shake it, I place it down with side 3 facing up.

Received Zigbee message from 'Cube', type 'attributeReport', cluster 'genMultistateInput', data '{"presentValue":0}' from endpoint 2 with groupID 0
Info 2023-01-06 13:35:13MQTT publish: topic 'zigbee2mqtt/Cube', payload '{"action":"shake","action_angle":null,"action_from_side":null,"action_side":null,"action_to_side":null,"battery":100,"current":0,"device_temperature":25,"linkquality":42,"operation_mode":"scene","power":0,"power_outage_count":57,"side":6,"side_up":6,"voltage":3000}'
Info 2023-01-06 13:35:13MQTT publish: topic 'zigbee2mqtt/Cube', payload '{"action":"","action_angle":null,"action_from_side":null,"action_side":null,"action_to_side":null,"battery":100,"current":0,"device_temperature":25,"linkquality":42,"operation_mode":"scene","power":0,"power_outage_count":57,"side":6,"side_up":6,"voltage":3000}'
Info 2023-01-06 13:35:13MQTT publish: topic 'zigbee2mqtt/Cube/action', payload 'shake'
Debug 2023-01-06 13:35:14Received Zigbee message from 'Cube', type 'attributeReport', cluster 'aqaraOpple', data '{"329":2}' from endpoint 2 with groupID 0
Debug 2023-01-06 13:35:14lumi.remote.cagl02: unknown key 329 with value 2
Debug 2023-01-06 13:35:14lumi.remote.cagl02: Processed data into payload {}
Info 2023-01-06 13:35:14MQTT publish: topic 'zigbee2mqtt/Cube', payload '{"action":null,"action_angle":null,"action_from_side":null,"action_side":null,"action_to_side":null,"battery":100,"current":0,"device_temperature":25,"linkquality":42,"operation_mode":"scene","power":0,"power_outage_count":57,"side":3,"side_up":6,"voltage":3000}'

So if you look at my logs, It receives the shake action and starts with side:6 and side_up:6 Then, the aqaraOpple cluster reports in with side 3 and the final report is side:3 and side_up: 6 since we never triggered a side_up action.

My aqaraOpple cluster is a bit different than @JJPro 's. Mine is not reporting/overwriting an action, it's only returning the value of the side facing up on the cube.

Ekinox09 commented 1 year ago

Excellent ! For my need, I just commented the line "action: 'side_up'" in the "aqaraOpple" cluster. That do the trick. "action" property is only updated by the "action_multistate" cluster with "side_up, hold or shake" (and "rotate" with the formal analog cluster) and "aqaraOpple" cluster only update "side_up" property with the final side up.

jzee923 commented 1 year ago

With the inclusion of the face up side value in aqaraOpple on the T1 pro cube, it makes me think all the "math" we have to determine "side" in the multiStateInput is redundant...

OstfrieseUnterwegs commented 1 year ago

Does anyone else get the error

Failed to configure 'CubeT1', attempt 1 (Error: Write 0x54ef44100062cf1e/1 aqaraOpple({"mode":1}, {"sendWhen":"immediate","timeout":10000,"disableResponse":false,"disableRecovery":false,"disableDefaultResponse":true,"direction":0,"srcEndpoint":null,"reservedBits":0,"manufacturerCode":4447,"transactionSequenceNumber":null,"writeUndiv":false}) failed (no response received) at DeconzAdapter.sendZclFrameToEndpoint (/opt/zigbee2mqtt/node_modules/zigbee-herdsman/src/adapter/deconz/adapter/deconzAdapter.ts:658:23) at runNextTicks (node:internal/process/task_queues:60:5) at processTimers (node:internal/timers:504:9) at Endpoint.sendRequest (/opt/zigbee2mqtt/node_modules/zigbee-herdsman/src/controller/model/endpoint.ts:299:20) at Endpoint.write (/opt/zigbee2mqtt/node_modules/zigbee-herdsman/src/controller/model/endpoint.ts:389:28) at Object.configure (/opt/zigbee2mqtt/data/extension/externally-loaded.js:150:9) at Configure.configure (/opt/zigbee2mqtt/lib/extension/configure.ts:117:13))

using a conbee2 USB stick The Cube is working OK, but an error always makes me a bit nervous.

filipeaparicio91 commented 1 year ago

@OstfrieseUnterwegs, I had the same issue and was able to get the Cube configured by toggling between modes (scene and action), which can be done by clicking the reset button 5 times. Doing this a few times allowed the Cube to configure correctly.

I am using a Sonoff router though, so I'm not sure if the process is the same with a Conbee2.

smartmatic commented 1 year ago

@OstfrieseUnterwegs, I had the same issue and was able to get the Cube configured by toggling between modes (scene and action), which can be done by clicking the reset button 5 times. Doing this a few times allowed the Cube to configure correctly.

I am using a Sonoff router though, so I'm not sure if the process is the same with a Conbee2.

This is possible with version 1.29.1-1 or is dev version needed?

filipeaparicio91 commented 1 year ago

@smartmatic, it should work with any version of Z2M. You just need to create an external converter for the cube and add it to your Z2M configuration file.

Here is the complete setup with the converter that I'm using:

1. Create a new JS file under config > zigbee2mqtt (ex: MFCZQ12LM.js) and paste the following code:

/**
  # Two Modes

  ## Scene Mode
    - rotate
    - shake
    - hold
    - side up
    - trigger after one-min inactivity

  ## Action Mode
    - slide
    - rotate
    - tap twice
    - flip90, flip180
    - shake
    - trigger after one-min inactivity

  # Clusters (Scene Mode): 

  ## Endpoint 2: 

  | Cluster            | Data                      | Description                   |
  | ------------------ | ------------------------- | ----------------------------- |
  | aqaraopple         | {329: 0-5}                | i side facing up              |
  | genMultistateInput | {presentValue: 0}         | action: shake                 |
  | genMultistateInput | {presentValue: 4}         | action: hold                  |
  | genMultistateInput | {presentValue: 2}         | action: wakeup                |
  | genMultistateInput | {presentValue: 1024-1029} | action: fall with ith side up |

  ## Endpoint 3: 

  | Cluster   | Data                                  | Desc                                       |
  | --------- | ------------------------------------- | ------------------------------------------ |
  | genAnalog | {267: 500, 329: 3, presentValue: -51} | 267: NA, 329: side up, presentValue: angle |

 */

  const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
  const exposes = require('zigbee-herdsman-converters/lib/exposes');
  const xiaomi = require('zigbee-herdsman-converters/lib/xiaomi');
  const utils = require('zigbee-herdsman-converters/lib/utils');
  const e = exposes.presets;
  const ea = exposes.access;

  const OPS_MODE = {
    ACTION: 0,
    SCENE: 1,
  };
  const ops_mode_lookup = { 0: 'action mode', 1: 'scene mode' };

  const aqara_opple = {
    cluster: 'aqaraOpple',
    type: ['attributeReport', 'readResponse'],
    options: (definition) => [
      ...xiaomi.numericAttributes2Options(definition),
      exposes.enum('operation_mode', ea.STATE, ['scene mode', 'action mode']),
    ],
    convert: (model, msg, publish, options, meta) => {

      if (msg.data.hasOwnProperty('155') || msg.data.hasOwnProperty('328')) {
        const ops_mode = msg.data['155'] || msg.data['328'];
        meta.state.operationMode = ops_mode;
      };

      const operation_mode = ops_mode_lookup[meta.state.operationMode];

      return {
        ...xiaomi.numericAttributes2Payload(msg, meta, model, options, msg.data),
        operation_mode,
        action: 'side_up',
        side_up: msg.data['329']+1,
      };
    },
  };

  const action_multistate = {
    cluster: 'genMultistateInput',
    type: ['attributeReport', 'readResponse'],
    options: [exposes.options.legacy()],
    convert: (model, msg, publish, options, meta) => {
        const value = msg.data['presentValue'];
        let result;
        if (value === 0) result = { action: 'shake' };
        else if (value === 2) result = { action: 'wakeup' };
        else if (value === 4) result = { action: 'hold' };
        else if (value >= 512) result = {action: 'tap', side: value-511};
        else if (value >= 256) result = {action: 'slide', side: value-255};
        else if (value >= 128) result = {action: 'flip180', side: value-127};
        else if (value >= 64) result = {action: 'flip90', action_from_side: Math.floor((value-64) / 8)+1, action_to_side: (value % 8)+1, action_side: (value % 8)+1, from_side: Math.floor((value-64) / 8)+1, to_side: (value % 8)+1, side: (value % 8)+1};
        else if (value >= 1024) result = { action: 'side_up', side_up: value - 1023 };
        if (result && !utils.isLegacyEnabled(options)) {delete result.to_side, delete result.from_side};
      return result ? result : null;
    },
  };

  const definition = {
    zigbeeModel: ['lumi.remote.cagl02'],
    model: 'CTP-R01',
    vendor: 'Lumi',
    description: 'Aqara cube T1 Pro',
    meta: { battery: { voltageToPercentage: '3V_2850_3000' } },
    configure: async (device, coordinatorEndpoint, logger) => {
      const endpoint = device.getEndpoint(1);
      await endpoint.write('aqaraOpple', {'mode': 1}, {manufacturerCode: 0x115f});
      await reporting.bind(endpoint, coordinatorEndpoint, ['genOnOff','genPowerCfg','genMultistateInput']);
    },
    fromZigbee: [aqara_opple, action_multistate, fz.MFKZQ01LM_action_analog],
    toZigbee: [],
    exposes: [
      /* Device Info */
      e.battery(),
      e.battery_voltage(),
      e.device_temperature(),
      e.power_outage_count(false),
      exposes
        .enum('operation_mode', ea.STATE, ['scene mode', 'action mode'])
        .withDescription(
          'Press LINK button 5 times to toggle between action mode and scene mode'
        ),
      /* Actions */
      e.angle('action_angle'),
      e.cube_side('action_from_side'),
      e.cube_side('action_side'),
      e.cube_side('action_to_side'),
      e.cube_side('side').withDescription('Destination side of action'),
      e.cube_side('side_up').withDescription('Upfacing side of current scene'),
      e.action([
        'shake',
        'wakeup',
        'fall',
        'tap',
        'slide',
        'flip180',
        'flip90',
        'hold',
        'side_up',
        'rotate_left',
        'rotate_right',
      ]),
    ],
  };

  module.exports = definition;

2. Edit the configuration.yaml file under config > zigbee2mqtt and add the following lines:

external_converters:
  - MFCZQ12LM.js

3. Reboot Z2M and pair the Cube.

4. Toggle between Action and Scene mode by clicking the Cube's reset button 5 times (repeat this 3-4 times to successfully configure the cube in Z2M)

In the future when the Cube is included natively in Z2M, you can delete the external converter and remove the code from the configuration.yaml file.

Hope this helps!

OstfrieseUnterwegs commented 1 year ago

@OstfrieseUnterwegs, I had the same issue and was able to get the Cube configured by toggling between modes (scene and action), which can be done by clicking the reset button 5 times. Doing this a few times allowed the Cube to configure correctly. I am using a Sonoff router though, so I'm not sure if the process is the same with a Conbee2.

This is possible with version 1.29.1-1 or is dev version needed?

I think by now I switched mode probably 20 times, deleted the cube and paired again, switched mode many times again - yet, it does not want to configure. However, I can use the cube in ioBroker and also see the mode changing etc. So probably it is just annoying but nothing more.

Strangely when using mqttexplorer and publish to zigbee2mqtt/bridge/request/device/configure I get a response { "data": { "id": "0x54ef44100062cf1e" }, "status": "ok" }

biosniper commented 1 year ago

@jzee923 Been working on getting a toZigbee converter working in order to change the operating mode in the z2m ui. image I was able to get it showing up as a toggle button but it doesn't actually function

const tzLocal = {
    MFCZQ12LM_scene_mode: {
        key: ['operation_mode'],
        convertSet: async (entity, key, value, meta) => {
            // Support existing syntax of a nested object just for the state field. Though it's quite silly IMO.
            const targetValue = value.hasOwnProperty('state') ? value.state : value;
            // Switches using aqaraOpple 0x0200 on the same endpoints as the onOff clusters.
            const lookupState = {"action": 0, "scene": 1};

Just wanted to say this is working pretty great for me as of 15th Jan 2023, so thank you very much for your code! Using a Sonoff ZBDongle-P with Z2M via HA.

This is a bit beyond my ability to contribute to other than feedback sadly!

ybizeul commented 1 year ago

Anyone managed to make « tap » work ? I don’t see anything in the logs when tapping.

jzee923 commented 1 year ago

you have to be in "action" mode and I believe it's a double tap

oskarslusitis commented 1 year ago

Is it possible to use additional things, like "roate left" with side 3 up? I integrated Cube with code from this thread through external convertors, but couldn't find such options in UI. Might I add i'm quite new in HA. Thanks!