/*
  xsns_106_gdk101.ino - Support for GDK101 gamma radiation sensor

  Copyright (C) 2019  Petr Novacek

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifdef USE_I2C
#ifdef USE_GDK101

/*********************************************************************************************\
 * GDK101 gamma radiation sensor ftlab.co.kr
 *
 * For background information see http://allsmartlab.com/eng/294-2/ and AN_GDK101_V1.1_I2C.pdf
 *
 * I2C Address:
 * A0 Short, A1 Short : 0x18
 * A0 Open,  A1 Short : 0x19
 * A0 Short, A1 Open  : 0x1A
 * A0 Open,  A1 Open  : 0x1B
\*********************************************************************************************/

#define XSNS_106                    106

#define XI2C_79                     79  // See I2CDEVICES.md

// I2C adresses
#define GDK101_ADDRESS1             0x18
#define GDK101_ADDRESS2             0x19
#define GDK101_ADDRESS3             0x1A
#define GDK101_ADDRESS4             0x1B

// I2C register
#define GDK101_RESET                0xA0
#define GDK101_READ_STATUS          0xB0
#define GDK101_READ_MEASURING_TIME  0xB1
#define GDK101_READ_10MIN_AVG       0xB2
#define GDK101_READ_1MIN_AVG        0xB3
#define GDK101_READ_FIRMWARE        0xB4

// gdk101 variables
typedef enum {
 GDK_STATUS_READY,
 GDK_STATUS_UNDER_10_MINUTES,
 GDK_STATUS_NORMAL,
 GDK_STATUS_INVALID,
} gdk_status_t;

typedef struct gdk_status_data {
  gdk_status_t status;
  bool vibration;
} gdk_status_data_t;

typedef struct gdk_measuring_time {
  uint8_t min;
  uint8_t sec;
} gdk_measuring_time_t;

typedef struct gdk_fW_version {
  uint8_t major;
  uint8_t minor;
} gdk_fW_version_t;

typedef struct gdk_avg_dose {
  uint8_t integral;
  uint8_t fractional;
} gdk_avg_dose_t;

struct GDK {
  uint32_t time;
//  gdk_measuring_time_t time = {0, 0};
  gdk_fW_version_t version;
  gdk_avg_dose_t ard_10min;
  gdk_avg_dose_t ard_1min;
  gdk_status_data_t status = {GDK_STATUS_INVALID, false};
  uint8_t address;
  uint8_t bus;
  uint8_t evr10 = 0;
  bool ready = false;
} Gdk;

/********************************************************************************************/

void Gdk101ErrLog(bool ret) {
  if (!ret) {
     DEBUG_SENSOR_LOG("GDK101: I2C read error");
  }
}

void Gdk101Detect(void) {
  for (Gdk.bus = 0; Gdk.bus < MAX_I2C; Gdk.bus++) {
    for (Gdk.address = GDK101_ADDRESS1; Gdk.address < GDK101_ADDRESS4 +1; Gdk.address++) {
      if (!I2cSetDevice(Gdk.address, Gdk.bus)) {
        continue;  // Do not make the next step without a confirmed device on the bus
      }
      bool reset = false;
      bool ret = Gdk101Reset(&reset);
      if (ret) {
        if (reset) {
          delay(10);
          uint8_t buf[2];
          ret = Gdk101Firmware(&Gdk.version);
          Gdk101ErrLog(ret);
          I2cSetActiveFound(Gdk.address, "GDK101", Gdk.bus);
          AddLog(LOG_LEVEL_DEBUG, PSTR("GDK: GDK101 v%d.%d"), Gdk.version.major, Gdk.version.minor);
          Gdk.ready = true;
          return;
        }
      }
    }
  }
}

bool Gdk101Reset(bool *reset) {
  uint8_t buf;
  bool ret = I2cValidRead8(&buf, Gdk.address, GDK101_RESET, Gdk.bus);
  if (ret) {
    *reset = (bool) buf;  // 0 = Reset fail, 1 = Reset success
  }
  return ret;
}

bool Gdk101Firmware(struct gdk_fW_version *fw) {
  uint8_t buf[2];
  bool ret = I2cValidRead16LE((uint16_t*) buf, Gdk.address, GDK101_READ_FIRMWARE, Gdk.bus);
  if (ret) {
    fw->major = buf[0];  // 0
    fw->minor = buf[1];  // 6
  }
  return ret;
}

bool Gdk101Ard10min(struct gdk_avg_dose *avg) {
  uint8_t buf[2];
  bool ret = I2cValidRead16LE((uint16_t*) buf, Gdk.address, GDK101_READ_10MIN_AVG, Gdk.bus);
  if (ret) {
    avg->integral = buf[0];
    avg->fractional = buf[1];
  }
  return ret;
}

bool Gdk101Ard1min(struct gdk_avg_dose *avg) {
  uint8_t buf[2];
  bool ret = I2cValidRead16LE((uint16_t*) buf, Gdk.address, GDK101_READ_1MIN_AVG, Gdk.bus);
  if (ret) {
    avg->integral = buf[0];
    avg->fractional = buf[1];
  }
  return ret;
}

bool Gdk101Status(struct gdk_status_data *status) {
  uint8_t buf[2];
  bool ret = I2cValidRead16LE((uint16_t*) buf, Gdk.address, GDK101_READ_STATUS, Gdk.bus);
  if (ret) {
    status->status = (gdk_status_t) buf[0];  // 0 = Ready, 1 = 10min Waiting, 2 = Normal
    status->vibration = (bool) buf[1];       // 0 = Off, 1 = On
  }
  return ret;
}

bool Gdk101MeasuringTime(struct gdk_measuring_time *meas_time) {
  // Can return 255 minutes and 60 seconds - Not used in Application Note AN_GDK101_V1.1
  uint8_t buf[2];
  bool ret = I2cValidRead16LE((uint16_t*) buf, Gdk.address, GDK101_READ_MEASURING_TIME, Gdk.bus);
  if (ret) {
    meas_time->min = buf[0];
    meas_time->sec = buf[1];
  }
  return ret;
}

void Gdk101EverySecond(void) {
  bool ret = Gdk101Status(&Gdk.status);
  Gdk101ErrLog(ret);
  if (Gdk.status.status > GDK_STATUS_READY) {
    Gdk.time++;
  } else {
    Gdk.time = 0;
  }

  Gdk.evr10++;
  if (10 == Gdk.evr10) {
    Gdk.evr10 = 0;

    ret = Gdk101Ard10min(&Gdk.ard_10min);
    Gdk101ErrLog(ret);
    ret = Gdk101Ard1min(&Gdk.ard_1min);
    Gdk101ErrLog(ret);
//    ret = Gdk101MeasuringTime(&Gdk.time);
//    Gdk101ErrLog(ret);
  }
}

#ifdef USE_WEBSERVER
const char HTTP_GDK101_FW[] PROGMEM = "{s}%s " "FW Version" "{m}%d.%d{e}";
const char HTTP_GDK101_STATUS[] PROGMEM = "{s}%s " "Status" "{m}%d{e}";
const char HTTP_GDK101_VIB_STATUS[] PROGMEM = "{s}%s " "Vibration Status" "{m}%d{e}";
//const char HTTP_GDK101_MEAS_TIME[] PROGMEM = "{s}%s " "Measurement Time" "{m}%d:%02d{e}";
const char HTTP_GDK101_MEAS_TIME[] PROGMEM = "{s}%s " "Measurement" "{m}%s{e}";
#endif  // USE_WEBSERVER


void Gdk101Show(uint8_t json) {
  char types[16];
  strcpy_P(types, PSTR("GDK101"));

  if (json) {
    ResponseAppend_P(PSTR(",\"%s\":{"), types);
    ResponseAppend_P(PSTR("\"Firmware\":%i.%i,"), Gdk.version.major, Gdk.version.minor);
    ResponseAppend_P(PSTR("\"RadiationAvg10Min\":%i.%02i,"), Gdk.ard_10min.integral, Gdk.ard_10min.fractional);
    ResponseAppend_P(PSTR("\"RadiationAvg1Min\":%i.%02i,"), Gdk.ard_1min.integral, Gdk.ard_1min.fractional);
    ResponseAppend_P(PSTR("\"Status\":%i,"), Gdk.status.status);
    ResponseAppend_P(PSTR("\"Vibration\":%i,"), Gdk.status.vibration);
//    ResponseAppend_P(PSTR("\"MeasTime\":\"%i:%02i\""), Gdk.time.min, Gdk.time.sec);
    ResponseAppend_P(PSTR("\"Measurement\":\"%s\""), GetDuration(Gdk.time).c_str());
    ResponseJsonEnd();
#ifdef USE_WEBSERVER
  } else {
#ifdef GDK101_SHOW_FW_VERSION
    WSContentSend_PD(HTTP_GDK101_FW, types, Gdk.version.major, Gdk.version.minor);
#endif
    WSContentSend_PD(HTTP_SNS_AVG_RAD_DOSE, types, "10", Gdk.ard_10min.integral, Gdk.ard_10min.fractional);
    WSContentSend_PD(HTTP_SNS_AVG_RAD_DOSE, types, "1", Gdk.ard_1min.integral, Gdk.ard_1min.fractional);
#ifdef GDK101_SHOW_STATUS
    WSContentSend_PD(HTTP_GDK101_STATUS, types, Gdk.status.status);
#endif
#ifdef GDK101_SHOW_VIBRATION_STATUS
    WSContentSend_PD(HTTP_GDK101_VIB_STATUS, types, Gdk.status.vibration);
#endif
//#ifdef GDK101_SHOW_MEAS_TIME
//    WSContentSend_PD(HTTP_GDK101_MEAS_TIME, types, Gdk.time.min, Gdk.time.sec);
    WSContentSend_PD(HTTP_GDK101_MEAS_TIME, types, GetDuration(Gdk.time).c_str());
//#endif
#endif  // USE_WEBSERVER
  }
}

/*********************************************************************************************\
   Interface
\*********************************************************************************************/

bool Xsns106(uint32_t function) {
  if (!I2cEnabled(XI2C_79)) { return false; }

  bool result = false;

  if (!Gdk.ready) {
    if ((TasmotaGlobal.uptime < 20) && (FUNC_EVERY_SECOND == function)) {
      Gdk101Detect();  // GDK101 power on ready can take 8 seconds so try every second for up to 20 seconds
    }
  } else {
    switch (function) {
      case FUNC_EVERY_SECOND:
        Gdk101EverySecond();
        break;
      case FUNC_JSON_APPEND:
        Gdk101Show(1);
        break;
#ifdef USE_WEBSERVER
      case FUNC_WEB_SENSOR:
        Gdk101Show(0);
        break;
#endif  // USE_WEBSERVER
    }
  }
  return result;
}

#endif  // USE_GDK
#endif  // USE_I2C
