Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
AdaFruit Huzzah ESP8266 + SHT15 - Temperature reading/saving
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SHT1X.h>
#define POLL_INTERVAL_MICROSECONDS 300000000 // DO NOT POLL MORE OFTEN THAN ONCE EVERY 3 SECONDS!
const size_t PACKET_SIZE=6;
unsigned char packet[PACKET_SIZE];
float temperature = 0;
float temperatureFloored = 0;
float humidity = 0;
float humidityFloored = 0;
unsigned short batteryLevel = 0;
const size_t batteryLevelSize = sizeof(batteryLevel);
const IPAddress serverIP = IPAddress(192,168,77,40);
WiFiClient tcp;
SHT1x sht15(4, 5);
void setup(void)
{
batteryLevel = (unsigned short)analogRead(A0);
temperature = sht15.readTemperatureF();
temperatureFloored = floorf(temperature);
humidity = sht15.readHumidity();
humidityFloored = floorf(humidity);
packet[0] = ((unsigned char)temperatureFloored)+50;
packet[1] = (unsigned char)((temperature-temperatureFloored)*100);
packet[2] = (unsigned char)humidityFloored;
packet[3] = (unsigned char)((humidity-humidityFloored)*100);
memcpy(packet+4, &batteryLevel, batteryLevelSize);
WiFi.begin("NETWORKNAME", "PASSWORD");
while(WiFi.status()!=WL_CONNECTED)
{
delay(500);
}
if(tcp.connect(serverIP, 27072))
{
tcp.write((const uint8_t *)packet, PACKET_SIZE);
tcp.stop();
}
ESP.deepSleep(POLL_INTERVAL_MICROSECONDS, WAKE_RF_DEFAULT);
}
void loop(void)
{
}
#!/usr/local/bin/node
"use strict";
var base = require("xbase"),
tiptoe = require("tiptoe"),
runUtil = require("xutil").run,
fileUtil = require("xutil").file,
libxmljs = require("libxmljs"),
telnet = require("telnet-client"),
moment = require("moment"),
redis = require("redis").createClient(27073, "127.0.0.1"),
JSFtp = require("jsftp"),
path = require("path"),
fs = require("fs");
var BASE_TIMESTAMP = 1454873832;
var REFRESH_INTERVAL = base.MINUTE*5;
var UNKNOWN_BATTERY_TIMELENGTH = REFRESH_INTERVAL+base.MINUTE;
var NS = "http://www.w3.org/2000/svg";
var RUN_OPTIONS = {silent:true};
var CONVERT_RUN_PATH = path.join(__dirname, "convertPNGToKodoRaw.py");
var DISPLAY_FILE_PATH = path.join(__dirname, "display.svg");
var KODO_IP = "192.168.77.221";
var NUM_READINGS_SAMPLE_SIZE = 10;
var NUM_BOXES = 10;
var SENSORS =
[
{ ip : "192.168.77.200", name : "Master Bedroom"},
{ ip : "192.168.77.201", name : "Rob's Office"},
{ ip : "192.168.77.202", name : "Kitchen"},
{ ip : "192.168.77.203", name : "Sunroom"},
{ ip : "192.168.77.204", name : "Sewing"},
{ ip : "192.168.77.205", name : "Theater"},
{ ip : "192.168.77.206", name : "Basement TV"},
{ ip : "192.168.77.207", name : "Unknown"},
{ ip : "192.168.77.208", name : "Outside Shade"},
{ ip : "192.168.77.209", name : "Outside Sun"}
];
var batteryStatusLevels = [ {percent : 100, threshold : 733}, {percent : 75, threshold : 742}, {percent : 50, threshold : 724}, {percent : 25, threshold : 715 }];
var OUT_SVG_PATH = fileUtil.generateTempFilePath(undefined, "-tempDisplay.svg");
var OUT_PNG_PATH = fileUtil.generateTempFilePath(undefined, "-tempDisplay.png");
var OUT_RAW_PATH = fileUtil.generateTempFilePath(undefined, "-tempDisplay.raw");
function runKodoCommand(command, cb)
{
var kodo = new telnet();
kodo.on("ready", function(prompt)
{
kodo.exec(command, function(err, response) { kodo.end(); });
});
kodo.on("close", function() { cb(); });
kodo.connect({host:KODO_IP, loginPrompt : /\(none\) login:/, shellPrompt : /\[root@\(none\) ~\]#\s/, username : "root", password : ""});
}
tiptoe(
function readDSVGAndFetchSensorData()
{
fs.readFile(DISPLAY_FILE_PATH, {encoding:"utf8"}, this.parallel());
SENSORS.serialForEach(function(SENSOR, subcb)
{
redis.lrange("d:" + SENSOR.ip, "-" + NUM_READINGS_SAMPLE_SIZE, "-1", subcb);
}, this.parallel());
},
function alterSVG(displaySVGRaw, sensorsData)
{
this.data.displaySVG = libxmljs.parseXml(displaySVGRaw).root();
[].pushSequence(0, NUM_BOXES-1).forEach(function(i)
{
var box = this.data.displaySVG.get("xmlns:g[@id='box" + i + "']", NS);
if(SENSORS.length<(i+1))
{
box.remove();
return;
}
var SENSOR = SENSORS[i];
box.get("xmlns:text[@class='where']", NS).text(SENSOR.name);
var sensorData = sensorsData[i];
var lastReadingValues = sensorData.last().split(",");
var currentTemp = +lastReadingValues[1];
if(currentTemp>=0)
box.get("xmlns:text[@class='temperature']/xmlns:tspan[@class='negative']", NS).remove();
box.get("xmlns:text[@class='temperature']/xmlns:tspan[@class='value']", NS).text("" + Math.floor(currentTemp));
box.get("xmlns:text[@class='temperature']/xmlns:tspan[@class='remainder']", NS).text("." + currentTemp.toFixed(1).substring(currentTemp.toFixed(1).length-1));
var currentHumidity = lastReadingValues[2];
box.get("xmlns:text[@class='humidity']/xmlns:tspan[@class='value']", NS).text("" + Math.floor(currentHumidity));
var prevReadingValues = sensorData[0].split(",");
var prevTemp = +prevReadingValues[1];
var tempTrend = box.get("xmlns:text[@class='temperature']/xmlns:tspan[@class='trend']", NS);
if(Math.round(prevTemp)===Math.round(currentTemp))
tempTrend.attr({fill:"#FFFFFF"});
else
tempTrend.text(Math.round(prevTemp)>Math.round(currentTemp) ? "" : "");
var prevHumidity = +prevReadingValues[2];
var humidityTrend = box.get("xmlns:text[@class='humidity']/xmlns:tspan[@class='trend']", NS);
if(Math.round(prevHumidity)===Math.round(currentHumidity))
humidityTrend.attr({fill:"#FFFFFF"});
else
humidityTrend.text(Math.round(prevHumidity)>Math.round(currentHumidity) ? "" : "");
var lastReadingMoment = moment(BASE_TIMESTAMP + (+lastReadingValues[0]), "X");
var batteryLevel = sensorData.map(function(readings) { return +readings.split(",").last(); }).median();
var batteryPercentage = 0;
batteryStatusLevels.forEach(function(batteryStatusLevel)
{
if(batteryPercentage>0)
return;
if(batteryLevel>batteryStatusLevel.threshold)
batteryPercentage = batteryStatusLevel.percent;
});
if((moment().valueOf()-lastReadingMoment.valueOf())>=UNKNOWN_BATTERY_TIMELENGTH)
batteryPercentage = "unknown";
box.get("xmlns:use[@class='battery']", NS).attr("href").value("#battery" + batteryPercentage);
}.bind(this));
fs.writeFile(OUT_SVG_PATH, this.data.displaySVG.doc().toString(false), {encoding:"utf8"}, this);
},
function renderImage()
{
runUtil.run("inkscape", ["--export-png", OUT_PNG_PATH, OUT_SVG_PATH], RUN_OPTIONS, this);
},
function convertToRaw()
{
runUtil.run("python", [CONVERT_RUN_PATH, OUT_PNG_PATH, OUT_RAW_PATH], RUN_OPTIONS, this);
},
function uploadToKodo()
{
var ftp = new JSFtp({host:KODO_IP});
ftp.put(OUT_RAW_PATH, "/tmp/temperature.raw", this);
},
function displayKodoImage()
{
runKodoCommand("cat /tmp/temperature.raw | /usr/local/Kobo/pickel showpic", this);
},
function turnOffBlink()
{
runKodoCommand("/usr/local/Kobo/pickel blinkoff", this);
},
function cleanup()
{
fs.unlink(OUT_SVG_PATH, this.parallel());
fs.unlink(OUT_PNG_PATH, this.parallel());
fs.unlink(OUT_RAW_PATH, this.parallel());
},
function finish(err)
{
if(err)
{
base.error(err);
process.exit(1);
}
process.exit(0);
}
);
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>
#include <time.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <hiredis/hiredis.h>
void die(const char * errmsg)
{
perror(errmsg);
exit(1);
}
#define PACKET_SIZE 6
#define BASE_TIMESTAMP 1454873832
int main(int argc, char ** argv)
{
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientAddrSize = sizeof(clientAddr);
int serverSock, clientSock;
int optval;
char clientIPAddress[INET_ADDRSTRLEN];
char packet[PACKET_SIZE];
unsigned short batteryLevel;
redisContext * redisContext;
struct timeval redisConnectTimeout = { 10, 0 };
redisContext = redisConnectWithTimeout("127.0.0.1", 27073, redisConnectTimeout);
if(redisContext==NULL || redisContext->err)
{
if(redisContext)
{
fprintf(stderr, "Connection error: %s\n", redisContext->errstr);
redisFree(redisContext);
}
else
{
fprintf(stderr, "Connection error: can't allocate redis context\n");
}
exit(1);
}
if((serverSock=socket(AF_INET, SOCK_STREAM, 0))<0)
die("socket");
optval = 1;
setsockopt(serverSock, SOL_SOCKET, SO_REUSEADDR, (const void *)&optval , sizeof(int));
memset((char *) &serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(27072);
serverAddr.sin_addr.s_addr = inet_addr("192.168.77.40");
if(bind(serverSock, (struct sockaddr *)&serverAddr, sizeof(serverAddr))<0)
die("bind");
if(listen(serverSock, 10)<0)
die("listen");
while(1)
{
clientSock = accept(serverSock, (struct sockaddr *)&clientAddr, &clientAddrSize);
if(clientSock<0)
continue;
if(read(clientSock, packet, PACKET_SIZE)==PACKET_SIZE)
{
inet_ntop(AF_INET, &clientAddr.sin_addr, clientIPAddress, INET_ADDRSTRLEN);
memcpy(&batteryLevel, packet+4, sizeof(batteryLevel));
printf("%s: %ld: Temp: %d.%dF Hum: %d.%d%% Battery: %d\n", clientIPAddress, time(0), (packet[0])-50, packet[1], packet[2], packet[3], batteryLevel);
redisCommand(redisContext, "RPUSH d:%s %ld,%d.%d,%d.%d,%d", clientIPAddress, (time(0)-1454873832), (packet[0])-50, packet[1], packet[2], packet[3], batteryLevel);
}
close(clientSock);
}
redisFree(redisContext);
close(serverSock);
return 0;
}
@tommck
tommck commented Mar 8, 2016

Hey, your battery level to percentage mapping seems really different than the mapping values the adafruit folks used. Did you use a different set of resistors than what they specified?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment