blueprint: name: "KNX Cover Control + Inverted Position Feedback (%)" description: > Control a Home Assistant cover entity via KNX group addresses (Up/Down + Stop) and send back an inverted position feedback in percent to KNX whenever the cover position changes (including changes initiated from Home Assistant). Example: HA 0% → KNX 100%, HA 100% → KNX 0%. domain: automation homeassistant: min_version: "2026.1.0" input: cover_entity: name: "Cover entity" description: "Select the cover to control." selector: entity: domain: cover ga_up_down: name: "KNX group address Up/Down" description: > 1-bit command GA for Up/Down (typical: 0 = Up/Open, 1 = Down/Close). KNX mapping: Up/Down command object. selector: text: ga_stop: name: "KNX group address Stop" description: > 1-bit command GA for Stop/Step. KNX mapping: Stop command object. selector: text: ga_position_feedback: name: "KNX position feedback (%) group address" description: > GA for inverted position feedback in percent (DPT 5.001 / type 'percent'). KNX mapping: Position status feedback (0..100%). selector: text: invert_direction: name: "Invert Up/Down direction" description: > Enable if your KNX Up/Down semantics are reversed. default: false selector: boolean: feedback_cooldown_s: name: "Feedback cooldown (seconds)" description: > Minimum seconds between two KNX feedback telegrams to reduce bus traffic. default: 1 selector: number: min: 0 max: 30 step: 1 mode: slider mode: queued max: 10 variables: cover_entity: !input cover_entity ga_up_down: !input ga_up_down ga_stop: !input ga_stop ga_position_feedback: !input ga_position_feedback invert_direction: !input invert_direction feedback_cooldown_s: !input feedback_cooldown_s trigger: # KNX Up/Down command - trigger: knx.telegram destination: !input ga_up_down incoming: true outgoing: false group_value_write: true group_value_read: false group_value_response: false id: knx_up_down # KNX Stop command - trigger: knx.telegram destination: !input ga_stop incoming: true outgoing: false group_value_write: true group_value_read: false group_value_response: false id: knx_stop # HA cover position change (attribute current_position) - trigger: state entity_id: !input cover_entity attribute: current_position id: ha_position_changed condition: [] action: - choose: # -------- KNX Up/Down -------- - conditions: - condition: trigger id: knx_up_down sequence: - variables: raw: "{{ trigger.payload | int(0) }}" # Normal semantics: 0 = Up/Open, 1 = Down/Close is_down: >- {% set down = (raw != 0) %} {{ (not down) if invert_direction else down }} - choose: - conditions: "{{ is_down }}" sequence: - action: cover.close_cover target: entity_id: !input cover_entity - conditions: "{{ not is_down }}" sequence: - action: cover.open_cover target: entity_id: !input cover_entity # -------- KNX Stop -------- - conditions: - condition: trigger id: knx_stop sequence: - action: cover.stop_cover target: entity_id: !input cover_entity # -------- HA → KNX Inverted Position Feedback -------- - conditions: - condition: trigger id: ha_position_changed sequence: - variables: # HA: 0% = closed, 100% = open # KNX inverted: 0% = open, 100% = closed pos: >- {% set p = state_attr(cover_entity, 'current_position') %} {% if p is not none %} {{ 100 - (p | int) }} {% else %} {{ none }} {% endif %} - condition: template value_template: "{{ pos is not none }}" - choose: - conditions: "{{ feedback_cooldown_s | int(0) > 0 }}" sequence: - delay: seconds: "{{ feedback_cooldown_s | int(0) }}" - action: knx.send data: address: !input ga_position_feedback type: percent payload: "{{ pos }}" response: false default: []