Add commands on inputs that push CMD_SWITCH or a mode to another node #146
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/yn-30-commands"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Binds pull; inputs now push. A
switch,touchoroccupancytakescommands: an endpoint command (node,endpoint,actionoftoggle,on,offorfollow) sendsCMD_SWITCHto another node's output, and- mode: <name>broadcasts a mode. This changes a merged format: the YN-18action: modebind moves intocommands, inputs no longer takebinds, and the sourceless-bind rule is gone. The YN-18 tests are rewritten for the new shape.Runtime (shared paths, for review):
ModeStrategybecomesCommandStrategyand handles three sourceless actions,Mode,CommandandFollowCommand.BindgainstargetNode,targetEndpointandcommandAction. A fresh press sends the command (a mode also applies locally, as before);followsends On or Off as the input changes.disabled_modesand the input'senabled_bygate all of them. Nothing new is received, so the interests are unchanged. A node does not hear its own bus frames, so the generator rejects a command whose target is on the same node and says to put a bind on that output instead (the ticket allowed it; review asked for the rejection).Fixed nodes: a node file with
fixed: truedescribes a hand-written node by label, withidas the wire ID, no board and endpoints ofindex,typeandlabelonly. The ticket said 1 to 10, but the hand-written IDs innode_ids.hrun up to 0xC0 (Lighting is 0x10), so the range is 1 to 254 andcheck_uniquecompares wire IDs (a fixed ID 16 collides with a built node whose ID is 6). The generator never builds a fixed node, and the build script skips it. A number innode:always means a YAML ID, never a fixed node's. Fixed nodes can also be bind sources.Generator: commands resolve by label or number as binds do, and the target must be a
light,fan,load,dimmable_light,variable_fan,colour_light,water_heatingorflow5d. YAML reads an unquotedonandoffas booleans, so both spellings are accepted (the schema allows true and false). Schema goes through theDefsregistry with afixedform selected by an if/then/else at the root; README gains Commands and Fixed nodes sections; the C3 fixture uses commands and there is a fixed-node fixture (fixed_lighting.yaml).Tested: native 651 -> 657, Python 163 (with jsonschema);
pio runandscripts/build_yaml_nodes.shpass.YN-30
Runtime change is sound: null targets are safe on every shared path, Mode behaves as before, and follow still sends Off in a disabled mode. Points to act on below. I could not read YN-30, so the first is checked against node_ids.h only.
@ -410,0 +417,4 @@raise fail(path, field, "needs a mode, or an endpoint and an action")else:if isinstance(raw["action"], bool):raw = {**raw, "action": "on" if raw["action"] else "off"}The bool-to-on/off conversion has no test. Delete these two lines and no test fails, since neither the parse tests nor the schema tests pass True or False as an action. Add a case to each.
@ -556,1 +579,4 @@def parse_fixed_node(header, raw_endpoints, path):reject_unknown(path, "node", header, {"id", "label", "fixed"})check_label(path, "node.label", header.get("label"))Fixed node id is limited to 1..10, but the hand-written nodes in node_ids.h are 0x01, 0x02, 0x10-0x14, 0x40, 0x41, 0x60, 0xA0, 0xA1, 0xC0. Lighting (0x10) cannot be declared, and the README example labels id 1 'Lighting' when 1 is the Bridge. Unless YN-30 says 1..10 on purpose, widen to 1..maxNodeId (schema and README too). If the range stays, check_unique rejecting fixed id 1 against YAML id 1 is spurious: their wire ids are 1 and 11.
@ -727,0 +803,4 @@if endpoint is None:raise fail(path, f"{field}.endpoint", f"{reference!r} is not an endpoint of {target['label']!r}")if endpoint["type"] not in commandTargets:raise fail(path, f"{field}.endpoint", f"{reference!r} is a {kind_name(endpoint)}, which cannot take a switch command")A command whose target is this node is generated, and the README says it does nothing because a node does not hear its own frames. Reject it here and say 'use a bind' instead of emitting a silent no-op. test_commands_become_sourceless_binds_from_their_own_input (bind4, Porch light) pins the no-op as valid.
@ -858,0 +922,4 @@CommandRig gated;gated.bind.enabled = gate;gateOpen = false;No test that a follow command still sends Off in a disabled mode. That is the one deliberate asymmetry in CommandStrategy::onSource, and removing it would pass every current test. The disabled-mode test also has no positive control, so it would pass if setMode(Away) did nothing.
@ -168,2 +184,3 @@const BindStrategy* strategyFor(const Bind& bind) {if (bind.target == nullptr) return bind.action == BindAction::Mode ? &modeStrategy : nullptr;if (bind.target == nullptr)return bind.action == BindAction::Mode || bind.action == BindAction::Command || bind.action == BindAction::FollowCommand ? &commandStrategy : nullptr;Refactor this logic chain to an
isCommandmethod