Faça o ZBCMR-01 ser reconhecido pelo Zigbee2MQTT
Recentemente comprei um motor de persiana inteligente Nova Digital ZBCMR-01, mas ao tentar adicioná‑lo ao Zigbee2MQTT ele apareceu como “não suportado. O motivo é simples: o modelo que comprei tem um fingerprint diferente do que está cadastrado na base de dados do z2m. Neste post mostro como contornar o problema usando um conversor externo, passo a passo, e ainda como enviar a correção para que todos se beneficiem.
Se você já viu a mensagem de dispositivo não suportado, não desista: a solução está em habilitar os conversores externos e criar um arquivo pequeno que mapeia o fingerprint do seu lote.
Por que o ZBCMR-01 não é reconhecido?
A página do Zigbee2MQTT para o ZBCMR-01 lista o modelo como suportado, mas o fingerprint cadastrado é zigbeeModel: TS0105 + manufacturerName: _TZE600_ogyg1y6b. Seu módulo, de um lote diferente, se identifica como TS0601 + _TZE284_5blghqkp, uma combinação que ainda não está na base de dados, portanto o z2m recusa o dispositivo mesmo sendo “o mesmo produto”.
Passo a passo para adicionar o conversor externo
- Verifique a versão do z2m – Acesse Settings > About. Se estiver desatualizado, atualize (geralmente não resolve sozinho, mas vale testar). Fonte principal.
- Habilite conversores externos (necessário a partir da v2.11). Em configuration.yaml (do z2m e não do Home Assistant. Acesse portanto o arquivo /root/config/zigbee2mqtt/configuration.yaml) confirme ou adicione:
advanced:
enable_external_js: trueÉ provável que já exista uma seção chamada “advanced”. Depois reinicie o z2m. Mais detalhes.
- Adicione o conversor externo – O jeito mais fácil é pelo frontend: Settings > Dev console > External converters. Crie um arquivo, por exemplo nova-zbcmr01.mjs, com o seguinte conteúdo:
// Nova Digital ZBCMR-01 - external converter for Zigbee2MQTT
// Made by bernabauer.com
// https://www.bernabauer.com/blog/faca-o-zbcmr-01-ser-reconhecido-pelo-zigbee2mqtt/
// Version: 1.0
import * as exposes from 'zigbee-herdsman-converters/lib/exposes';
import * as tuya from 'zigbee-herdsman-converters/lib/tuya';
const e = exposes.presets;
const ea = exposes.access;
// DP1 - Control command. Values: 0=OPEN, 1=STOP, 2=CLOSE, 3=CONTINUE (never observed in practice).
// Confirmed reliable for both read and write.
const coverState = {
to: (v) => {
const map = {OPEN: new tuya.Enum(0), STOP: new tuya.Enum(1), CLOSE: new tuya.Enum(2), CONTINUE: new tuya.Enum(3)};
return map[v];
},
from: (v) => {
const map = {0: 'OPEN', 1: 'STOP', 2: 'CLOSE', 3: 'CONTINUE'};
return map[v];
},
};
// DP3 - Direction of the last movement command. Values: 0=opening, 1=closing.
// Confirmed via 14+ direct correlations. Does not indicate whether movement
// is still in progress, and stays unchanged when a STOP command is sent
// mid-travel (no distinct "stopped" value observed). Read-only.
const direction = {
from: (v) => (v === 0 ? 'opening' : 'closing'),
};
// DP8 - Real position report (%). Values: 0-100. Confirmed live during real
// motor movement. Read-only: writing here has no effect (tested - no error,
// no echo, no movement). Use DP9 to command a target position.
const coverPositionReport = {
from: (v, meta, options, publish) => {
publish({state: v === 0 ? 'CLOSE' : 'OPEN', position_report: v});
return v;
},
};
// DP9 - Target position command (%). Values: 0-100. Confirmed: writing here
// moves the motor to the exact percentage, later reflected on DP8. Same
// set/report split as the sibling "ZSM-01" (Novato) device in the official
// library. Listed BEFORE DP8 in tuyaDatapoints so writes to 'position' route
// here (tz.datapoints picks the first array entry matching the property).
const coverPositionSet = {
to: (v) => v,
};
// DP11 - Inverts the cover position scale. Confirmed live: default (false)
// leaves the motor as-is; writing 'false' while already false triggered a
// resync that opened the motor fully. Do not write unless intentionally
// changing the inversion - it moves the motor to resync.
const invertCover = {
to: (v) => v,
from: (v) => v,
};
// DP19 - Favorite position (%). Confirmed live: writing here does not move
// the motor immediately, just stores the value (motor beeps to confirm the
// save). Matches "favorite_position" on the sibling ZSM-01 device (same
// value type: numeric 0-100, write-only). No recall/goto trigger found for
// it - not even in the official ZSM-01 definition.
const favoritePosition = {
to: (v) => v,
from: (v) => v,
};
// DP20 - Click control (step nudge). Values: up=0, down=1. Confirmed live:
// "down" triggered a brief CLOSE that auto-stopped after ~1s (a small nudge),
// matching "click_control" on the sibling ZSM-01 device. Exposed as two
// single-value enums (button entities in HA) instead of one two-value enum
// (which HA renders as a dropdown) - matches the cover's own open/close buttons.
const nudgeUp = {
to: () => new tuya.Enum(0),
};
const nudgeDown = {
to: () => new tuya.Enum(1),
};
export default {
fingerprint: tuya.fingerprint('TS0601', ['_TZE284_5blghqkp']),
model: 'ZBCMR-01',
vendor: 'Nova Digital',
description: 'Roller Blind Motor (TS0601 batch)',
extend: [tuya.modernExtend.tuyaBase({dp: true, queryOnConfigure: true})],
exposes: [
e.cover_position().setAccess('position', ea.STATE_SET),
e.enum('direction', ea.STATE, ['opening', 'closing']).withDescription(
'DP3 - Direction of the last movement command (0=opening, 1=closing). '
+ 'Does not indicate whether movement is still in progress.',
).withHomeAssistant({icon: 'mdi:arrow-up-down-bold'}),
e.binary('invert_cover', ea.STATE_SET, true, false).withLabel('Invert Cover Position').withDescription(
"DP11 - Inverts the cover position, false: open=100,close=0, true: open=0,close=100 "
+ "(default false). The value must be `true` or `false`",
).withHomeAssistant({icon: 'mdi:swap-vertical'}),
e.numeric('favorite_position', ea.STATE_SET).withValueMin(0).withValueMax(100).withDescription(
'DP19 - Stores a favorite position (%). Does not move the motor immediately.',
).withHomeAssistant({entityCategory: 'config'}),
e.numeric('position_report', ea.STATE).withUnit('%').withDescription(
'DP8 - Raw position report, mirrors the cover position as a standalone sensor.',
).withHomeAssistant({icon: 'mdi:roller-shade'}),
e.enum('nudge_up', ea.STATE_SET, ['PRESS']).withLabel('Nudge up').withDescription(
'DP20 - Step nudge up: triggers a brief OPEN movement that auto-stops after ~1s.',
).withHomeAssistant({icon: 'mdi:chevron-up'}),
e.enum('nudge_down', ea.STATE_SET, ['PRESS']).withLabel('Nudge down').withDescription(
'DP20 - Step nudge down: triggers a brief CLOSE movement that auto-stops after ~1s.',
).withHomeAssistant({icon: 'mdi:chevron-down'}),
],
meta: {
tuyaDatapoints: [
[1, 'state', coverState],
[3, 'direction', direction],
[9, 'position', coverPositionSet],
[8, 'position', coverPositionReport],
[11, 'invert_cover', invertCover],
[19, 'favorite_position', favoritePosition],
[20, 'nudge_up', nudgeUp],
[20, 'nudge_down', nudgeDown],
],
},
};
Se preferir, salve o arquivo diretamente na pasta external_converters/ (irmã do configuration.yaml).
- Reinicie o z2m e repareie – Vá em Settings > Tools > Restart Zigbee2MQTT. Se o dispositivo já estava listado como “unsupported”, remova-o e coloque‑o em modo de pareamento novamente (reset físico do motor + permitir entrada na rede).
- Teste abrir/fechar/parar e posição – Use os comandos OPEN, CLOSE, STOP e ajuste a posição com valores de 0 a 100. Caso o comportamento esteja invertido ou a posição não seja reportada, ajuste o conversor (por exemplo, invertendo a posição) ou siga o guia de descoberta de DPs para Tuya (fonte complementar 3).
- Depois que funcionar – Abra uma issue “New device support” no repositório do Zigbee2MQTT (exemplo de issue) anexando os dados da interview, para que esse fingerprint seja incorporado oficialmente e você não precise manter o conversor externo para sempre.

Após seguir esses passos, seu motor deve aparecer na lista de dispositivos com as funcionalidades de abertura, fechamento, parada e controle de posição. Teste cada comando e confirme que os relatórios de estado estão corretos.
Testando e ajustando
Como os Datapoints (DPs) da Tuya podem variar entre lotes, vale observar o log do z2m enquanto aciona o motor. Se notar que a posição está invertida, basta mudar a linha exposes: [e.cover_position().setAccess(‘position’, ea.STATE_SET)] para usar exposes: [e.cover_position().setAccess(‘position’, ea.STATE_SET)] com a opção invert_cover: true na configuração do dispositivo, ou ajustar o conversor para mapear o DP correto. Se precisar de ajuda com o mapeamento de DPs, consulte o guia detalhado de suporte a novos dispositivos Tuya (fonte complementar 3).
Contribuindo para a base oficial
Quando tudo estiver funcionando, considere devolver à comunidade. Abra uma pull request no repositório zigbee-herdsman-converters ou uma issue no zigbee2mqtt com o fingerprint e a descrição do seu lote. Assim, o suporte será integrado nas próximas versões e você poderá remover o conversor externo.
