Koenkk / Koenkk/zigbee2mqtt

[External Converter]: ZG-IR01 from HOBEIAN

Open
#33,007 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

external converter
Dominant language
TypeScript
Stars
15.7k
Forks
2k
Avg merge
18h 55m
Merged PRs (30d)
35

Description

Link

https://aliexpress.com/item/1005012528513279.html

Database entry

{"id":7,"type":"EndDevice","ieeeAddr":"0xa4c138f8b889a2eb","nwkAddr":50491,"manufId":4742,"manufName":"HOBEIAN","powerSource":"Battery","modelId":"ZG-IR01","epList":[1],"endpoints":{"1":{"profId":260,"epId":1,"devId":256,"inClusterList":[0,3,4,5,61184,57348,60672,6,1026,1029,1],"outClusterList":[3],"clusters":{"genBasic":{"attributes":{"manufacturerName":"HOBEIAN","zclVersion":3,"appVersion":151,"modelId":"ZG-IR01","powerSource":3}},"msTemperatureMeasurement":{"attributes":{"measuredValue":2600}},"msRelativeHumidity":{"attributes":{"measuredValue":5500}},"genPowerCfg":{"attributes":{"batteryPercentageRemaining":200,"batteryVoltage":26}}},"binds":[],"configuredReportings":[],"meta":{}}},"appVersion":151,"stackVersion":2,"hwVersion":1,"dateCode":"15062026","swBuildId":"0115062026","zclVersion":3,"interviewCompleted":true,"interviewState":"SUCCESSFUL","meta":{"configured":"0.0.0"},"lastSeen":1788369992914}

Zigbee2MQTT version

2.14.0 (unknown)

External converter
import deviceIndexImport from "zigbee-herdsman-converters/devices/index";
import {tzZosung} from "zigbee-herdsman-converters/lib/zosung";

let definitions = deviceIndexImport;

for (let i = 0; i < 5 && !Array.isArray(definitions); i++) {
    definitions = definitions?.default;
}

if (!Array.isArray(definitions)) {
    throw new Error("Could not load built-in Zigbee device definitions");
}

const original = definitions.find(
    (definition) =>
        definition.model === "ZG-IR01" &&
        definition.vendor === "HOBEIAN",
);

if (!original) {
    throw new Error("Built-in HOBEIAN ZG-IR01 definition not found");
}


//
// Convert HA raw IR timings in microseconds to a Broadlink IR packet.
//
function timingsToBroadlinkBase64(timings, repeatCount = 0) {
    if (!Array.isArray(timings) || timings.length === 0) {
        throw new Error("IR timings must be a non-empty array");
    }

    const encoded = [];

    for (const timing of timings) {
        const duration = Math.abs(Number(timing));

        if (!Number.isFinite(duration) || duration <= 0) {
            throw new Error(`Invalid IR timing: ${timing}`);
        }

        // Broadlink tick = 32.84 µs approximately.
        const ticks = Math.max(
            1,
            Math.round((duration * 269) / 8192),
        );

        if (ticks > 0xffff) {
            throw new Error(`IR timing too long: ${timing}`);
        }

        if (ticks < 0x100) {
            encoded.push(ticks);
        } else {
            encoded.push(
                0x00,
                (ticks >> 8) & 0xff,
                ticks & 0xff,
            );
        }
    }

    const repeats = Math.max(
        0,
        Math.min(255, Math.trunc(Number(repeatCount) || 0)),
    );

    const packet = [
        0x26,                       // IR packet
        repeats,
        encoded.length & 0xff,     // data length, little endian
        (encoded.length >> 8) & 0xff,
        ...encoded,
        0x0d,
        0x05,
    ];

    // Broadlink packets are padded to a 16-byte boundary
    // including the four-byte transport header.
    while ((packet.length + 4) % 16 !== 0) {
        packet.push(0x00);
    }

    return Buffer.from(packet).toString("base64");
}


//
// Override only native HA ir_emitter handling.
//
const zgIr01NativeInfrared = {
    key: ["ir_emitter"],

    convertSet: async (entity, key, value, meta) => {
        if (
            value &&
            typeof value === "object" &&
            Array.isArray(value.timings)
        ) {
            const broadlinkCode = timingsToBroadlinkBase64(
                value.timings,
                value.repeat_count ?? 0,
            );

            return await tzZosung.zosung_ir_code_to_send.convertSet(
                entity,
                "ir_emitter",
                broadlinkCode,
                meta,
            );
        }

        // Preserve support for directly supplied IR codes.
        return await tzZosung.zosung_ir_code_to_send.convertSet(
            entity,
            key,
            value,
            meta,
        );
    },
};


export default {
    ...original,

    // Our ir_emitter converter must come before the built-in one.
    toZigbee: [
        zgIr01NativeInfrared,

        ...(original.toZigbee ?? []).filter(
            (converter) =>
                !converter.key?.includes("ir_emitter"),
        ),
    ],
};
What does/doesn't work with the external definition?

Home assistant's new infrared.* entity type doesn't work.

WARNING: It was vibe coded, but it's so simple so I don't think it's too much of a problem, but I know how some people (including me sometimes) can get a bit annoyed with AI use.

Notes

software_build_id: 0115062026
date_code: 15062026
endpoints:

{"1":{"clusters":{"input":["genBasic","genIdentify","genGroups","genScenes","manuSpecificTuya","zosungIRControl","zosungIRTransmit","genOnOff","msTemperatureMeasurement","msRelativeHumidity","genPowerCfg"],"output":["genIdentify"]}}}

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the built-in definition loaded through zigbee-herdsman-converters/devices/index and the existing tzZosung converter used by the external definition. Compare the native ir_emitter handling with the supplied timingsToBroadlinkBase64 path. Done means Home Assistant's infrared entity works for ZG-IR01 while direct IR codes remain supported.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript
Domain
embedded-iot
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.