Skip to content

Instantly share code, notes, and snippets.

@ersatzavian
Created July 7, 2014 18:09
Show Gist options
  • Save ersatzavian/bc93eaacf8201c1244ef to your computer and use it in GitHub Desktop.
Save ersatzavian/bc93eaacf8201c1244ef to your computer and use it in GitHub Desktop.
Build a basic "thermostat" with the imp003 EVB
// Copyright (c) 2014 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT
// Agent Code
target <- 30.0;
current <- "Unkown";
device.send("target", target);
device.on("current", function(t){ current <- format("%0.1f C",t)});
http.onrequest(function(request,res){
if(request.method == "POST"){
local post = http.urldecode(request.body);
if("target" in post){
target = post.target.tofloat();
device.send("target", target);
}
}
local html = "<html><body><form method='post'>Current Temperature: "+current+"<input type='submit' value='Refresh'><br><form method='post'>Set Temperature: <input name='target' type='number' min='20' max='40' step='0.5' value='"+target+"'><input type='submit'></form></body></html>";
res.send(200,html);
});
// Copyright (c) 2014 Electric Imp
// This file is licensed under the MIT License
// http://opensource.org/licenses/MIT
// Device Code
// Temperature polling period (seconds)
const POLLING_PERIOD = 1;
target <- 30.0; // thermostat "target" temperature (setpoint)
class TMP112 {
// static values (address offsets)
static TEMP_REG = 0x00;
static CONF_REG = 0x01;
static T_LOW_REG = 0x02;
static T_HIGH_REG = 0x03;
// Send this value on general-call address (0x00) to reset device
static RESET_VAL = 0x06;
// ADC resolution in degrees C
static DEG_PER_COUNT = 0.0625;
// i2c address
addr = null;
// i2c bus (passed into constructor)
i2c = null;
// interrupt pin (configurable)
int_pin = null;
// configuration register value
conf = null;
// Default temp thresholds
T_LOW = 75; // Celsius
T_HIGH = 80;
// Default mode
EXTENDEDMODE = false;
SHUTDOWN = false;
// conversion ready flag
CONV_READY = false;
// interrupt state - some pins require us to poll the interrupt pin
LAST_INT_STATE = null;
POLL_INTERVAL = null;
INT_CALLBACK = null;
// generic temp interrupt
function tmp112_int(state) {S
server.log("Device: TMP112 Interrupt Occurred. State = "+state);
}
/*
* Class Constructor. Takes 3 to 5 arguments:
* _i2c: Pre-configured I2C Bus
* _addr: I2C Slave Address for device. 8-bit address.
* _int_pin: Pin to which ALERT line is connected
* _alert_poll_interval: Interval (in seconds) at which to poll the ALERT pin (optional)
* _alert_callback: Callback to call on ALERT pin state changes (optional)
*/
constructor(_i2c, _addr, _int_pin, _alert_poll_interval = 1, _alert_callback = null) {
this.addr = _addr;
this.i2c = _i2c;
this.int_pin = _int_pin;
/*
* Top-level program should pass in Pre-configured I2C bus.
* This is done to allow multiple devices to be constructed on the bus
* without reconfiguring the bus with each instantiation and causing conflict.
*/
this.int_pin.configure(DIGITAL_IN);
LAST_INT_STATE = this.int_pin.read();
POLL_INTERVAL = _alert_poll_interval;
if (_alert_callback) {
INT_CALLBACK = _alert_callback;
} else {
INT_CALLBACK = this.tmp112_int;
}
read_conf();
}
/*
* Check for state changes on the ALERT pin.
*
* Not all imp pins allow state-change callbacks, so ALERT pin interrupts are implemented with polling
*
*/
function poll_interrupt() {
imp.wakeup(POLL_INTERVAL, poll_interrupt);
local int_state = int_pin.read();
if (int_state != LAST_INT_STATE) {
LAST_INT_STATE = int_state;
INT_CALLBACK(state);
}
}
/*
* Take the 2's complement of a value
*
* Required for Temp Registers
*
* Input:
* value: number to take the 2's complement of
* mask: mask to select which bits should be complemented
*
* Return:
* The 2's complement of the original value masked with the mask
*/
function twos_comp(value, mask) {
value = ~(value & mask) + 1;
return value & mask;
}
/*
* General-call Reset.
* Note that this may reset other devices on an i2c bus.
*
* Logging is included to prevent this from silently affecting other devices
*/
function reset() {
server.log("TMP112 Class issuing General-Call Reset on I2C Bus.");
i2c.write(0x00,format("%c",RESET_VAL));
// update the configuration register
read_conf();
// reset the thresholds
T_LOW = 75;
T_HIGH = 80;
}
/*
* Read the TMP112 Configuration Register
* This updates several class variables:
* - EXTENDEDMODE (determines if the device is in 13-bit extended mode)
* - SHUTDOWN (determines if the device is in low power shutdown mode / one-shot mode)
* - CONV_READY (determines if the device is done with last conversion, if in one-shot mode)
*/
function read_conf() {
conf = i2c.read(addr,format("%c",CONF_REG), 2);
// Extended Mode
if (conf[1] & 0x10) {
EXTENDEDMODE = true;
} else {
EXTENDEDMODE = false;
}
if (conf[0] & 0x01) {
SHUTDOWN = true;
} else {
SHUTDOWN = false;
}
if (conf[1] & 0x80) {
CONV_READY = true;
} else {
CONV_READY = false;
}
}
/*
* Read, parse and log the current state of each field in the configuration register
*
*/
function print_conf() {
conf = i2c.read(addr,format("%c",CONF_REG), 2);
server.log(format("TMP112 Conf Reg at 0x%02x: %02x%02x",addr,conf[0],conf[1]));
// Extended Mode
if (conf[1] & 0x10) {
server.log("TMP112 Extended Mode Enabled.");
} else {
server.log("TMP112 Extended Mode Disabled.");
}
// Shutdown Mode
if (conf[0] & 0x01) {
server.log("TMP112 Shutdown Enabled.");
}
else {
server.log("TMP112 Shutdown Disabled.");
}
// One-shot Bit (Only care in shutdown mode)
if (conf[0] & 0x80) {
server.log("TMP112 One-shot Bit Set.");
} else {
server.log("TMP112 One-shot Bit Not Set.");
}
// Thermostat or Comparator Mode
if (conf[0] & 0x02) {
server.log("TMP112 in Interrupt Mode.");
} else {
server.log("TMP112 in Comparator Mode.");
}
// Alert Polarity
if (conf[0] & 0x04) {
server.log("TMP112 Alert Pin Polarity Active-High.");
} else {
server.log("TMP112 Alert Pin Polarity Active-Low.");
}
// Alert Pin
if (int_pin.read()) {
if (conf[0] & 0x04) {
server.log("TMP112 Alert Pin Asserted.");
} else {
server.log("TMP112 Alert Pin Not Asserted.");
}
} else {
if (conf[0] & 0x04) {
server.log("TMP112 Alert Pin Not Asserted.");
} else {
server.log("TMP112 Alert Pin Asserted.");
}
}
// Alert Bit
if (conf[1] & 0x20) {
server.log("TMP112 Alert Bit 1");
} else {
server.log("TMP112 Alert Bit: 0");
}
// Conversion Rate
local cr = (conf[1] & 0xC0) >> 6;
switch (cr) {
case 0:
server.log("TMP112 Conversion Rate Set to 0.25 Hz.");
break;
case 1:
server.log("TMP112 Conversion Rate Set to 1 Hz.");
break;
case 2:
server.log("TMP112 Conversion Rate Set to 4 Hz.");
break;
case 3:
server.log("TMP112 Conversion Rate Set to 8 Hz.");
break;
default:
server.error("TMP112 Conversion Rate Invalid: "+format("0x%02x",cr));
}
// Fault Queue
local fq = (conf[0] & 0x18) >> 3;
server.log(format("TMP112 Fault Queue shows %d Consecutive Fault(s).", fq));
}
/*
* Enter or exit low-power shutdown mode
* In shutdown mode, device does one-shot conversions
*
* Device comes up with shutdown disabled by default (in continuous-conversion/thermostat mode)
*
* Input:
* State (bool): true to enable shutdown/one-shot mode.
*/
function shutdown(state) {
read_conf();
local new_conf = 0;
if (state) {
new_conf = ((conf[0] | 0x01) << 8) + conf[1];
} else {
new_conf = ((conf[0] & 0xFE) << 8) + conf[1];
}
i2c.write(addr, format("%c%c%c",CONF_REG,(new_conf & 0xFF00) >> 8,(new_conf & 0xFF)));
// read_conf() updates the variables for shutdown and extended modes
read_conf();
}
/*
* Enter or exit 13-bit extended mode
*
* Input:
* State (bool): true to enable 13-bit extended mode
*/
function set_extendedmode(state) {
read_conf();
local new_conf = 0;
if (state) {
new_conf = ((conf[0] << 8) + (conf[1] | 0x10));
} else {
new_conf = ((conf[0] << 8) + (conf[1] & 0xEF));
}
i2c.write(addr, format("%c%c%c",CONF_REG,(new_conf & 0xFF00) >> 8,(new_conf & 0xFF)));
read_conf();
}
/*
* Set the T_low threshold register
* This value is used to determine the state of the ALERT pin when the device is in thermostat mode
*
* Input:
* t_low: new threshold register value in degrees Celsius
*
*/
function set_t_low(t_low) {
t_low = (t_low / DEG_PER_COUNT).tointeger();
local mask = 0x0FFF;
if (EXTENDEDMODE) {
mask = 0x1FFF;
if (t_low < 0) {
twos_comp(t_low, mask);
}
t_low = (t_low & mask) << 3;
} else {
if (t_low < 0) {
twos_comp(t_low, mask);
}
t_low = (t_low & mask) << 4;
}
server.log(format("set_t_low setting register to 0x%04x (%d)",t_low,t_low));
i2c.write(addr, format("%c%c%c",T_LOW_REG,(t_low & 0xFF00) >> 8, (t_low & 0xFF)));
T_LOW = t_low;
}
/*
* Set the T_high threshold register
* This value is used to determine the state of the ALERT pin when the device is in thermostat mode
*
* Input:
* t_high: new threshold register value in degrees Celsius
*
*/
function set_t_high(t_high) {
t_high = (t_high / DEG_PER_COUNT).tointeger();
local mask = 0x0FFF;
if (EXTENDEDMODE) {
mask = 0x1FFF;
if (t_high < 0) {
twos_comp(t_high, mask);
}
t_high = (t_high & mask) << 3;
} else {
if (t_high < 0) {
twos_comp(t_high, mask);
}
t_high = (t_high & mask) << 4;
}
server.log(format("set_t_high setting register to 0x%04x (%d)",t_high,t_high));
i2c.write(addr, format("%c%c%c",T_HIGH_REG,(t_high & 0xFF00) >> 8, (t_high & 0xFF)));
T_HIGH = t_high;
}
/*
* Read the current value of the T_low threshold register
*
* Return: value of register in degrees Celsius
*/
function get_t_low() {
local result = i2c.read(addr, format("%c",T_LOW_REG), 2);
local t_low = (result[0] << 8) + result[1];
//server.log(format("get_t_low got: 0x%04x (%d)",t_low,t_low));
local mask = 0x0FFF;
local sign_mask = 0x0800;
local offset = 4;
if (EXTENDEDMODE) {
//server.log("get_t_low: TMP112 in extended mode.")
sign_mask = 0x1000;
mask = 0x1FFF;
offset = 3;
}
t_low = (t_low >> offset) & mask;
if (t_low & sign_mask) {
//server.log("get_t_low: Tlow is negative.");
t_low = -1.0 * (twos_comp(t_low,mask));
}
//server.log(format("get_t_low: raw value is 0x%04x (%d)",t_low,t_low));
T_LOW = (t_low.tofloat() * DEG_PER_COUNT);
return T_LOW;
}
/*
* Read the current value of the T_high threshold register
*
* Return: value of register in degrees Celsius
*/
function get_t_high() {
local result = i2c.read(addr, format("%c",T_HIGH_REG), 2);
local t_high = (result[0] << 8) + result[1];
local mask = 0x0FFF;
local sign_mask = 0x0800;
local offset = 4;
if (EXTENDEDMODE) {
sign_mask = 0x1000;
mask = 0x1FFF;
offset = 3;
}
t_high = (t_high >> offset) & mask;
if (t_high & sign_mask) {
t_high = -1.0 * (twos_comp(t_high,mask));
}
T_HIGH = (t_high.tofloat() * DEG_PER_COUNT);
return T_HIGH;
}
/*
* If the TMP112 is in shutdown mode, write the one-shot bit in the configuration register
* This starts a conversion.
* Conversions are done in 26 ms (typ.)
*
*/
function start_conversion() {
read_conf();
local new_conf = 0;
new_conf = ((conf[0] | 0x80) << 8) + conf[1];
i2c.write(addr, format("%c%c%c",CONF_REG,(new_conf & 0xFF00) >> 8,(new_conf & 0xFF)));
}
/*
* Read the temperature from the TMP112 Sensor
*
* Returns: current temperature in degrees Celsius
*/
function read_c() {
if (SHUTDOWN) {
start_conversion();
CONV_READY = false;
local timeout = 30; // timeout in milliseconds
local start = hardware.millis();
while (!CONV_READY) {
read_conf();
if ((hardware.millis() - start) > timeout) {
server.error("Device: TMP112 Timed Out waiting for conversion.");
return -999;
}
}
}
local result = i2c.read(addr, format("%c", TEMP_REG), 2);
local temp = (result[0] << 8) + result[1];
local mask = 0x0FFF;
local sign_mask = 0x0800;
local offset = 4;
if (EXTENDEDMODE) {
mask = 0x1FFF;
sign_mask = 0x1000;
offset = 3;
}
temp = (temp >> offset) & mask;
if (temp & sign_mask) {
temp = -1.0 * (twos_comp(temp, mask));
}
return temp * DEG_PER_COUNT;
}
/*
* Read the temperature from the TMP112 Sensor and convert
*
* Returns: current temperature in degrees Fahrenheit
*/
function read_f() {
local temp_c = read_c();
if (temp_c == -999) {
return -999;
} else {
return (read_c() * 9.0 / 5.0 + 32.0);
}
}
}
// Read temperature and light level, then send to agent at a regular interval
function poll() {
imp.wakeup(POLLING_PERIOD, poll); // Schedule next reading
local temp = tempSensor.read_c(); // Read temperature
agent.send("current",temp);
if (temp < target) {
// turn the "heater" on
led.write(0);
} else {
// turn the "heater" off
led.write(1);
}
}
agent.on("target", function(newtarget) {
target = newtarget;
server.log(format("New set point: %0.2f",target));
});
// 8-bit left-justified I2C addresses
const TMP102_ADDR = 0x92; // Temperature sensor
// Configure and turn off white LED
led_wht <- hardware.pinM;
led_wht.configure(DIGITAL_OUT); led_wht.write(0); // White active-high
// Configure red LED ("heater")
led <- hardware.pinE;
led.configure(DIGITAL_OUT);
led.write(1); // start off
// TMP102 interrupt pin - not used
alert <- hardware.pinW;
alert.configure(DIGITAL_IN);
// Configure i2c bus, maximum speed
i2c <- hardware.i2cAB;
i2c.configure(CLOCK_SPEED_400_KHZ);
tempSensor <- TMP112(i2c, TMP102_ADDR, alert);
// Start polling
poll();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment