// (C) 2026 Philip Endecott.
// Distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENCE.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#include "opentherm.hh"

#include "clocks.hh"
#include "debug.hh"
#include "registers.hh"
#include "vic.hh"

#include <libpbe/elemsof.hh>

#include <bit>

#include <string.h>


namespace opentherm {


// OpenTherm Protocol
// ==================

// Bits are Manchester-encoded; a 0 is represented by 01 
// and a 1 is represented by 10.
// The opentherm interface module doesn't invert these values.
// The bit rate is 1000 bits/sec, i.e. 1 ms per bit. 
// "Timing should be reset on each transition so that any 
// timing errors do not accumulate".
//
// Bits are grouped into 1+32+1-bit frames:
//   Start bit = 1
//   Parity bit, such that the total number of 1 bits is even.
//   Message type (3 bits)
//   Spare (4 bits)
//   Data ID (8 bits)
//   Data value (16 bits)
//   Stop bit = 1
// That is the transmission order. Each field is sent most significant 
// bit first.
//
// When idle the bus is at 0, so the start of the start bit is 
// indicated by a 0-to-1 transition.
//
// Message types are:
//   Master-to-slave (i.e. controller to boiler, hmm, this terminology has changed I guess):
//    000 Read data
//    001 Write data
//    010 Invalid data
//   Slave-to-master (boiler to controller):
//    100 Read ack
//    101 Write ack
//    110 Data invalid
//    111 Unknown Data ID
//
// Possible exchanges are:
//   Master sends read_data with a data_id;
//     Slave responds with read_ack with the same data_id and its data_value.
//     Or it responds with data_invalid, or unknown_data_id.
//   Master sends write_data with a data_id and data_value;
//     Slave responds with write ack.
//     Or it responds with data_invalid or unknown_data_id.
//   Then we have this write-and-read operation:
//   Master sends read_data with a data_id and data_value;
//     Slave responds with read_ack with the same data_id and a data_value.
//     Or it responds with data_invalid, or unknown_data_id.
//   Bizarely, master sends invalid_data and slave responds with data_invalid 
//   or unknown_data_id, huh? I guess this is to probe for supported features 
//   or something.
//
// Responses should start between 20ms and 800ms after the request 
// has finished. If nothing is received within 800ms, presumably the 
// slave did not receive the request correctly or some other error 
// has occurred.
// 
// The next request must be sent at least 100ms after the end of 
// the previous response.


enum class MessageType: unsigned {
  read_data       = 0,
  write_data      = 1,
  invalid_data    = 2,
  unused          = 3,
  read_ack        = 4,
  write_ack       = 5,
  data_invalid    = 6,
  unknown_data_id = 7
};


struct Frame {
  // To get the required bit order on the wire, we need to access fields 
  // from most significant to least significant bit.  
  // gcc places these bitfields in memory from least to most significant bit. 
  // (This is of course non-portable.)
  // In order to be able to treat this as a flat sequence of bits when 
  // sending and receiving, we need to reverse the order of the fields 
  // relative to the wire order:
  unsigned       data_value   : 16;   // Last sent.
  unsigned       data_id      :  8;
  unsigned       spare        :  4;   // =0 nope, that makes it non-trivial.
  MessageType    message_type :  3;
  unsigned       parity       :  1;   // First sent.
};

static_assert(sizeof(Frame) == 4);
static_assert(std::is_trivial<Frame>());


// Hardware Implementation
// =======================

// We'll use timer 1 for both transmitting and receiving.
//
// For transmitting, I've tried using the "external match" feature 
// but anything that involves changing timer registers on-the-fly 
// seems doomed. So instead, simply:
//   Set the match register to the number of ticks per half bit time.
//   Set the match control register to enable the interrupt and to 
//   reset the counter.
//   In the interrupt handler, set the output as appropriate for this 
//   half-bit.
//
// For receiving we'll set the capture control register to capture and 
// generate an interrupt on both rising and falling edges of the input; 
// in the interrupt handler we'll look at the captured timer value and 
// determine whether one or two half-bit-periods have elapsed.

// Available pins
// --------------
//
//   For transmit, I previously wanted to use a Timer 1 external match pin:
//     MAT1.0 = P0.12 = UART1.DSR = CANsomething
//     MAT1.1 = P0.13 = UART1.DTR = CANsomething
//     MAT1.2 = P0.17 = CAP1.2    = SSPsomething
//          and P0.19 = CAP1.2    = SSPsomething
//     MAT1.3 = P0.18 = CAP1.3    = SSPsomething
//          and P0.20 = EINT3     = SSPsomething
//   and chose MAT1.3 on P0.20. I'm no longer using the external match, 
//   but will continue to use P0.20.
//
//   For receive, we need a Timer 1 capture pin:
//     CAP1.0 = P0.10 = UART1.RTS
//     CAP1.1 = P0.11 = UART1.CTS
//     CAP1.2 = P0.17 = MAT1.2    = SSPsomething
//     CAP1.3 = P0.18 = MAT1.3    = SSPsomething
//
// We don't want to interfere with the UART control pins.
// We also don't want to interfere with GPIOs that we're using.
// Let's use CAP1.3 on pin P0.18.

// Timing
// ------
//
// The period of pclk is 203.4505 ns (though this could change).
// So there are 2457.6 pclk cycles per 0.5ms opentherm half bit time.
// Rounding to 2458 gives an error of 163 ppm, but 2458 factorises to 2 x 1229.
// Rounding to 2457 gives an error of 244 ppm, and 2457 factorises to 3 x 3 x 3 x 7 x 13.
// We can break those factors into the timer prescale value and the 
// values we put in the match and capture registers.
// Let's aim for about 100 timer ticks per half bit time; 
// that allows us to measure the position of the received 
// edges to a precision of about 1%.
// 7 x 13 = 91 which is as good as we can get from the factorisation. 
// So the prescale value will be the product of the other factors, 
// i.e. 3x3x3 = 27.
//
// OK, now let's determine that as if we didn't know what the 
// pclk frequency is:

constexpr int t_halfbit_us = 500;
constexpr int halfbit_ticks = 91;

// This is correct except that it overflows a signed 32-bit int:
// constexpr int timer_prescale = t_halfbit_us * f_pclk / halfbit_ticks / 1'000'000;
// Fix by doing some of the division earlier:
constexpr int timer_prescale = (t_halfbit_us/10) * f_pclk / halfbit_ticks / 100'000;

static_assert(timer_prescale == 27);  // For our current f_pclk.

constexpr int t_tick_us = 1'000'000 * timer_prescale / f_pclk;
// Use that for computing e.g. timeouts. It's not very precise.


// Match and Capture register usage
// --------------------------------
//
// MR1  Wait between syncs.
// MR2  Receive timeouts.
// MR3  Transmit halfbit timing.
// CR3  Receive edge timing.


// Interrupt Latency
// =================

// Transmitting requires good interrupt latency. We're allowed 150 us from 
// the nominal transition time to the actual transition. At ~20 MHz 
// that's about 3000 CPU clock cycles. That sounds like plenty, right? 
// But does anything block on other peripherals, e.g. a UART? The 
// modbus code sends to the UART synchronously, but that is "main 
// program" code that is interruptable, right? So I guess this is OK.
//
// Interrupt latency is a bit less of an issue when receiving, we just have 
// to process each capture in the half-bit-time before the next one (min 
// 250 us).


void setup()
{
  // To get the expected timing, the values we write are both minus 1.
  // This is documented for the prescale: "... the TC to increment on every 
  // PCLK when PR = 0, every 2 PCLKs when PR = 1, etc." It's less obvious 
  // for the match register, bit figure 59 in the guide makes it clear.
  // (Note that figure also indicates that the interrupt occurs before the 
  // reset.)
  *T1PR  = timer_prescale-1;
  *T1MR3 = halfbit_ticks-1;

  *T1TCR = 2;    // Reset.
  *T1TCR = 0;    // De-assert reset and Disable.
}


using void_func_ptr_t = void (*)();


// Transmission:
// =============

// To send a frame, call transmit_frame() with the frame to send and 
// a callback function to be called when transmission is done.

uint32_t transmit_word;
unsigned int transmit_state;  // Counts half-bits from 0 to 67, including the start and stop bits.
void_func_ptr_t transmit_frame_done;

static void transmit_next_bit()
{
  auto bitnum = transmit_state >> 1;
  auto bit = (bitnum == 0 || bitnum == 33) ? 1
           : (transmit_word >> (32-bitnum)) & 1;
  auto phase = transmit_state & 1;

  auto out = bit ^ phase;
  if (out) *IO0SET = 1<<20;
  else     *IO0CLR = 1<<20;

  transmit_state = transmit_state + 1;
  if (transmit_state == 68) {
    *T1MCR = 0;    // Disable interrupt.
    *T1TCR = 0;    // Disable.
    transmit_frame_done();
  }
}

static void set_parity(Frame& f)
{
  f.parity = 0;
  auto n = std::popcount(reinterpret_cast<const uint32_t&>(f));
  f.parity = n & 1;
}

static void transmit_frame(Frame f, void_func_ptr_t cbk)
{
  transmit_frame_done = cbk;

  f.spare = 0;
  set_parity(f);

  memcpy(&transmit_word, &f, sizeof(transmit_word));
  transmit_state = 0;

  *T1TCR = 2;    // Reset.
  *T1MCR = 0b011'000'000'000;    // Interrupt and reset when MR3 matches.
  transmit_next_bit();
  *T1TCR = 1;    // Enable.
}


// Reception
// =========

// To receive a frame, call receive_frame() with a callback function 
// to be called when the frame has been received, or an error has occured. 
// The callback is passed the error code and received frame.

enum class ReceiveError {
  ok,
  pulse_too_short,
  pulse_too_long,
  no_response,
  frame_truncated,
  garbage_after_frame,
  parity_error,
  manchester_encoding_error,
  missing_start_or_stop_bit,
};

uint32_t receive_word;
int receive_state;
int receive_bitnum;  // 0 = start bit, 1..32 = data bits, 33 = stop bit.
uint32_t prev_receive_edge_time;

using receive_frame_done_t = void (*)(ReceiveError, Frame);
receive_frame_done_t receive_frame_done;


// State machine
// -------------
// 
// State
// -----
// 00      We have seen the low-to-high transition at the start of a 1 bit.
//         We'll now see a short high pulse.
//         We enter this state when we see the rising edge of the start bit.
// 
// 01      We have seen the high-to-low transition at the start of a 0 bit.
//         We'll now see a short low pulse.
//
// 10      We have seen the low-to-high transition in the middle of a 0 bit.
//         We'll now see either a short high pulse ending at the end of this 
//         bit, or a long high pulse ending at the middle of the next bit.
//         We output a 0 bit when we enter this state.
//
// 11      We have seen the high-to-low transition in the middle of a 1 bit.
//         We'll now see either a short low pulse ending at the end of this 
//         bit, or a long low pulse ending at the middle of the next bit.
//         We output a 1 bit when we enter this state.
// 
// At the end of a short pulse we toggle both bits; at the end of a long 
// pulse we toggle the right bit.


static void receive_done(ReceiveError error = ReceiveError::ok)
{
  *T1TCR = 2;    // Reset.
  *T1TCR = 0;    // Disable.
  *T1CCR = 0;    // Stop capturing and interrupting on edges of CAP 1.3.

  Frame frame;
  memcpy(&frame, &receive_word, sizeof(frame));

debug("parity = ",static_cast<int>(frame.parity),
      " message_type = ",static_cast<int>(frame.message_type),
      " data_id = ",static_cast<int>(frame.data_id),
      " data_value = ",static_cast<int>(frame.data_value),
      "\n");

  receive_frame_done(error, frame);
}

static void receive_error(ReceiveError error)
{
debug("RECEIVE ERROR #", static_cast<int>(error), "\n");
  // If we've detected an error in the middle of a receive frame, we 
  // should wait until the potential end of the frame before allowing 
  // a transmission. So wait here, or what? TODO.
  receive_done(error);
}


static void receive_frame(receive_frame_done_t cbk)
{
  // We should possibly wait briefly before starting to receive, to avoid 
  // any spurious transitions on the receive signal at the end of transmission.
  // The minimum gap between request and response is 20 ms.

  receive_frame_done = cbk;

  *T1TCR = 2;    // Reset.

  // We must have started to receive the ack frame within 800 ms of finishing 
  // transmitting ours.
  constexpr int t_timeout_us = 800'000;
  constexpr int timeout_ticks = t_timeout_us / t_tick_us;
  *T1MR2 = timeout_ticks;

  *T1MCR = 0b000'001'000'000;    // Interrupt when MR2 matches.
  *T1CCR = 0b111'000'000'000;    // Capture and interrupt on rising and falling edges of CAP 1.3.

  receive_state = 0;
  receive_bitnum = -1;
  receive_word = 0;

  *T1TCR = 1;    // De-assert reset, and enable.
}

template <receive_frame_done_t cbk>
static void receive_frame()
{
  receive_frame(cbk);
}


static void receive_edge()
{
  auto edge_time = *T1CR3;

  if (receive_bitnum == -1) {
    // This is the first edge.
    // Now that we've started to receive the frame, reduce the timeout 
    // to the maximum frame time from now.
    constexpr int t_bit_max_us = 1150;
    constexpr int t_frame_max_us = t_bit_max_us * 34;
    constexpr int t_timeout_us = t_frame_max_us + 10'000;  // Allow 10 ms extra.
    constexpr int timeout_ticks = t_timeout_us / t_tick_us;
    *T1MR2 = edge_time + timeout_ticks;
    receive_bitnum = 0;

  } else {

    auto ticks = edge_time - prev_receive_edge_time;
    // The spec allows the transition to be -100/+150 us.
    // We may or may not see that on our GPIO; I think the interface 
    // board has slow falling edges. Anyway, that means that:
    //   The shortest short pulse is  500 - 100 - 150 =  250 us
    //   The longest  short pulse is  500 + 100 + 150 =  750 us
    //   The shortest long  pulse is 1000 - 100 - 150 =  750 us
    //   The longest  long  pulse is 1000 + 100 + 150 = 1250 us
    if (ticks < 250 / t_tick_us) {
      receive_error(ReceiveError::pulse_too_short);
      return;

    } else if (ticks < 750 / t_tick_us) {
      // Short pulse.
      receive_state ^= 2;

    } else if (ticks < 1250 / t_tick_us) {
      // Long pulse.
      if (!(receive_state & 2)) {
        receive_error(ReceiveError::manchester_encoding_error);
        return;
      }

    } else {
      receive_error(ReceiveError::pulse_too_long);
      return;
    }

    receive_state ^= 1;

    if (receive_state == 3) {
      // received 1
      if (0 < receive_bitnum && receive_bitnum <= 32) {
        receive_word |= (1 << (32-receive_bitnum));
      } // else start or stop bit, ok, or excess bits after frame, which we 
        // detect in the timeout code. We could report that error here.
      ++receive_bitnum;

    } else if (receive_state == 2) {
      // received 0
      if (receive_bitnum == 0 || receive_bitnum == 33) {
        receive_error(ReceiveError::missing_start_or_stop_bit);
        return;
      }
      ++receive_bitnum;
    }

  }

  prev_receive_edge_time = edge_time;
}


static bool check_parity(uint32_t f)
{
  // We check the uint32_t, not the Frame, to avoid confusion about the spare bits.
  auto n = std::popcount(f);
  return (n & 1) == 0;
}


static void receive_timeout()
{
  if (receive_bitnum == -1) {
    // We've not received anything and reached the 800 ms timeout.
    receive_error(ReceiveError::no_response);
    return;

  } else if (receive_bitnum < 34) {
    // We received something, but then the frame stopped.
    receive_error(ReceiveError::frame_truncated);
    return;

  } else if (receive_bitnum > 34) {
    // Excess edges after frame.
    receive_error(ReceiveError::garbage_after_frame);
    return;

  } else if (!check_parity(receive_word)) {
    receive_error(ReceiveError::parity_error);
    return;

  } else {
    receive_done();
  }
}


// Interrupts
// ==========

static void wait_done();

IRQ_HANDLER
void timer_irq_handler()
{
  *VICVectAddr = 1;  // "Writing to this register does not set the value for future
                     // reads from it. Rather, this register should be written near the
                     // end of an ISR, to update the priority hardware."
                     // (So why not move it to the end?)
  auto interrupt_bits = *T1IR;
  *T1IR = 0xff;      // Clears the interrupt.

  if (interrupt_bits & (1<<3)) {        // MR3
    transmit_next_bit();
  } else if (interrupt_bits & (1<<7)) { // CR3
    receive_edge();
  } else if (interrupt_bits & (1<<2)) { // MR2
    receive_timeout();
  } else if (interrupt_bits & (1<<1)) { // MR1
    wait_done();
  }
}


// Higher-level:
// =============

void_func_ptr_t read_or_write_done;

static void check_write_ack(ReceiveError error, Frame frame)
{
  if (error == ReceiveError::ok) {
    if (frame.message_type == MessageType::write_ack) {
// Could check that data_id is as expected.
    } else {
debug("Got something not a write ack with type ",static_cast<int>(frame.message_type),"\n");
    }
  }
  read_or_write_done();
}

uint16_t* read_data_value_p;

static void check_read_ack(ReceiveError error, Frame frame)
{
  if (error == ReceiveError::ok) {
    if (frame.message_type == MessageType::read_ack) {
// Could check that data_id is as expected.
      *read_data_value_p = frame.data_value;
debug("Read value ",*read_data_value_p,"\n");
    } else {
debug("Got something not a read ack with type ",static_cast<int>(frame.message_type),"\n");
    }
  }
  read_or_write_done();
}

template <receive_frame_done_t cbk>
static void exchange_frames(Frame frame)
{
  transmit_frame(frame, &receive_frame<cbk>);
}

static void write_data(uint8_t data_id, uint16_t data_value)
{
debug("OpenTherm will write ",data_value," to ",data_id,"\n");
  Frame f;
  f.message_type = MessageType::write_data;
  f.data_id = data_id;
  f.data_value = data_value;
  exchange_frames<&check_write_ack>(f);
}

static void read_data(uint8_t data_id, uint16_t* data_value_p)
{
debug("OpenTherm will read from ",data_id," sending ",*data_value_p,"\n");
  Frame f;
  f.message_type = MessageType::read_data;
  f.data_id = data_id;
  f.data_value = *data_value_p;  // We send the current value of the register in the 
                                 // read request; this is needed for the read+write of 
                                 // the status register.
  read_data_value_p = data_value_p;
  exchange_frames<&check_read_ack>(f);
}



// Register Sync
// =============

// We have a set of OpenTherm registers that we periodically sync 
// with the boiler, either reading or writing, or a combined write-and-read 
// (done with the read command):

enum class RegisterType { read, write, write_read };

struct RegisterInfo {
  RegisterType type;
  uint8_t      data_id;
  uint16_t     data_value = 0;
};

RegisterInfo registers[] = {
  { RegisterType::write_read,  0, 2<<8  },   // ch_enable ~ Status
  { RegisterType::write,       1, 60<<8 },   // ch_flow_target_temp = TSet
  { RegisterType::write,      56, 50<<8 },   // dhw_target_temp = TdhwSet
  { RegisterType::read,       25,       },   // ch_flow_temp = Tboiler
  { RegisterType::read,       28,       },   // ch_return_temp = Tret
  { RegisterType::read,       27,       },   // outside_temp = Toutside
  { RegisterType::read,       18,       },   // ch_pressure = CH-pressure
  { RegisterType::read,        3,       },   // S-Config / S-MemberIDcode; not in enum.
                                             // We are required to read this, at least at startup; we ignore it.
  // Other things we could have:
  // CH water pressure.
  // DHW flow rate.
  // Real time clock.
  // Relative modulation level (read, percentage).
};


static void do_next_sync();

static void wait_before_next_sync()
{
  *T1TCR = 2;    // Reset.

  // Let's wait for 400 ms:
  constexpr int t_timeout_us = 400'000;
  constexpr int timeout_ticks = t_timeout_us / t_tick_us;
  *T1MR1 = timeout_ticks;

  *T1MCR = 0b000'000'001'000;    // Interrupt when MR1 matches.

  *T1TCR = 1;    // De-assert reset, and enable.
}

static void wait_done()
{
  *T1TCR = 2;    // Reset.
  *T1MCR = 0;    // No matching.
  *T1TCR = 0;    // De-assert reset and Disable.

  do_next_sync();
}


static void do_next_sync()
{
  static int next_register = 0;

  auto& r = registers[next_register];
  switch (r.type) {
    case RegisterType::write:
      write_data(r.data_id, r.data_value);
      break;

    case RegisterType::read:
      read_data(r.data_id, &r.data_value);
      break;

    case RegisterType::write_read:
      read_data(r.data_id, &r.data_value);
      break;
  }

  next_register = (next_register + 1) % pbe::elemsof(registers);
}


void start_sync_process()
{
  read_or_write_done = &wait_before_next_sync;
  do_next_sync();
}


// Modbus access to registers
// ==========================

uint16_t get_register(uint16_t address)
{
  if (address >= pbe::elemsof(registers)) {
    return 0;
  }

  auto raw = registers[address].data_value;

  if (address == Register::ch_enable) {
    return raw;
  }

  // All the other registers store temperatures or pressures 
  // in the OpenTherm 8.8 fixed point format, i.e. 1 sign bit, 
  // 7 integer bits and 8 fraction bits.
  // Convert that to the decimal fixed point form with one 
  // fraction digit that other modbus devices use:

  return (raw * 10 + 128) / 256;

  // I doubt this works for negative values.
}

void set_register(uint16_t address, uint16_t value)
{
  if (address == Register::ch_enable) {
    // We have simply 0 or 1 for ch_enable, but the OpenTherm status 
    // value has more bits. We always turn on bit 1 for DHW enable. 
    // These bits are in the high byte of the value we send; the low 
    // byte is the boiler status that we get back from the write_read.
    value = ((value & 1) | 2) << 8;
  }

  if (address < pbe::elemsof(registers)) {
    registers[address].data_value = value;
  }
}



};  // namespace opentherm







