/**
*******************************************************************************
* @file modbus_diag.c
* @brief Реализация диагностики устройства Modbus
*******************************************************************************
* @details
Модуль обработки запросов диагностической информации (0x08):
- Полная поддержка всех подфункций диагностики согласно спецификации Modbus
- Выставление любого бита в Diagnostics Register
- Сбор статистики работы устройства
- Управление режимами работы (Normal/Listen Only)
******************************************************************************/
#include "modbus_diag.h"

#ifdef MODBUS_ENABLE_DIAGNOSTICS

MB_DiagnosticsInfoTypeDef MB_DIAG = {0}; ///< Глобальная структура диагностики

/**
  * @brief Инициализация диагностических счетчиков
  */
void MB_DiagnosticsInit(void)
{
  MB_DIAG.DiagnosticRegister = 0;
  MB_DIAG.DeviceMode = MODBUS_NORMAL_MODE;
  
  // Инициализация счетчиков
  MB_DIAG.Counters.BusMessage = 0;
  MB_DIAG.Counters.BusCommunicationErr = 0;
  MB_DIAG.Counters.BusExceptionErr = 0;
  MB_DIAG.Counters.SlaveMessage = 0;
  MB_DIAG.Counters.SlaveNoResponse = 0;
  MB_DIAG.Counters.SlaveNAK = 0;
  MB_DIAG.Counters.SlaveBusy = 0;
  MB_DIAG.Counters.BusCharacterOverrun = 0;
}


/**
  * @brief    Выставить бит в регистре диагностике
  * @param    bit_num         Номер бита для выставления (1-15, 0 бит нельзя выставить)
  * @param    bit_state       Состояние бита для выставления (Выставить/Сбросить)
  * @return   >0 - номер выставленного бита, 0 - ошибка
  */
int MB_Diagnostics_WriteBit(int bit_num, int bit_state)
{
  if(bit_num == 0 || bit_num > 15)
    return 0;
  
  if(bit_state)
    MB_DIAG.DiagnosticRegister |= (1 << bit_num);
  else
    MB_DIAG.DiagnosticRegister &= ~(1 << bit_num);
  
  return bit_num;
}
/**
  * @brief    Прочитать состояние бита диагностического регистра
  * @param    bit_num         Номер бита (0-15)
  * @return   1 - бит установлен, 0 - бит сброшен или ошибка
  */
int MB_Diagnostics_GetBit(int bit_num)
{
  if(bit_num < 0 || bit_num > 15)
    return 0;
    
  return (MB_DIAG.DiagnosticRegister >> bit_num) & 0x01;
}

/**
  * @brief    Обработать функцию Diagnostics (Serial Line only) (0x08)
  * @param    modbus_msg        Указатель на структуру сообщения modbus
  * @return   fMessageHandled   Статус обработки команды
  */
uint8_t MB_Process_Diagnostics(RS_MsgTypeDef *modbus_msg)
{
  uint16_t sub_function = modbus_msg->MbData[0];
  uint16_t request_data = modbus_msg->MbData[1];
  
  // Если устройство в режиме Listen Only, отвечаем только на sub-function 0x01
  if (MB_DIAG.DeviceMode == MODBUS_LISTEN_ONLY_MODE && sub_function != 0x0001)
  {
    return 0; // Не отвечаем в режиме Listen Only
  }
  
  switch(sub_function)
  {
    case 0x0000: // Return Query Data
      // Эхо-ответ с теми же данными
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = request_data;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0001: // Restart Communications
      // Перезапуск коммуникаций - выходим из Listen Only режима
      MB_DIAG.DeviceMode = MODBUS_NORMAL_MODE;
      
      // Если request_data = 0xFF00, очищаем лог событий
      if (request_data == 0xFF00)
      {
          MB_DiagnosticsInit(); // Полный сброс
      }
      else
      {
        // Очищаем только счетчики, но не регистр диагностики
        MB_DIAG.Counters.BusMessage = 0;
        MB_DIAG.Counters.BusCommunicationErr = 0;
        MB_DIAG.Counters.BusExceptionErr = 0;
        MB_DIAG.Counters.SlaveMessage = 0;
        MB_DIAG.Counters.SlaveNoResponse = 0;
        MB_DIAG.Counters.SlaveNAK = 0;
        MB_DIAG.Counters.SlaveBusy = 0;
        MB_DIAG.Counters.BusCharacterOverrun = 0;
      }
      
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = request_data;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0002: // Return Diagnostic Register
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.DiagnosticRegister;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0003: // Change ASCII Input Delimiter
      // В RTU режиме не поддерживается
      modbus_msg->FuncCode |= FC_ERR_VALUES_START;
      modbus_msg->Except_Code = ET_ILLEGAL_FUNCTION;
      return 0;
        
    case 0x0004: // Force Listen Only Mode
      MB_DIAG.DeviceMode = MODBUS_LISTEN_ONLY_MODE;
      // В режиме Listen Only не отправляем ответ
      return 0;
        
    case 0x000A: // Clear Counters and Diagnostic Register
      MB_DiagnosticsInit(); // Полный сброс
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = 0;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x000B: // Return Bus Message Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.BusMessage;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x000C: // Return Bus Communication Error Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.BusCommunicationErr;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x000D: // Return Bus Exception Error Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.BusExceptionErr;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x000E: // Return Server Message Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.SlaveMessage;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x000F: // Return Slave No Response Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.SlaveNoResponse;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0010: // Return Slave NAK Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.SlaveNAK;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0011: // Return Slave Busy Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.SlaveBusy;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0012: // Return Bus Character Overrun Count
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = MB_DIAG.Counters.BusCharacterOverrun;
      modbus_msg->ByteCnt = 4;
      break;
        
    case 0x0014: // Clear Overrun Counter and Flag
      MB_DIAG.Counters.BusCharacterOverrun = 0;
      // Сбрасываем флаг переполнения в DiagnosticRegister
      MB_DIAG.DiagnosticRegister &= ~(1<<0);
      modbus_msg->MbData[0] = sub_function;
      modbus_msg->MbData[1] = 0;
      modbus_msg->ByteCnt = 4;
      break;
        
    default:
      modbus_msg->FuncCode |= FC_ERR_VALUES_START;
      modbus_msg->Except_Code = ET_ILLEGAL_FUNCTION;
      return 0;
  }
  
  return 1;
}

/**
  * @brief    Увеличивает счетчик сообщений на шине
  */
void MB_Diagnostics_BusMessageCnt(void)
{
  MB_DIAG.Counters.BusMessage++;
}

/**
  * @brief Увеличивает счетчик ошибок связи
  */
void MB_Diagnostics_CommunicationErrorCnt(void)
{
  if (MB_DIAG.Counters.BusCommunicationErr < 0xFFFF)
    MB_DIAG.Counters.BusCommunicationErr++;
}

/**
  * @brief Увеличивает счетчик исключений
  */
void MB_Diagnostics_ExceptionErrorCnt(void)
{
  if (MB_DIAG.Counters.BusExceptionErr < 0xFFFF)
    MB_DIAG.Counters.BusExceptionErr++;
}

/**
  * @brief Увеличивает счетчик переполнения символов
  */
void MB_Diagnostics_CharacterOverrunCnt(void)
{
  if (MB_DIAG.Counters.BusCharacterOverrun < 0xFFFF)
  {
    MB_DIAG.Counters.BusCharacterOverrun++;
    // Устанавливаем флаг переполнения в DiagnosticRegister
    MB_DIAG.DiagnosticRegister |= (1 << 0);
  }
}

/**
  * @brief Увеличивает счетчик отсутствия ответов
  */
void MB_Diagnostics_SlaveMessageCnt(void)
{
  if (MB_DIAG.Counters.SlaveMessage < 0xFFFF)
    MB_DIAG.Counters.SlaveMessage++;
}

/**
  * @brief Увеличивает счетчик отсутствия ответов
  */
void MB_Diagnostics_SlaveNoResponseCnt(void)
{
  if (MB_DIAG.Counters.SlaveNoResponse < 0xFFFF)
    MB_DIAG.Counters.SlaveNoResponse++;
}

/**
  * @brief Увеличивает счетчик NAK ответов
  */
void MB_Diagnostics_SlaveNAKCnt(void)
{
  if (MB_DIAG.Counters.SlaveNAK < 0xFFFF)
    MB_DIAG.Counters.SlaveNAK++;
}

/**
  * @brief Увеличивает счетчик занятости устройства
  */
void MB_Diagnostics_SlaveBusyCnt(void)
{
  if (MB_DIAG.Counters.SlaveBusy < 0xFFFF)
    MB_DIAG.Counters.SlaveBusy++;
}

/**
  * @brief    Получение текущего режима устройства
  * @return   Текущий режим работы устройства
  */
MB_DeviceModeTypeDef MB_GetDeviceMode(void)
{
  return MB_DIAG.DeviceMode;
}

#endif //MODBUS_ENABLE_DIAGNOSTICS
