# =============================================================================
#  HomeMaster OpenTherm Gateway — RAW DATA-ID SCANNER (FULL 0..255)
#
#  The OpenTherm Data-ID field is 8 bits, so the real address space is 0..255,
#  not just the 0..127 defined by the spec. Some boilers use the upper half for
#  vendor data (e.g. Daikin D2C uses id 162 for the DHW setpoint), so a proper
#  scan must cover all 256 ids.
#
#  Mechanism:
#    - the opentherm component keeps sending frames (Status + keepalive reads);
#    - before_send hijacks EVERY outgoing frame and rewrites x.id to the next
#      number in a 0..255 sweep (kept as a READ request);
#    - before_process_response classifies the reply:
#         type 4 (READ_ACK)      -> id IS supported
#         type 7 (UNKNOWN_DATAID) -> id NOT supported
#         (no line)              -> boiler ignored it / timed out
#
#  ALIASING TEST (important for ids >= 128):
#    Many OpenTherm stacks mask the id to 7 bits, so a request for id 128 is
#    physically sent as id 0, 162 as 34, etc. If that happens, the "answer" for
#    the upper half is just an echo of the lower half, NOT real support.
#    We store the raw 16-bit value of every id 0..127, and when an id >= 128
#    answers we compare it to its mirror (id - 128). Same bytes => ALIAS
#    (fake); different => genuine upper-half register.
#
#  Each frame ~1 s, so a full 0..255 sweep is ~4-5 min. Run it 10-15 min.
#  READ-ONLY: never writes to the boiler, never enables heating. Throwaway
#  diagnostic — flash it, grab the log, then flash your real config back.
#
#  Grep the log for "otscan".
# =============================================================================

esphome:
  name: homemaster-opentherm-934ca0     # keep the adopted name (OTA needs it)
  name_add_mac_suffix: false
  friendly_name: HomeMaster OpenTherm Gateway (SCAN)

esp32:
  variant: esp32
  board: esp32dev
  flash_size: 16MB
  framework:
    type: esp-idf

logger:
  level: DEBUG                          # component also prints each frame itself

api:

wifi:
  ap:
    ssid: "HomeMaster OT Fallback"

captive_portal:

ota:
  - platform: esphome

# scan_id: which Data-ID we probe next (0..255).
# lo_seen / lo_val: for each lower id 0..127, whether we saw a reply and its
#                   raw u16 value, so we can alias-check the upper half.
globals:
  - id: scan_id
    type: int
    restore_value: no
    initial_value: '0'
  - id: lo_seen
    type: bool[128]
    restore_value: no
  - id: lo_val
    type: uint16_t[128]
    restore_value: no

opentherm:
  id: ot_bus
  in_pin: GPIO21
  out_pin: GPIO26
  sync_mode: true

  before_send:
    then:
      - lambda: |-
          // Sweep the FULL 8-bit id space 0..255 as READ requests.
          x.id = (uint8_t) id(scan_id);
          id(scan_id) = (id(scan_id) + 1) % 256;
          if (id(scan_id) == 0)
            ESP_LOGI("otscan", "================ full sweep 0..255 done ================");

  before_process_response:
    then:
      - lambda: |-
          int t   = (int) x.type;
          int idn = (int) x.id;
          uint16_t v = x.u16();

          if (t != 4 && t != 5) {                     // not a data ACK
            if (t == 7)
              ESP_LOGW("otscan", "id=%3d  NOT supported (Unknown-DataId)", idn);
            return;
          }

          if (idn < 128) {                            // lower half: record for alias check
            id(lo_seen)[idn] = true;
            id(lo_val)[idn]  = v;
            ESP_LOGI("otscan", "id=%3d  supported  u16=%u  f88=%.2f", idn, v, x.f88());
          } else {                                    // upper half: test against mirror
            int m = idn - 128;
            if (id(lo_seen)[m] && id(lo_val)[m] == v)
              ESP_LOGW("otscan", "id=%3d  ALIAS of id %d (7-bit mask, not real)", idn, m);
            else
              ESP_LOGI("otscan", "id=%3d  supported (UPPER-HALF, genuine)  u16=%u", idn, v);
          }

# Keepalive reads so the component keeps sending; their ids get rewritten above.
sensor:
  - platform: opentherm
    t_boiler:
      name: "SCAN keepalive"
