Create a gist now

Instantly share code, notes, and snippets.

Embed
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

This comment has been minimized.

Show comment
Hide comment
@tommck

tommck 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?

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