/**
******************************************************************************
* @file           dallas_tools.c
* @brief          Драйвер для работы с датчиками температуры DS18B20
******************************************************************************
@details
Библиотека предназначена для работы с цифровыми датчиками температуры DS18B20 
по однопроводному интерфейсу 1-Wire. Реализована поддержка инициализации, поиска, 
добавления и работы с несколькими датчиками.

@verbatim
==============================================================================
                        ## Основные задачи библиотеки ##
==============================================================================
Эта библиотека предоставляет следующие основные функции:
  (+) Инициализация шины 1-Wire и обнаружение подключённых датчиков
  (+) Инициализация структуры датчика по:
       - ROM-адресу
       - пользовательским байтам (TH, TL, UserByte3, UserByte4)
       - порядковому номеру в списке найденных устройств
  (+) Конфигурация разрешения измерения
  (+) Чтение температуры
  (+) Замена «потерянного» датчика
  (+) Деинициализация структуры датчика

==============================================================================
                             ## Быстрый старт ##
==============================================================================
Пример последовательности инициализации и использования:

  1. Определение пина и таймера для OneWire в ow_port.h:
      #define OW_GPIO_Port  GPIOB
      #define OW_Pin_Numb   0
      #define OW_Pin        (1<<OW_Pin_Numb)
      #define OW_TIM        TIM3
      #define OW_TIM_1US_PERIOD 24
      
  2. Подключение библиотеки и настройка таймеров:
      #include "dallas_tools.h"
      MX_TIM_Init();
      
  3. Инициализация шины и поиск датчиков:
      Dallas_BusFirstInit(&htim);

  4. Инициализация датчика Dallas_SensorHandleTypeDef по одному из методов:
      sens1.Init.init_func = &Dallas_SensorInitByInd;          // по индексу
      sens1.Init.InitParam.Ind = 0;  // порядковый номер найденного датика для инициализации
      
      sens2.Init.init_func = &Dallas_SensorInitByROM;          // по ROM-адресу
      sens2.Init.InitParam.ROM = 0;  // ROM датика для инициализации
      
      sens3.Init.init_func = &Dallas_SensorInitByUserBytes;    // по пользовательским байтам
      sens3.Init.InitParam.UserBytes.UserByte1 = 1;  // UseBytes датика для инициализации
      sens3.Init.InitParam.UserBytes.UserByte2 = 2;  // UseBytes датика для инициализации
      sens3.Init.InitParam.UserBytes.UserByte3 = 3;  // UseBytes датика для инициализации
      sens3.Init.InitParam.UserBytes.UserByte4 = 4;  // UseBytes датика для инициализации

  5. Инициализация структуруы датчика:
      Dallas_AddNewSensors(&hdallas, &sens);

  6. Работа с датчиком:
      Dallas_StartConvertTAll(hdallas, DALLAS_WAIT_BUS, 0);
      Dallas_ReadTemperature(&sens);

==============================================================================
                        ## Требуемые зависимости ##
==============================================================================
Для работы библиотеки требуется:
  - Драйвер OneWire (файлы onewire.c/h и ow_port.c/.h)
  - Драйвер DS18B20 (файлы ds18b20.c/h)

@endverbatim
==============================================================================
*****************************************************************************/


/* Includes ----------------------------------------------------------------*/

#include "dallas_tools.h"
#include "string.h"


/* Declarations and definitions --------------------------------------------*/
DALLAS_HandleTypeDef hdallas;

/* Functions ---------------------------------------------------------------*/

/**
* @brief  Функция для иниицализации шины OW для датчиков
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_BusFirstInit(TIM_HandleTypeDef *htim)
{
  if(htim == NULL)
    return HAL_ERROR;
  
  HAL_StatusTypeDef result;
  
  
  HAL_TIM_Base_Start(htim);
  
  hdallas.onewire = &OW;
  hdallas.ds_devices = &DS;
  OW.DataPin = OW_Pin;
  OW.DataPort = OW_GPIO_Port;  
  /* Инициализация onewire и поиск датчиков*/
  OneWire_Init(&OW);
  
  
  return DS18B20_Search(&DS, &OW) != HAL_OK;
}


/**
 * @brief  Функция для иниицализации нового датчика в структуре
 * @param  hdallas        Указатель на хендл для общения с датчиками
 * @param  sensor         Указатель на структуру датчика
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_AddNewSensors(DALLAS_HandleTypeDef *hdallas, DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  
  if(hdallas == NULL)
    return HAL_ERROR;
  if(sensor == NULL)
    return HAL_ERROR;  
  
  sensor->hdallas = hdallas;
  
  result = sensor->Init.init_func(hdallas, sensor);
  
  return result;
}




/**
 * @brief  Функция для нахождения нового датчика на место потерянного
 * @param  sensor         Указатель на структуру датчика
 * @retval HAL Status 
 */
HAL_StatusTypeDef Dallas_ReplaceLostedSensor(DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  
  
  if(sensor == NULL)
    return HAL_ERROR;  
  
  result = Dallas_IsConnected(sensor);
  
  if(sensor->isLost)
  {
    if(DS18B20_Search(sensor->hdallas->ds_devices, sensor->hdallas->onewire) != HAL_OK)
      return HAL_ERROR;
    
    if(sensor->Init.init_func(sensor->hdallas, sensor) != HAL_OK)
      return HAL_ERROR;
    
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY; // датчик не потерян
  }
}


/**
 * @brief  Запускает измерение температуры на всех датчиках
 * @param  hdallas        Указатель на хендл для общения с датчиками
 * @param  waitCondition    Условие ожидания завершения преобразования
 * @param  dallas_delay_ms  Время ожидания окончания конверсии
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_StartConvertTAll(DALLAS_HandleTypeDef *hdallas, DALLAS_WaitConvertionTypeDef waitCondition, uint8_t dallas_delay_ms)
{
  HAL_StatusTypeDef result;
  uint8_t rxDummyData;

  if(hdallas == NULL)
    return HAL_ERROR;
  
  // Отправка команды начала преобразования температуры
  result = DS18B20_StartConvTAll(hdallas->onewire);
  if(result != HAL_OK)
  {
    return result;
  }
//  // Проверка что преобразование началось
//  if(OneWire_ReadBit(onewire) == 1)
//    return HAL_ERROR;
    
  // Ожидание завершения преобразования, путем проверки шины
  if (waitCondition == DALLAS_WAIT_BUS)
  {
    result = DS18B20_WaitForEndConvertion(hdallas->onewire);
    return result;
  }

  // Ожидание завершения преобразования, путем задержки
  if (waitCondition == DALLAS_WAIT_DELAY)
  {
    uint32_t delayValueMs = 0;

    switch (dallas_delay_ms)
    {
      case DALLAS_CONFIG_9_BITS:
        delayValueMs = DALLAS_DELAY_MS_9_BITS;
        break;

      case DALLAS_CONFIG_10_BITS:
        delayValueMs = DALLAS_DELAY_MS_10_BITS;
        break;

      case DALLAS_CONFIG_11_BITS:
        delayValueMs = DALLAS_DELAY_MS_11_BITS;
        break;

      case DALLAS_CONFIG_12_BITS:
        delayValueMs = DALLAS_DELAY_MS_12_BITS;
        break;

      default:
        break;
    }

    HAL_Delay(delayValueMs);
  }

  return result;
}

/**
 * @brief  Измеряет температуру на датчике
 * @param  sensor        Указатель на структуру датчика
 * @param  waitCondition Условие ожидания завершения преобразования
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_ConvertT(DALLAS_SensorHandleTypeDef *sensor, DALLAS_WaitConvertionTypeDef waitCondition)
{
  HAL_StatusTypeDef result;
  uint8_t rxDummyData;
  
  if(sensor == NULL)
    return HAL_ERROR;
  if(sensor->isInitialized == 0)
    return HAL_ERROR;
  
  /* Проверка присутствует ли выбранный датчик на линии */
  result = Dallas_IsConnected(sensor);
  if (result != HAL_OK)
    return result;

  // Отправка команды начала преобразования температуры
  result = DS18B20_StartConvT(sensor->hdallas->onewire, (uint8_t *)&sensor->sensROM);
  if(result != HAL_OK)
  {
    return result;
  }

  // Ожидание завершения преобразования, путем проверки шины
  if (waitCondition == DALLAS_WAIT_BUS)
  {
    result = DS18B20_WaitForEndConvertion(sensor->hdallas->onewire);
    if(result == HAL_TIMEOUT)
    {
      sensor->f.timeout_convertion_cnt++;
    }
    return result;
  }

  // Ожидание завершения преобразования, путем задержки
  if (waitCondition == DALLAS_WAIT_DELAY)
  {
    uint32_t delayValueMs = 0;

    switch (sensor->hdallas->scratchpad.ConfigRegister)
    {
      case DALLAS_CONFIG_9_BITS:
        delayValueMs = DALLAS_DELAY_MS_9_BITS;
        break;

      case DALLAS_CONFIG_10_BITS:
        delayValueMs = DALLAS_DELAY_MS_10_BITS;
        break;

      case DALLAS_CONFIG_11_BITS:
        delayValueMs = DALLAS_DELAY_MS_11_BITS;
        break;

      case DALLAS_CONFIG_12_BITS:
        delayValueMs = DALLAS_DELAY_MS_12_BITS;
        break;

      default:
        break;
    }

    HAL_Delay(delayValueMs);
  }
  
  /* Не считываем температуру, если не выбрано ожидание окончания преобразования */
  if(waitCondition != DALLAS_WAIT_NONE)
  {
    result = Dallas_ReadTemperature(sensor);
  }

  return result;
}


/**
 * @brief  Читает измеренную датчиком температуру
 * @param  sensor Указатель на структуру датчика
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_ReadTemperature(DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  
  if(sensor == NULL)
    return HAL_ERROR;
  if(sensor->isInitialized == 0)
    return HAL_ERROR;
  
  /* Проверка присутствует ли выбранный датчик на линии */
  result = Dallas_IsConnected(sensor);
  if (result != HAL_OK)
  {
    return result;
  }

  
  result = DS18B20_CalcTemperature(sensor->hdallas->onewire, (uint8_t *)&sensor->sensROM, (uint8_t *)&sensor->hdallas->scratchpad, &sensor->temperature);
  
  if (result != HAL_OK)
  {
    sensor->f.read_temperature_err_cnt++;
    return result;
  }  
  
  return HAL_OK;
}

/**
 * @brief  Проверяет подключен ли датчик (чтение scratchpad)
 * @param  sensor Указатель на структуру датчика
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_IsConnected(DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  if(sensor == NULL)
    return HAL_ERROR;
     
  result = Dallas_ReadScratchpad(sensor);
  
  if (result == HAL_OK)
  {
    sensor->isConnected = 1;
    sensor->isLost = 0;
    return HAL_OK;
  }
  else
  {
    sensor->temperature = 0;
    if(sensor->isConnected == 1)
    {
      sensor->f.disconnect_cnt++;
    }
    sensor->isLost = 1;
    sensor->isConnected = 0;
    
//    Dallas_ReplaceLostedSensor(sensor);
    return HAL_BUSY; // использую busy, чтобы отличать ситуацию от HAL_ERROR
  }
}

/**
 * @brief  Записывает пользовательские байты
 * @param  sensor         Указатель на структуру датчика
 * @param  UserBytes12    Пользовательские байты 1 и 2
 * @param  UserBytes34    Пользовательские байты 3 и 4
 * @param  UserBytesMask  Маска, какие байты записывать, а какие нет
 * @retval HAL Status
 * @details старший байт - UserByte4/UserByte2, младший - UserByte3/UserByte1.
 */
HAL_StatusTypeDef Dallas_WriteUserBytes(DALLAS_SensorHandleTypeDef *sensor, uint16_t UserBytes12, uint16_t UserBytes34, uint8_t UserBytesMask)
{  
  HAL_StatusTypeDef result;
  if(sensor == NULL)
    return HAL_ERROR;
  
  if(sensor->isInitialized == 0)
    return HAL_ERROR;
  
  /* Проверка присутствует ли выбранный датчик на линии */
  result = Dallas_IsConnected(sensor);
  if (result != HAL_OK)
    return result;
  
  result = DS18B20_WriteUserBytes(sensor->hdallas->onewire, (uint8_t *)&sensor->sensROM, UserBytes12, UserBytes34, UserBytesMask);
  if (result != HAL_OK)
  {
    sensor->f.write_err_cnt++;
    return result;
  }  
  result = Dallas_ReadScratchpad(sensor);
  if (result != HAL_OK)
  {
    return result;
  }  
  
  return result;
}


HAL_StatusTypeDef Dallas_ReadScratchpad(DALLAS_SensorHandleTypeDef *sensor)
{
  if(sensor == NULL)
    return HAL_ERROR;
  return DS18B20_ReadScratchpad(sensor->hdallas->onewire, (uint8_t *)&sensor->sensROM, (uint8_t *)&sensor->hdallas->scratchpad);
}

/**
 * @brief  Инициализирует структуру датчика по ROM
 * @param  hdallas        Указатель на хендл для общения с датчиками
 * @param  sensor         Указатель на структуру датчика
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_SensorInitByROM(DALLAS_HandleTypeDef *hdallas, DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  
  if(hdallas == NULL)
    return HAL_ERROR;
  if(sensor == NULL)
    return HAL_ERROR;  
    
  uint8_t ROM[8] = {0};
  ROM[0] = (sensor->Init.InitParam.ROM >> (7*8)) & 0xFF;
  ROM[1] = (sensor->Init.InitParam.ROM >> (6*8)) & 0xFF;
  ROM[2] = (sensor->Init.InitParam.ROM >> (5*8)) & 0xFF;
  ROM[3] = (sensor->Init.InitParam.ROM >> (4*8)) & 0xFF;
  ROM[4] = (sensor->Init.InitParam.ROM >> (3*8)) & 0xFF;
  ROM[5] = (sensor->Init.InitParam.ROM >> (2*8)) & 0xFF;
  ROM[6] = (sensor->Init.InitParam.ROM >> (1*8)) & 0xFF;
  ROM[7] = (sensor->Init.InitParam.ROM >> (0*8)) & 0xFF;
  
  if(DS18B20_IsValidAddress(ROM) != HAL_OK)
    return HAL_ERROR;
  
  uint8_t comparebytes = DALLAS_ROM_SIZE;
  int ROM_ind = 0;
  for(int i = 0; i < hdallas->onewire->RomCnt; i++)
  {
    comparebytes = DALLAS_ROM_SIZE;
    for(int rom_byte = 0; rom_byte < DALLAS_ROM_SIZE; rom_byte++)
    {
      if(hdallas->ds_devices->DevAddr[i][rom_byte] == ROM[rom_byte])
        comparebytes--;
    }
    if(comparebytes == 0)
    {
      ROM_ind = i;
      break;
    }
  }
  
  /* Проверка присутствует ли выбранный датчик на линии */
  if(comparebytes == 0)
  {
    
    result = Dallas_SensorInit(hdallas, sensor, &hdallas->ds_devices->DevAddr[ROM_ind]);
    return result;
  }
  else
  {
    return HAL_ERROR;
  }
  
  
}



/**
 * @brief  Инициализирует структуру датчика по пользовательским байтам
 * @param  hdallas        Указатель на хендл для общения с датчиками
 * @param  sensor         Указатель на структуру датчика
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_SensorInitByUserBytes(DALLAS_HandleTypeDef *hdallas, DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  
  if(hdallas == NULL)
    return HAL_ERROR;
  if(sensor == NULL)
    return HAL_ERROR;  
    
  uint8_t UserByte1 = sensor->Init.InitParam.UserBytes.UserByte1;
  uint8_t UserByte2 = sensor->Init.InitParam.UserBytes.UserByte2;
  uint8_t UserByte3 = sensor->Init.InitParam.UserBytes.UserByte3;
  uint8_t UserByte4 = sensor->Init.InitParam.UserBytes.UserByte4;
  uint8_t UserByte12Cmp = 0;
  uint8_t UserByte34Cmp = 0;
   
  for(int i = 0; i < hdallas->onewire->RomCnt; i++)
  {    
    /* Проверка присутствует ли выбранный датчик на линии */
    result = DS18B20_ReadScratchpad(hdallas->onewire, (uint8_t *)&hdallas->ds_devices->DevAddr[i], (uint8_t *)&hdallas->scratchpad);
    if (result != HAL_OK)
      return result;
    
    /* Сравнение UserByte1 и UserByte2, если они не равны нулю */
    if(UserByte1 | UserByte2)
    {
      if( (hdallas->scratchpad.tHighRegister == UserByte1) &&
          (hdallas->scratchpad.tLowRegister == UserByte2))
      {
        UserByte12Cmp = 1;
      }
    }/* Если сравнение UserByte1 и UserByte2 не выбрано, то считаем что они совпадают */
    else
    {
      UserByte12Cmp = 1;
    }
    /* Сравнение UserByte3 и UserByte4, если они не равны нулю */
    if(UserByte3 | UserByte4)
    {
      if( (hdallas->scratchpad.UserByte3 == UserByte3) &&
          (hdallas->scratchpad.UserByte4 == UserByte4))
      {
        UserByte34Cmp = 1;
      }
    }/* Если сравнение UserByte3 и UserByte4 не выбрано, то считаем что они одинаковые */
    else
    {
      UserByte34Cmp = 1;
    }
    /* Если нашли нужный датчик - завершаем поиск */
    if(UserByte12Cmp && UserByte34Cmp)
    {
//      sensor->isInitialized = 1;
//      sensor->Init.init_func = (HAL_StatusTypeDef (*)())Dallas_SensorInitByUserBytes;
      result = Dallas_SensorInit(hdallas, sensor, &hdallas->ds_devices->DevAddr[i]);
      return result;
    }
  }
  
  sensor->sensROM = 0; 
  /* Возвращаем ошибку если не нашли */
  return HAL_ERROR;
}

/**
 * @brief  Инициализирует структуру датчика по порядковому номеру
 * @param  hdallas        Указатель на хендл для общения с датчиками
 * @param  sensor         Указатель на структуру датчика
 * @retval HAL Status
 * @details Порядковый номер датчика в списке найденных.
 *          Т.е. каким по счету этот датчик был найден
 */
HAL_StatusTypeDef Dallas_SensorInitByInd(DALLAS_HandleTypeDef *hdallas, DALLAS_SensorHandleTypeDef *sensor)
{
  HAL_StatusTypeDef result;
  
  if(hdallas == NULL)
    return HAL_ERROR;
  if(sensor == NULL)
    return HAL_ERROR;  

  result = Dallas_SensorInit(hdallas, sensor, &hdallas->ds_devices->DevAddr[sensor->Init.InitParam.Ind]);
  
  return result;
}

/**
 * @brief  Инициализирует датчик для работы
 * @param  hdallas        Указатель на хендл для общения с датчиками
 * @param  sensor     Указатель на структуру датчика
 * @param  ROM        ROM датчика, который надо инициализировать
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_SensorInit(DALLAS_HandleTypeDef *hdallas, DALLAS_SensorHandleTypeDef *sensor, uint8_t (*ROM)[DALLAS_ROM_SIZE])
{
  HAL_StatusTypeDef result;
  
  if(sensor == NULL)
    return HAL_ERROR;
  if(hdallas == 0)
    return HAL_ERROR;
  
  sensor->hdallas = hdallas;
  sensor->sensROM = 0;
  sensor->sensROM = *(uint64_t *)(ROM);
//  for(int i = 0; i < DALLAS_ROM_SIZE; i++)
//    sensor->sensROM |= ((uint64_t)(*ROM)[i] << (56 - 8*i));
  
  /* Проверка присутствует ли выбранный датчик на линии */
  result = Dallas_ReadScratchpad(sensor);
  if (result == HAL_OK)
  {
    /* Установка разрешения */
    result = DS18B20_SetResolution(hdallas->onewire, (uint8_t *)ROM, sensor->Init.Resolution);
    if (result == HAL_OK)
    {
      sensor->isInitialized = 1;
      return HAL_OK;
    }
    else
    {
      sensor->isInitialized = 0;
      return result;
    }
  }
  else
  {
    sensor->isInitialized = 0;
    return result;
  }
}

/**
 * @brief  Деинициализирует структуру датчика
 * @param  sensor         Указатель на структуру датчика
 * @retval HAL Status
 */
HAL_StatusTypeDef Dallas_SensorDeInit(DALLAS_SensorHandleTypeDef *sensor)
{
  if(sensor == NULL)
    return HAL_ERROR;
    
  memset(&sensor->f, 0, sizeof(sensor->f));
  sensor->isConnected = 0;
  sensor->isInitialized = 0;
  sensor->isLost = 0;
  sensor->temperature = 0;
  sensor->sensROM = 0;
  
  return HAL_OK;
}
