Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@mitchellhenke
Created September 16, 2019 14:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mitchellhenke/5ad58a86e7e56c5c056d588f54175449 to your computer and use it in GitHub Desktop.
Save mitchellhenke/5ad58a86e7e56c5c056d588f54175449 to your computer and use it in GitHub Desktop.
defmodule HomeSensor.BME280 do
use GenServer
use Bitwise
require Logger
alias Circuits.I2C
@address 0x77
@chip_id 0x60
@chip_id_register 0xD0
@dig_t1_register 0x88
@dig_h1_register 0xA1
@dig_h2_register 0xE1
# @dig_h3_register 0xE3
# @dig_h4_register 0xE4
# @dig_h5_register 0xE5
# @dig_h6_register 0xE7
@soft_reset_register 0xE0
@ctrl_hum_register 0xF2
@status_register 0xF3
@ctrl_meas_register 0xF4
@config_register 0xF5
@pressure_data_register 0xF7
# @temp_data_register 0xFA
# @humid_data_register 0xFD
@iir_filter_disable 0x00
# @overscan_disable 0x00
@overscan_x1 0x01
@mode_sleep 0x00
@mode_force 0x01
# @mode_normal 0x03
# 1000ms
# @standby_tc_1000 0x05
defstruct i2c_ref: nil,
mode: @mode_sleep,
iir: @iir_filter_disable,
overscan: @overscan_x1,
temp_calib: {nil, nil, nil},
pressure_calib: {nil, nil, nil, nil, nil, nil, nil, nil, nil},
humidity_calib: {nil, nil, nil, nil, nil, nil}
def start_link(_ \\ nil) do
GenServer.start_link(__MODULE__, nil)
end
def read_temperature(pid) do
GenServer.call(pid, :read_temperature)
end
@impl true
def init(_state) do
{:ok, ref} = I2C.open("i2c-1")
Process.send_after(self(), :initialize, 0)
Process.send_after(self(), :read_temperature, 0)
{:ok, %__MODULE__{i2c_ref: ref}}
end
@impl true
def handle_call({:write_read, register, bytes}, _from, %{i2c_ref: ref} = state) do
response = I2C.write_read!(ref, @address, <<register>>, bytes)
{:reply, response, state}
end
@impl true
def handle_call(:get, _from, state) do
{:reply, state, state}
end
@impl true
def handle_call(:read_temperature, _from, %{i2c_ref: ref} = state) do
:ok = I2C.write(ref, @address, <<@ctrl_hum_register, @overscan_x1>>)
ctrl_meas = (@overscan_x1 <<< 5) + (@overscan_x1 <<< 2) + @mode_force
:ok = I2C.write(ref, @address, <<@ctrl_meas_register, ctrl_meas>>)
wait_for_status(ref)
{humidity, pressure_hpa, temperature_f} =
read(ref, {state.temp_calib, state.pressure_calib, state.humidity_calib})
{:reply, {humidity, pressure_hpa, temperature_f}, state}
end
@impl true
def handle_info(:read_temperature, %{i2c_ref: ref} = state) do
:ok = I2C.write(ref, @address, <<@ctrl_hum_register, @overscan_x1>>)
ctrl_meas = (@overscan_x1 <<< 5) + (@overscan_x1 <<< 2) + @mode_force
:ok = I2C.write(ref, @address, <<@ctrl_meas_register, ctrl_meas>>)
wait_for_status(ref)
{humidity, pressure_hpa, temperature_f} =
read(ref, {state.temp_calib, state.pressure_calib, state.humidity_calib})
data =
"weather,source=pi,location=downstairs temperature=#{temperature_f},humidity=#{humidity},pressure=#{
pressure_hpa
}"
auth = "Basic #{Base.encode64("username:password")}"
r =
HTTPoison.post("https://influxdb_server.domain.com/write?db=db_name", data, [
{"Authorization", auth}
])
Process.send_after(self(), :read_temperature, 60_000)
{:noreply, state}
end
@impl true
def handle_info(:initialize, %{i2c_ref: ref} = state) do
with {:ok, <<@chip_id>>} <- I2C.write_read(ref, @address, <<@chip_id_register>>, 1),
:ok <- I2C.write(ref, @address, <<@soft_reset_register, 0xB6>>),
:ok <- :timer.sleep(4),
{:ok,
<<t1::little-unsigned-integer-size(16), t2::little-signed-integer-size(16),
t3::little-signed-integer-size(16), p1::little-unsigned-integer-size(16),
p2::little-signed-integer-size(16), p3::little-signed-integer-size(16),
p4::little-signed-integer-size(16), p5::little-signed-integer-size(16),
p6::little-signed-integer-size(16), p7::little-signed-integer-size(16),
p8::little-signed-integer-size(16),
p9::little-signed-integer-size(16)>>} <-
I2C.write_read(ref, @address, <<@dig_t1_register>>, 24),
{:ok, <<h1::little-unsigned-integer-size(8)>>} <-
I2C.write_read(ref, @address, <<@dig_h1_register>>, 1),
{:ok,
<<h2::little-signed-integer-size(16), h3::little-unsigned-integer-size(8),
h4::little-signed-integer-size(8), h5::little-unsigned-integer-size(8),
h6::little-signed-integer-size(8),
h7::little-signed-integer-size(8)>>} <-
I2C.write_read(ref, @address, <<@dig_h2_register>>, 7),
# WRITE_CONFIG
# Forced Mode and IIR disabled
:ok <- I2C.write(ref, @address, <<@ctrl_hum_register, @overscan_x1>>),
ctrl_meas <- (@overscan_x1 <<< 5) + (@overscan_x1 <<< 2) + @mode_force,
:ok <- I2C.write(ref, @address, <<@ctrl_meas_register, ctrl_meas>>),
:ok <- I2C.write(ref, @address, <<@config_register, 0x00>>) do
h2 = h2 / 1
h3 = h3 / 1
h4 = h4 <<< 4 ||| (h5 &&& 0xF)
h5 = h6 <<< 4 ||| h5 >>> 4
h6 = h7 / 1
state = %{
state
| temp_calib: {t1, t2, t3},
pressure_calib: {p1, p2, p3, p4, p5, p6, p7, p8, p9},
humidity_calib: {h1, h2, h3, h4, h5, h6}
}
{:noreply, state}
else
e ->
Logger.error("Failed to initialize - #{inspect(e)}")
{:noreply, state}
end
end
def handle_info({:ssl_closed, {:sslsocket, _, _}}, state) do
{:noreply, state}
end
def read(ref, {temp_calib, pressure_calib, humidity_calib}) do
<<p1::little-unsigned-integer-size(8), p2::little-unsigned-integer-size(8),
p3::little-unsigned-integer-size(8), t1::little-unsigned-integer-size(8),
t2::little-unsigned-integer-size(8), t3::little-unsigned-integer-size(8),
h1::little-unsigned-integer-size(8),
h2::little-unsigned-integer-size(8)>> =
I2C.write_read!(ref, @address, <<@pressure_data_register>>, 8)
{temp_calib1, temp_calib2, temp_calib3} = temp_calib
raw_temperature = ((t1 * 256.0 + t2) * 256.0 + t3) / 16
var1 = (raw_temperature / 16384.0 - temp_calib1 / 1024.0) * temp_calib2
var2 =
(raw_temperature / 131_072.0 - temp_calib1 / 8192.0) *
(raw_temperature / 131_072.0 - temp_calib1 / 8192.0) * temp_calib3
t_fine = var1 + var2
temperature_c = t_fine / 5120.0
temperature_f = Float.round(temperature_c * 9 / 5 + 32.0, 2)
{p_calib0, p_calib1, p_calib2, p_calib3, p_calib4, p_calib5, p_calib6, p_calib7, p_calib8} =
pressure_calib
raw_pressure = ((p1 * 256.0 + p2) * 256.0 + p3) / 16
var1 = t_fine / 2.0 - 64000.0
var2 = var1 * var1 * p_calib5 / 32768.0
var2 = var2 + var1 * p_calib4 * 2.0
var2 = var2 / 4.0 + p_calib3 * 65536.0
var3 = p_calib2 * var1 * var1 / 524_288.0
var1 = (var3 + p_calib1 * var1) / 524_288.0
var1 = (1.0 + var1 / 32768.0) * p_calib0
pressure = 1_048_576.0 - raw_pressure
pressure = (pressure - var2 / 4096.0) * 6250.0 / var1
var1 = p_calib8 * pressure * pressure / 2_147_483_648.0
var2 = pressure * p_calib7 / 32768.0
pressure = pressure + (var1 + var2 + p_calib6) / 16.0
# hectoPascals
pressure_hpa = pressure / 100
# HUMIDITY
{h_calib0, h_calib1, h_calib2, h_calib3, h_calib4, h_calib5} = humidity_calib
adc = h1 <<< 8 ||| h2
var1 = t_fine - 76800.0
var2 = h_calib3 * 64.0 + h_calib4 / 16384.0 * var1
var3 = adc - var2
var4 = h_calib1 / 65536.0
var5 = 1.0 + h_calib2 / 67_108_864.0 * var1
var6 = 1.0 + h_calib5 / 67_108_864.0 * var1 * var5
var6 = var3 * var4 * (var5 * var6)
humidity = var6 * (1.0 - h_calib0 * var6 / 524_288.0) / 100
{humidity, pressure_hpa, temperature_f}
end
def wait_for_status(ref) do
<<status::little-unsigned-integer-size(8)>> =
I2C.write_read!(ref, @address, <<@status_register>>, 1)
Logger.info("Status is #{inspect(status)}")
if (status &&& 0x08) > 0 do
Logger.error("Status is not ready, it is: #{inspect(status)}")
:timer.sleep(2)
wait_for_status(ref)
else
:ok
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment