⚠ Tradução desatualizada (em 2026-05-27). O conceito de widget foi redefinido: agora um widget é um cartão de dispositivo no dashboard do portal (componente React específico do produto), não um artefato gerado a partir de
contracts/widgets/. A camada antigawidget-registry/contracts/widgets/foi removida. A versão em inglês em ../en/09-add-product/02-add-widget.md é a fonte da verdade atual. Esta tradução será atualizada separadamente.
Adicionar um Widget e um Novo Dispositivo¶
Ciclo completo: desde a bifurcação do repositório até o PR mesclado. Abrange firmware, contrato, widget React e testes de portal.
Se você apenas precisar de firmware sem um novo widget — consulte 01-add-new-product.md.
Pré-requisitos¶
- Python 3.9+ com
pip install pyyaml jsonschema - Node.js 18+
- PlatformIO CLI
- Acesso ao portal iDryer para testes UIKit
Passo 1. Bifurcar e Clonar¶
- Bifurque o repositório
idryer-coreno GitHub. -
Clone sua bifurcação localmente:
-
Verifique se o contrato passa na validação no estado atual:
Passo 2. Editar o Contrato¶
Todas as alterações vão para contracts/mqtt_contract.yaml. Mantenha tudo em um único changeset.
Aviso
Não edite arquivos em _generated/ — são sobrescritos pelos geradores.
2a. Vocabulário de capacidades (novo tipo de periférico)¶
Se o dispositivo tiver um novo tipo de hardware (por exemplo, um sensor de CO2), adicione uma entrada na seção capability_vocabulary:
capability_vocabulary:
co2:
description: "CO2 sensor (ppm)"
config_flag: hasAirCo2
telemetry_field: airCo2Ppm
Isto adiciona automaticamente o campo hasAirCo2: bool a iDryer::Config na próxima regeneração.
2b. Funções canônicas (nova função + widget)¶
Se o dispositivo expõe um novo item de menu, registre a função em canonical_roles:
O valor widget é o nome do componente React que você escreverá no Passo 5.
2c. Ações de invocação (se o widget envia comandos)¶
Se o widget acionar uma ação no dispositivo, descreva-a em invoke_actions:
invoke_actions:
my_device:
co2.calibrate:
description: "Start CO2 sensor calibration"
args:
targetPpm:
type: uint16
description: "Reference CO2 value (ppm)"
required: true
2d. Perfil de dispositivo (novo tipo de dispositivo)¶
Adicione o perfil a device_profiles:
device_profiles:
my_device:
description: "My device"
capabilities: [led, co2]
invoke_actions: [co2.calibrate]
Os valores de capacidade vêm do capability_vocabulary definido no passo 2a.
Passo 3. Validar e Regenerar¶
Sinalizadores:
| Sinalizador | Efeito |
|---|---|
| (nenhum) | Validar + todos os geradores + copiar para portal |
--firmware-only |
Apenas geradores de firmware, pular cópia de portal |
--help |
Mostrar ajuda |
No sucesso, _generated/ é atualizado com:
uart_protocol.h,mqtt_topics.h— cabeçalhos C++iDryer_api.h— fachada Config/DeviceTypemqtt-api.types.ts— tipos TypeScriptscaffolds/my_device/— esqueleto do projeto PlatformIO- No portal: arquivos em
src/components/widgets/
Se regen.sh sair com um erro, corrija o problema antes de continuar.
Passo 4. Implementar Firmware¶
Use o projeto de andaime gerado:
Preencha as seções TODO em src/main.cpp:
onOnline()— carregue a configuração do NVS, inicialize o hardware.loop()— sonde sensores, chames_runtime.publishTelemetry(tel).buildInfoJson()— já preenchido pelo gerador a partir de capacidades.onInvoke()— manipuleco2.calibrate.
Para detalhes, consulte 01-add-new-product.md.
Passo 5. Criar o Widget React¶
Os widgets vivem em contracts/widgets/ e são copiados para o portal por regen.sh.
Nota
Não edite widgets diretamente em portal/src/components/widgets/ — serão sobrescritos na próxima execução de regen.sh. Edite apenas em contracts/widgets/.
Criar o arquivo de widget¶
// contracts/widgets/Co2Display.tsx
import type { WidgetProps } from "./widget-props";
export function Co2DisplayWidget({ device }: WidgetProps) {
const unit = device.units[0];
const co2 = unit?.co2Ppm ?? null;
return (
<div style={{ padding: "8px 16px" }}>
{co2 !== null ? `${co2} ppm` : "—"}
</div>
);
}
Registrar em index.ts¶
Registrar em widget-registry.tsx (no portal)¶
Após a próxima execução de regen.sh, o arquivo aparecerá em portal/src/components/widgets/Co2Display.tsx. Adicione uma entrada a widget-registry.tsx manualmente:
import { Co2DisplayWidget } from "./Co2Display";
export const WIDGET_REGISTRY: Record<WidgetName, React.ComponentType<WidgetProps>> = {
// ...
Co2Display: Co2DisplayWidget,
};
Passo 6. Testar em UIKit¶
Abra portal/src/pages/UiKitPage.tsx e adicione uma seção com dados simulados dentro do grupo Device Dashboard Widgets:
<KitSection title="Co2Display">
<Co2DisplayWidget device={MOCK_DEVICE} item={MOCK_CO2_ITEM} socket={null} />
</KitSection>
Abra o portal localmente e navegue para /uikit — o widget deve renderizar sem um login.
Passo 7. Checklist de PR¶
Antes de submeter o PR, verifique que:
-
./contracts/regen.shcompleta sem erros -
_generated/*é confirmado (não em.gitignore) -
contracts/widgets/— novo arquivo de widget adicionado -
contracts/widgets/index.ts— widget exportado -
widget-registry.tsxno portal — widget registado - O widget renderiza em
/uikitsem erros de console - O andaime em
_generated/scaffolds/my_device/reflete corretamente as capacidades - A descrição do PR declara: propósito do dispositivo, capacidades, nome do widget
Submeta o PR contra o ramo main do repositório idryer-core.
Todas as Alterações em Um PR¶
| Arquivo | Tipo de mudança |
|---|---|
contracts/mqtt_contract.yaml |
Fonte de verdade |
contracts/_generated/* |
Auto-gerado — confirmado integralmente |
contracts/widgets/MyWidget.tsx |
Novo arquivo |
contracts/widgets/index.ts |
+1 linha de exportação |
(portal, após regen.sh) |
src/components/widgets/MyWidget.tsx — cópia |
| (portal, manual) | src/components/widgets/widget-registry.tsx — +1 entrada |
| (portal, manual) | src/pages/UiKitPage.tsx — +1 seção em KitGroup |