Skip to content

Conversation

@jacky202509
Copy link
Contributor

Summary

This PR adds support for the OWON AC221, an IR (infrared) controller device.

The AC221 is an IR-only device and does not provide real state feedback from
controlled appliances, therefore optimistic control is used.

Changes

  • Add a new device definition for the OWON AC221 IR controller
  • Define the OWON private cluster with proper TypeScript typing
  • Implement one-key pairing as a SET-only action
  • Parse and expose one-key pairing result data from the device
  • Avoid unknown (?) UI states in zigbee2mqtt

Motivation

Previously, the AC221 was not supported and one-key pairing resulted in an
unknown state in the UI. Since IR devices are inherently one-way, treating
pairing as an action instead of a readable state better matches the device
behavior and improves user experience.

Compatibility

  • No breaking changes
  • Existing devices and configurations are unaffected

Tested with

  • OWON AC221
  • zigbee2mqtt (latest)

DerDreschner and others added 30 commits November 13, 2025 14:22
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <[email protected]>
…k#10396)

Co-authored-by: MaDMaLKaV <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Koen Kanters <[email protected]>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
jacky202509 and others added 5 commits November 13, 2025 15:51
…mp meter

- Introduce DP135 to expose high-precision AC frequency
- Only expose high-precision frequency for devices with applicationVersion >= 132
- Update tuyaDatapoints and exposes accordingly
- Ensures backward compatibility for older devices
},
} satisfies Fz.Converter<"fallDetectionOwon", OwonFallDetection, ["attributeReport", "readResponse"]>,

cb432Metering: {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? m.electricityMeter should cover this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree this is standard Electrical Measurement
functionality. To keep this PR focused on the CB432 device, I’ll remove the
device-specific alarm handling here and consider adding a generic
modernExtend in a separate PR.

convert: (model, msg, publish, options, meta) => {
const result: KeyValue = {};

if (msg.data.acAlarmsMask !== undefined || msg.data[2048] !== undefined) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given this is standard functionality, this can be added to

function genericMeter(args: MeterArgs = {}) {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree this is standard Electrical Measurement
functionality. To keep this PR focused on the CB432 device, I’ll remove the
device-specific alarm handling here and consider adding a generic
modernExtend in a separate PR.

},
} satisfies Fz.Converter<"haElectricalMeasurement", undefined, ["attributeReport", "readResponse"]>,

owonCb432ThresholdRead: {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree this is standard Electrical Measurement
functionality. To keep this PR focused on the CB432 device, I’ll remove the
device-specific alarm handling here and consider adding a generic
modernExtend in a separate PR.

owonAcOneKeyPairingResponse: {
cluster: "manuSpecificOwonAc",
type: ["commandOneKeyPairingResponse", "commandOneKeyPairingResultUpdate"],
// biome-ignore lint/suspicious/noExplicitAny: required for converter compatibility
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove (and don't use any below`)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had remove it

}

return payload;
},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use satisfies Fz.Converter< with the custom cluster definition here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted to satisfies Fz.Converter for the manufacturer-specific cluster; command types are kept untyped as they are not part of ZCL definitions.

};
}
return {};
},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted to satisfies Fz.Converter for the manufacturer-specific cluster; command types are kept untyped as they are not part of ZCL definitions.


owonCB432Threshold: {
key: ["over_voltage_threshold", "over_current_threshold", "over_power_threshold"],
convertSet: async (entity, key, value, meta) => {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree this is standard Electrical Measurement
functionality. To keep this PR focused on the CB432 device, I’ll remove the
device-specific alarm handling here and consider adding a generic
modernExtend in a separate PR.

model: "CB432",
vendor: "OWON",
description: "32A/63A power circuit breaker",
extend: [m.onOff(), m.electricityMeter({cluster: "metering"})],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep these extends here (same for other definitions)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion. I agree this is standard Electrical Measurement
functionality. To keep this PR focused on the CB432 device, I’ll remove the
device-specific alarm handling here and consider adding a generic
modernExtend in a separate PR.

ID: 0xfd00,
manufacturerCode: Zcl.ManufacturerCode.OWON_TECHNOLOGY_INC,
attributes: {
status: {ID: 0x0000, type: Zcl.DataType.ENUM8, write: true, max: 0xff},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This attribute is now defined via deviceAddCustomCluster. In this typed definition write is not a supported property anymore, so it’s omitted while keeping the same functionality.

extend: [
m.onOff({endpointNames: ["l1", "l2", "l3"]}),
m.onOff({endpointNames: ["l1", "l2", "l3"], powerOnBehavior: false}),
m.iasZoneAlarm({
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use m.deviceEndpoints here (remove meta: {multiEndpoint: true}, and endpoint: (device) => ({l1: 1, l2: 2, l3: 3}),)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using m.deviceEndpoints handles multi-endpoint, so meta.multiEndpoint is not needed.
This device does not support the StartUpOnOff attribute, so powerOnBehavior is explicitly disabled.

vendor: "OWON",
whiteLabel: [{vendor: "Oz Smart Things", model: "WSP403"}],
description: "Smart plug",
extend: [m.onOff(), m.electricityMeter({cluster: "metering"}), m.forcePowerSource({powerSource: "Mains (single phase)"})],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use this instead of the configure/tozigbee/fromzigbee/expose

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The device is now migrated to modernExtend.
Power-on behavior is intentionally disabled as WSP403 does not reliably support startUpOnOff.

A minimal configure() is kept only to customize metering reporting parameters,
which are not configurable via modernExtend in this codebase.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A minimal configure() is kept only to customize metering reporting parameters,

To have a consistent experience, all devices should have the same reporting parameters unless there is a really good reason not to, is there one in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom reporting parameters are added because WSP403 reports
metering data very infrequently with the default configuration.

In practice this results in delayed or missing power updates,
which makes real-time power monitoring unreliable for this device.

The adjusted reporting improves usability without increasing
network traffic significantly.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WSP403 reports metering data very infrequently

I don't understand, it should report with the same frequency as all other plugs right? The default can be found here:

// Report change with every 0.05A change

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, thanks for pointing this out.

After reviewing modernExtend.electricityMeter(), it already configures
standard reporting for seMetering.
The custom configure() is not needed here and will be removed to keep
reporting consistent with other devices.

model: "WSP402",
vendor: "OWON",
description: "Smart plug",
extend: [m.onOff(), m.electricityMeter({cluster: "metering"})],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The device is now migrated to modernExtend.
Power-on behavior is intentionally disabled as WSP402 does not reliably support startUpOnOff.

A minimal configure() is kept only to customize metering reporting parameters,
which are not configurable via modernExtend in this codebase.

@Koenkk Koenkk merged commit 056279c into Koenkk:master Jan 7, 2026
3 checks passed
@Koenkk
Copy link
Owner

Koenkk commented Jan 7, 2026

Thanks!

Could you also submit a picture for the docs (guide)? Make sure that the file name is AC221.png

@jacky202509
Copy link
Contributor Author

I’ve submitted the device picture for the docs, named AC221.png.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.