/*
 * Copyright 2018 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "mgos.h"
#include "mgos_sht31_internal.h"
#include "mgos_i2c.h"

// Datasheet:
// https://cdn-shop.adafruit.com/product-files/2857/Sensirion_Humidity_SHT3x_Datasheet_digital-767294.pdf

// Private functions follow
static bool mgos_sht31_cmd(struct mgos_sht31 *sensor, uint16_t cmd) {
  uint8_t data[2];

  if (!sensor || !sensor->i2c)
    return false;

  data[0]=cmd>>8;
  data[1]=cmd&0xFF;
  if (!mgos_i2c_write(sensor->i2c, sensor->i2caddr, data, 2, true)) {
    LOG(LL_ERROR, ("I2C=0x%02x cmd=%u (0x%04x) write error", sensor->i2caddr, cmd, cmd));
    return false;
  }
  LOG(LL_DEBUG, ("I2C=0x%02x cmd=%u (0x%04x) write success", sensor->i2caddr, cmd, cmd));

  return true;
}


static uint8_t crc8(const uint8_t *data, int len) {
  const uint8_t poly=0x31;
  uint8_t crc=0xFF;

  for (int j=len; j; --j) {
    crc^=*data++;
    for (int i=8; i; --i)
      crc=(crc & 0x80) ? (crc << 1) ^ poly : (crc << 1);
  }
  return crc;
}


static uint16_t mgos_sht31_status(struct mgos_sht31 *sensor) {
  uint8_t data[3];
  uint16_t value;

  mgos_sht31_cmd(sensor, MGOS_SHT31_READSTATUS);
  if (!mgos_i2c_read(sensor->i2c, sensor->i2caddr, data, 3, true))
    return 0;

  // Check CRC8 checksums
  if ((data[2]!=crc8(data, 2)))
    return 0;
  value=(data[0]<<8) + data[1];

  return value;
}


// Private functions end

// Public functions follow
struct mgos_sht31 *mgos_sht31_create(struct mgos_i2c *i2c, uint8_t i2caddr) {
  struct mgos_sht31 *sensor;
  uint16_t status0, status1, status2;

  if (!i2c) return NULL;

  sensor=calloc(1, sizeof(struct mgos_sht31));
  if (!sensor) return NULL;
  sensor->i2caddr=i2caddr;
  sensor->i2c=i2c;
  sensor->last_read_time=0;

  mgos_sht31_cmd(sensor, MGOS_SHT31_SOFTRESET);

  // Toggle heater on and off, which shows up in status register bit 13 (0=Off, 1=On)
                                 // heater is off, bit13 is 0
  status0=mgos_sht31_status(sensor);
  mgos_sht31_cmd(sensor, MGOS_SHT31_HEATEREN);
                                 // heater is on, bit13 is 1
  status1=mgos_sht31_status(sensor);
  mgos_sht31_cmd(sensor, MGOS_SHT31_HEATERDIS);
                                 // heater is off, bit13 is 0
  status2=mgos_sht31_status(sensor);

  if (((status0 & 0x2000) == 0) && ((status1 & 0x2000) != 0) && ((status2 & 0x2000) == 0)) {
    LOG(LL_INFO, ("SHT31 created at I2C 0x%02x", i2caddr));
    return sensor;
  }

  LOG(LL_ERROR, ("Failed to create SHT31 at I2C 0x%02x", i2caddr));
  free(sensor);
  return NULL;
}


void mgos_sht31_destroy(struct mgos_sht31 **sensor) {
  if (!*sensor) return;
  free (*sensor);
  *sensor=NULL;
  return;
}


bool mgos_sht31_read(struct mgos_sht31 *sensor) {
  double now = mg_time();

  if (!sensor || !sensor->i2c)
    return false;

  if (now - sensor->last_read_time < MGOS_SHT31_READ_DELAY) {
    return true;
  }
  // Read out sensor data here
  //
  uint8_t data[6];
  float humidity, temperature;

  mgos_sht31_cmd(sensor, MGOS_SHT31_MEAS_HIGHREP);

  mgos_usleep(500);

  if (!mgos_i2c_read(sensor->i2c, sensor->i2caddr, data, 6, true))
    return false;

  // Check CRC8 checksums
  if ((data[2]!=crc8(data, 2)) || (data[5]!=crc8(data+3, 2)))
    return false;

  temperature = data[0]*256 + data[1];
  temperature *= 175;
  temperature /= 0xffff;
  temperature -= 45;
  sensor->temperature = temperature;

  humidity = data[3] * 256 + data[4];
  humidity *= 100;
  humidity /= 0xFFFF;
  sensor->humidity = humidity;

  LOG(LL_DEBUG, ("temperature=%.2fC humidity=%.1f%%", sensor->temperature, sensor->humidity));
  sensor->last_read_time=now;
  return true;
}


float mgos_sht31_getTemperature(struct mgos_sht31 *sensor) {
  if (!mgos_sht31_read(sensor)) return NAN;

  return sensor->temperature;
}


float mgos_sht31_getHumidity(struct mgos_sht31 *sensor) {
  if (!mgos_sht31_read(sensor)) return NAN;

  return sensor->humidity;
}


bool mgos_sht31_i2c_init(void) {
  return true;
}


// Public functions end
