--[[
 BME280 Lua module
 Requires i2c and bme280_math module
 Written by Lukas Voborsky, @voborsky
]]

local bme280 = {}

-- bme280.setup()
-- bme280:setup()
-- bme280:read()
-- bme280:altitude()
-- bme280:dewpoint()
-- bme280:qfe2qnh()
-- bme280:startreadout()

local type, assert = type, assert
local table_concat, math_floor = table.concat, math.floor
local i2c_start, i2c_stop, i2c_address, i2c_read, i2c_write, i2c_TRANSMITTER, i2c_RECEIVER =
  i2c.start, i2c.stop, i2c.address, i2c.read, i2c.write, i2c.TRANSMITTER, i2c.RECEIVER
local bme280_math_setup, bme280_math_read, bme280_math_qfe2qnh, bme280_math_altitude, bme280_math_dewpoint =
  bme280_math.setup, bme280_math.read, bme280_math.qfe2qnh, bme280_math.altitude, bme280_math.dewpoint
local tmr_create, tmr_ALARM_SINGLE = tmr.create, tmr.ALARM_SINGLE


local BME280_I2C_ADDRESS1 = 0x76
local BME280_I2C_ADDRESS2 = 0x77

local BME280_REGISTER_CONTROL = 0xF4
local BME280_REGISTER_CONTROL_HUM = 0xF2
local BME280_REGISTER_CONFIG= 0xF5
local BME280_REGISTER_CHIPID = 0xD0

local BME280_REGISTER_DIG_T = 0x88  -- 0x88-0x8D ( 6)
local BME280_REGISTER_DIG_P = 0x8E  -- 0x8E-0x9F (18)
local BME280_REGISTER_DIG_H1 = 0xA1  -- 0xA1      ( 1)
local BME280_REGISTER_DIG_H2 = 0xE1  -- 0xE1-0xE7 ( 7)
local BME280_REGISTER_PRESS = 0xF7	 -- 0xF7-0xF9

-- local BME280_FORCED_MODE = 0x01

-- maximum measurement time in ms for maximum oversampling for all measures
-- 113 > 1.25 + 2.3*16 + 2.3*16 + 0.575 + 2.3*16 + 0.575 ms
local BME280_SAMPLING_DELAY =113

-- Local functions
local read_reg
local write_reg
local bme280_setup
local bme280_read
local bme280_startreadout

-- -- Note that the space between debug and the arglist is there for a reason
-- -- so that a simple global edit "   debug(" -> "-- debug(" or v.v. to
-- -- toggle debug compiled into the module.
-- local print, node_heap = print, node.heap
-- local function debug (fmt, ...) -- upval: cnt (, print, node_heap)
  -- if not bme280.debug then return end
  -- if (...) then fmt = fmt:format(...) end
  -- print("[bme280]", node_heap(), fmt)
-- end

--------------------------- Set up the bme280 object ----------------------------
--       bme280 has method setup to create the sensor object and setup the sensor
--       object created by bme280.setup() has methods: read, qfe2qnh, altitude, dewpoint
---------------------------------------------------------------------------------

function bme280.setup(id, addr, temp_oss, press_oss, humi_oss, power_mode, inactive_duration, IIR_filter, full_init)
  return bme280_setup(nil, id,
    addr, temp_oss, press_oss, humi_oss, power_mode, inactive_duration, IIR_filter, full_init)
end

------------------------------------------------------------------------------
function bme280_setup(self, id, addr,
  temp_oss, press_oss, humi_oss, power_mode, inactive_duration, IIR_filter, full_init)

  addr = (addr==2) and BME280_I2C_ADDRESS2 or BME280_I2C_ADDRESS1
  full_init = full_init or true

  -- debug("%d %x %d", id, addr, BME280_REGISTER_CHIPID)
  local chipid = read_reg(id, addr, BME280_REGISTER_CHIPID, 1)
  if not chipid then
    return nil
  end
  -- debug("chip_id: %x", chipid:byte(1))
  local isbme = (chipid:byte(1) == 0x60)

  local buf = {}
  buf[1] = read_reg(id, addr, BME280_REGISTER_DIG_T, 6)
  buf[2] = read_reg(id, addr, BME280_REGISTER_DIG_P, 18)
  if (isbme) then
    buf[3] = read_reg(id, addr, BME280_REGISTER_DIG_H1, 1)
    buf[4] = read_reg(id, addr, BME280_REGISTER_DIG_H2, 7)
  end

  local sensor, config = bme280_math_setup(table_concat(buf),
    temp_oss, press_oss, humi_oss, power_mode, inactive_duration, IIR_filter)
  self = self  or {
    setup = bme280_setup,
    read = bme280_read,
    startreadout = bme280_startreadout,
    qfe2qnh = bme280_math_qfe2qnh,
    altitude = bme280_math_altitude,
    dewpoint = bme280_math_dewpoint
  }
  self.id, self.addr = id, addr
  self._sensor, self._config, self._isbme = sensor, config, isbme

  if (full_init) then
    write_reg(id, addr, BME280_REGISTER_CONFIG, config[1])
    if (isbme) then write_reg(id, addr, BME280_REGISTER_CONTROL_HUM, config[2]) end
    write_reg(id, addr, BME280_REGISTER_CONTROL, config[3])
  end

  return self
end

function bme280_read(self, alt)
  local buf = read_reg(self.id, self.addr, BME280_REGISTER_PRESS, 8) -- registers are P[3], T[3], H[2]
  if buf then
    return bme280_math_read(self._sensor, buf, alt)
  else
    return nil
  end
end

function bme280_startreadout(self, callback, delay, alt)
  assert(type(callback) == "function", "invalid callback parameter")

  delay = delay or BME280_SAMPLING_DELAY

  if self._isbme then write_reg(self.id, self.addr, BME280_REGISTER_CONTROL_HUM, self._config[2]) end
  write_reg(self.id, self.addr, BME280_REGISTER_CONTROL, math_floor(self._config[3]:byte(1)/4)+ 1)
    -- math_floor(self._config[3]:byte(1)/4)+ 1
    --   an awful way to avoid bit operations but calculate (config[3] & 0xFC) | BME280_FORCED_MODE
    -- Lua 5.3 integer division // would be more suitable

  tmr_create():alarm(delay, tmr_ALARM_SINGLE,
    function()
      callback(bme280_read(self, alt))
    end
  )
end

function write_reg(id, dev_addr, reg_addr, data)
  i2c_start(id)
  if not i2c_address(id, dev_addr, i2c_TRANSMITTER) then
    -- debug("No ACK on address: %x", dev_addr)
    return nil
  end
  i2c_write(id, reg_addr)
  local c = i2c_write(id, data)
  i2c_stop(id)
  return c
end

function read_reg(id, dev_addr, reg_addr, n)
  i2c_start(id)
  if not i2c_address(id, dev_addr, i2c_TRANSMITTER) then
    -- debug("No ACK on address: %x", dev_addr)
    return nil
  end
  i2c_write(id, reg_addr)
  i2c_stop(id)
  i2c_start(id)
  i2c_address(id, dev_addr, i2c_RECEIVER)
  local c = i2c_read(id, n)
  i2c_stop(id)
  return c
end

------------------------------------------------ -----------------------------
return bme280
