commit 02e008ab252d549a1bdaf131aac004cc48d38760 Author: Gabe Shaughnessy Date: Wed Sep 28 11:31:38 2016 -0500 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..804eb40 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +Flask and SocketIO frontend to weather station. It can accept multiple stations simultaneously. + +To setup: +pip -r requirements + +To run: +Run station receiver that pushes socketIO packets to flask webserver: +$python stationServer.py + +Run flask webserver that listens for SocketIO packets: +$python app.py + +Webpage will be located at and will update when packet is received from stations +http://localhost:5000/ diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..2af0c1c --- /dev/null +++ b/app/app.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +# Set this variable to "threading", "eventlet" or "gevent" to test the +# different async modes, or leave it set to None for the application to choose +# the best option based on available packages. +async_mode = None + +if async_mode is None: + try: + import eventlet + async_mode = 'eventlet' + except ImportError: + pass + + if async_mode is None: + try: + from gevent import monkey + async_mode = 'gevent' + except ImportError: + pass + + if async_mode is None: + async_mode = 'threading' + + print('async_mode is ' + async_mode) + +# monkey patching is necessary because this application uses a background +# thread +if async_mode == 'eventlet': + import eventlet + eventlet.monkey_patch() +elif async_mode == 'gevent': + from gevent import monkey + monkey.patch_all() + +import json +import time +from threading import Thread +from flask import Flask, render_template +from flask_socketio import SocketIO + +from numpy import exp, cos, linspace +import os, re + +app = Flask(__name__, static_url_path='/static') +app.config['SECRET_KEY'] = 'secret!' +socketio = SocketIO(app, async_mode=async_mode) +thread = None + + +@socketio.on('dataPacket', namespace='/') +def handle_message(message): + if app.debug: + print('Obtained packet from station: ') + print message + # print message['data'] + socketio.emit('dataPacket', + {'data': 'Server generated event', 'packet': message}, + namespace='/web') + +@app.route('/') +def index(): + return render_template('index.html') + +if __name__ == '__main__': + socketio.run(app, host='0.0.0.0', port=5000, debug=False) diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..b5ee766 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,28 @@ +Flask==0.11.1 +Flask-SocketIO==2.7.1 +Jinja2==2.8 +MarkupSafe==0.23 +PyYAML==3.12 +Werkzeug==0.11.11 +argparse==1.2.1 +backports-abc==0.4 +bokeh==0.10.0 +certifi==2016.9.26 +click==6.6 +gevent==1.1.2 +greenlet==0.4.10 +itsdangerous==0.24 +numpy==1.11.1 +pandas==0.18.1 +python-dateutil==2.5.3 +python-engineio==1.0.3 +python-socketio==1.6.0 +pytz==2016.6.1 +pyzmq==15.4.0 +requests==2.11.1 +singledispatch==3.4.0.3 +six==1.10.0 +socketIO-client==0.7.0 +tornado==4.4.1 +websocket-client==0.37.0 +wsgiref==0.1.2 diff --git a/app/static/css/style.css b/app/static/css/style.css new file mode 100644 index 0000000..7f04ec9 --- /dev/null +++ b/app/static/css/style.css @@ -0,0 +1,4 @@ +{ + font-size: 320px; + line-height: 2; +} diff --git a/app/stationDB.py b/app/stationDB.py new file mode 100644 index 0000000..626f7a1 --- /dev/null +++ b/app/stationDB.py @@ -0,0 +1,198 @@ +import sqlite3 +import pandas as pd +import stationDB as st +from jinja2 import Template + +from bokeh.plotting import * +from bokeh.resources import INLINE +# from bokeh.util.browser import view + +from bokeh.models import HoverTool +from bokeh.embed import components +import time +import datetime +import os.path + + + +class DB: + def __init__(self,fileName, table): + self.fileName=fileName + self.table=table + self.columnNames=[] + if not os.path.isfile(self.fileName): + self.createTable() + self.getColumnNames() + + def createTable(self): + con=sqlite3.connect(self.fileName) + c=con.cursor() + c.execute("CREATE TABLE %s (timestamp real, timeString text)" % self.table) + + con.close() + + def addColumn(self, name): + con=sqlite3.connect(self.fileName) + c=con.cursor() + type="real" + c.execute("ALTER TABLE %s ADD COLUMN %s %s" % (self.table, name, type)) + con.close() + + def getColumnNames(self): + con=sqlite3.connect(self.fileName) + command="SELECT * from %s" % self.table + self.df=pd.read_sql_query(command, con) + self.columnNames=self.df.columns + con.close() + + def addData(self, packet): + con=sqlite3.connect(self.fileName) + c=con.cursor() + + # create column if not present + for key in packet.keys(): + if key not in self.columnNames: + self.addColumn(key) + self.getColumnNames() + + + columns=', '.join(packet.keys()) + placeholders = ', '.join('?' * len(packet)) + sql = 'INSERT INTO master ({}) VALUES ({})'.format(columns, placeholders) + c.execute(sql, packet.values()) + + # # command="create table if not exists %s (date real, dateString text, name text, value real)" % name + # c.execute(command) + # timeString=datetime.datetime.fromtimestamp(time).strftime('%Y/%m/%d %H:%M:%S') + # dataTuple=(time, timeString, name, data,) + # command="INSERT INTO %s VALUES (?, ?, ?, ?)" % name + # c.execute(command, dataTuple) + con.commit() + con.close() + + def loadDB(self,name): + con=sqlite3.connect(self.fileName) + command="SELECT * from %s" % name + self.df=pd.read_sql_query(command, con) + con.close() + +class DBview: + def __init__(self,db,df,UTCOffset): + # self.timeRange=timeRange + self.db = db + self.df = df + self.UTCOffset = UTCOffset + + + def qp(self,attrList, name, timeRange): + now=datetime.datetime.now() + epoch=datetime.datetime.utcfromtimestamp(0) + tstart=now-datetime.timedelta(days=timeRange) + tcut=(tstart-epoch).total_seconds() + dataFrame=self.df[self.df.timestamp>tcut] + dataFrame['t']=dataFrame.timestamp*1000 + # y1=getattr(self.df,attr1) + # y2=getattr(self.df,attr2) + output_file(name+'.html') + timeString=[datetime.datetime.fromtimestamp(dt).strftime('%Y/%m/%d %H:%M:%S') for dt in dataFrame.timestamp] + source=ColumnDataSource(data=dataFrame.to_dict('list')) + TOOLS="resize,hover,crosshair,pan,wheel_zoom,box_zoom,reset,tap,previewsave,box_select,poly_select,lasso_select" + p=figure(x_axis_type="datetime",tools=TOOLS) + for key in attrList: + print key + p.scatter('t',key, source=source) + p.select(dict(type=HoverTool)).tooltips=[ + ("Time", "@timeString"), + ("Value", "@y1"), + ("Value", "@y2"), + ] + show(p) + + def qph(self,attrList, name, timeRange): + now=datetime.datetime.now() + epoch=datetime.datetime.utcfromtimestamp(0) + tstart=now-datetime.timedelta(days=0,hours=timeRange) + tcut=(tstart-epoch).total_seconds() + dataFrame=self.df[self.df.timestamp>tcut] + dataFrame['t']=dataFrame.timestamp*1000 + # y1=getattr(self.df,attr1) + # y2=getattr(self.df,attr2) + output_file(name+'.html') + timeString=[datetime.datetime.fromtimestamp(dt).strftime('%Y/%m/%d %H:%M:%S') for dt in dataFrame.timestamp] + source=ColumnDataSource(data=dataFrame.to_dict('list')) + TOOLS="resize,hover,crosshair,pan,wheel_zoom,box_zoom,reset,tap,previewsave,box_select,poly_select,lasso_select" + p=figure(x_axis_type="datetime",tools=TOOLS) + for key in attrList: + print key + p.scatter('t',key, source=source) + p.select(dict(type=HoverTool)).tooltips=[ + ("Time", "@timeString"), + ("Value", "@y1"), + ("Value", "@y2"), + ] + show(p) + + return source + + + def qphLive(self,attrList, name, timeRange): + # attrList=['stationLoadVolt'] + now=datetime.datetime.now() + epoch=datetime.datetime.utcfromtimestamp(0) + tstart=now-datetime.timedelta(days=0,hours=timeRange-self.UTCOffset) + tcut=(tstart-epoch).total_seconds() + dataFrame=self.df[self.df.timestamp>tcut] + dataFrame['t']=dataFrame.timestamp*1000 - self.UTCOffset*3600*1000 + output_server(name,url='http://10.0.1.2:5006') + colors=["red","blue","green","orange","purple","black","gray","magenta","cyan","brown","gold","darkkhaki","darksalmon"] + timeString=[datetime.datetime.fromtimestamp(dt).strftime('%Y/%m/%d %H:%M:%S') for dt in dataFrame.timestamp] + source=ColumnDataSource(data=dataFrame.to_dict('list')) + TOOLS="resize,hover,crosshair,pan,wheel_zoom,box_zoom,reset,tap,previewsave,box_select,poly_select,lasso_select" + keyList=attrList.keys() + p={} + ds={} + for mainKey in keyList: + if mainKey == keyList[0]: + p[mainKey]=figure(x_axis_type="datetime",tools=TOOLS, width=600, height=400, title=mainKey) + else: + p[mainKey]=figure(x_axis_type="datetime",tools=TOOLS, width=600, height=400, title=mainKey, x_range=p[keyList[0]].x_range) + ikey=0 + hover={} + for key in attrList[mainKey]: + print key + # keySource=ColumnDataSource({'x': source.data['t'], 'y': series.values, 'series_name': name_for_display, 'Date': toy_df.index.format()}) + p[mainKey].scatter('t',key, source=source, name=key,fill_color=colors[ikey],line_color=colors[ikey], legend=key) + + hover = p[mainKey].select(dict(type=HoverTool)) + # hover[ikey].renderers=[source.data[key]] + # hover[ikey].tooltips=tooltips+[("Series",key),("Time","@timeString"), ("Value", "@"+key)] + hover.tooltips=[("Series",key),("Time","@timeString"), ("Value", "@"+key)] + # hover.mode = "mouse" + ikey+=1 + p[mainKey].legend.orientation="top_left" + renderer = p[mainKey].select(dict(name=key)) + ds[mainKey]=renderer[0].data_source + + + # allP = vplot(*p.values()) + # allP = gridplot([p.values()]) + group=lambda flat, size: [flat[i:i+size] for i in range(0,len(flat), size)] + allP = gridplot(group(p.values(),1)) + show(allP) + + + while True: + print 'updating...' + self.db=st.DB('data.sdb','master') + self.db.loadDB('master') + self.df = self.db.df + now=datetime.datetime.now() + tstart=now-datetime.timedelta(days=0,hours=timeRange-self.UTCOffset) + tcut=(tstart-epoch).total_seconds() + dataFrame=self.df[self.df.timestamp>tcut] + dataFrame['t']=dataFrame.timestamp*1000 - self.UTCOffset*3600*1000 + for mainKey in keyList: + ds[mainKey].data = dataFrame.to_dict('list') + # print ds.data['stationIRTemp'] + cursession().store_objects(ds[mainKey]) + time.sleep(30) diff --git a/app/stationPlots.py b/app/stationPlots.py new file mode 100644 index 0000000..1d9fd81 --- /dev/null +++ b/app/stationPlots.py @@ -0,0 +1,26 @@ +import stationDB as st +import numpy as np +import sqlite3 +import pandas as pd +from bokeh.plotting import * +from bokeh.models import HoverTool + +import time +import datetime +import pytz + +db=st.DB('data.sdb','master') +db.loadDB('master') +UTCOffset = -5 +dbv=st.DBview(db, db.df, UTCOffset) + +attrList={} +attrList['Station Temperature']=['WeatherStationTemperature','WeatherStationHTUTemp','WeatherStationDewpoint'] +attrList['Station IR']=['WeatherStationIRTemp','WeatherStationMLXTemp'] +attrList['Brightness']=['WeatherStationBrightness'] +attrList['Voltage']=['WeatherStationLoad'] +attrList['Current']=['WeatherStationCurrent'] +attrList['UV']=['WeatherStationUVIndex'] + +viewHistory=24 +dbv.qphLive(attrList, 'DayView', viewHistory) diff --git a/app/stationServer.py b/app/stationServer.py new file mode 100644 index 0000000..1d6f9db --- /dev/null +++ b/app/stationServer.py @@ -0,0 +1,133 @@ +from datetime import datetime as dt +import sys +import os +from socketIO_client import SocketIO, LoggingNamespace +from subprocess import check_output +import socket +import time +import json +import stationDB +import datetime +from crypt import * +import numpy as np + + +class Station: + def __init__(self): + self.name='Rpi' + self.udpPort=8123 + self.BUF_SIZE=1024 + self.packets={} + self.packetMean={} + self.nMeasurements={} + self.lastUpdate={} + self.avgFreq=30 + self.lastRelease=datetime.datetime.now() + # self.secret_key = '1234567890123456' + + + + + def initSocket(self, host, port ): + try: + self.socketIO = SocketIO(host, port) + self.socketConnected=True + except: + print "Unable to open socket: ", sys.exc_info()[0] + self.socketConnected=False + raise + + def startUDPListen(self,ip,port): + self.sock = socket.socket(socket.AF_INET, # Internet + socket.SOCK_DGRAM) # UDP + self.sock.bind((ip, port)) + + def startUDPSend(self): + self.sock = socket.socket(socket.AF_INET, # Internet + socket.SOCK_DGRAM) # UDP + + + def sendPacket(self, message, ip, port): + self.sock.sendto(message, (ip, port)) + + def recvPacket(self, verbose): + data, addr = self.sock.recvfrom(self.BUF_SIZE) # buffer size is 1024 bytes + # decoded = decodePacket(self.secret_key,data) + try: + packet=json.loads(data) + except ValueError, e: + print 'bad json data' + print data + return {} + + if verbose: + print "Message from ", addr, " :", packet + return packet + + def checkForPacketRelease(self): + releasePacket=False + now=datetime.datetime.now() + releaseTime=0 + if (now-self.lastRelease).seconds > self.avgFreq: + self.packetMean['timestamp']=time.time() + + releasePacket=True + releaseTime=float(now.strftime("%s")) + self.lastRelease=now + timeString=now.strftime('%Y/%m/%d %H:%M:%S') + self.packetMean['timeString']=timeString + self.nMeasurements['timeString']=1 + # reset packet list + for key in self.packets.keys(): + self.packets[key]=[] + + + return (releasePacket,releaseTime) + + + def updateMeasurement(self, key, value): + + if key not in self.lastUpdate.keys(): + self.lastUpdate[key]=datetime.datetime.now() + + self.lastUpdate[key]=datetime.datetime.now() + if key not in self.packets.keys(): + self.packets[key]=value + self.packets[key]=np.append(self.packets[key],value) + self.packetMean[key] = np.mean(self.packets[key]) + self.nMeasurements[key] = len(self.packets[key]) + + +# +db=stationDB.DB('data.sdb','master') +s=Station() +s.udpPort=9990 +s.startUDPListen('0.0.0.0',s.udpPort) + +sio=SocketIO('localhost', 5000) +verbose=False +readPackets=True +while readPackets: + rawPacket=s.recvPacket(verbose) + print rawPacket + try: + sio.emit('dataPacket', rawPacket) + print 'sent packet' + except: + print 'socket not connected' + + # rawPacket['name'] + + for dataName in rawPacket['data'].keys(): + key = rawPacket['name'].replace(' ','') + dataName.replace(' ','') + value = rawPacket['data'][dataName]['value'] + releasePacket=s.updateMeasurement(key,value) + releasePacket,releaseTime=s.checkForPacketRelease() + if releasePacket: + for (key,value) in s.packetMean.items(): + print 'Updating db: ',key,' = ',value + db.addData(s.packetMean) + s.packetMean={} + + +s.sock.close() diff --git a/app/templates/homeConditions.html b/app/templates/homeConditions.html new file mode 100644 index 0000000..f93a281 --- /dev/null +++ b/app/templates/homeConditions.html @@ -0,0 +1,153 @@ + + + + Home Conditions + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + diff --git a/app/templates/index.html b/app/templates/index.html new file mode 100644 index 0000000..438e6c0 --- /dev/null +++ b/app/templates/index.html @@ -0,0 +1,132 @@ + + + + Home Conditions + + + + + + + + + + + + + + + +
+
+ + + + + + + +
+
+
+ + diff --git a/esp8266/ESP_httplib.cpp b/esp8266/ESP_httplib.cpp new file mode 100644 index 0000000..540300f --- /dev/null +++ b/esp8266/ESP_httplib.cpp @@ -0,0 +1,239 @@ +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#ifdef __AVR_ATtiny85__ + #include "TinyWireM.h" + #define Wire TinyWireM +#else + #include +#endif + +#include "ESP_httplib.h" +#include "ESP8266WiFi.h" + +/**************************************************************************/ +// Instantiates new ESP class +/**************************************************************************/ +ESP_HTTP::ESP_HTTP(){ +} + +/**************************************************************************/ +// Instantiates new ESP class +/**************************************************************************/ +boolean ESP_HTTP::begin(void){ + header="" + stationName +""; + // refresh HTML page + header +=""; + header +=""; + + css=""; + css+=""; + css+=""; + css+=""; + css+=""; + css+=""; + css+=""; + body="
"; + endHTML="
"; + return true; +} + +String ESP_HTTP::getStatus(Data data){ + + // Apply color coding status using data limits + String dataclass; + if (data.value > data.ul2){ + if (data.invertLimits){ + dataclass = "info"; + } else { + dataclass = "danger"; + } + } else if (data.value > data.ul1){ + if (data.invertLimits){ + dataclass = "success"; + } else { + dataclass = "warning"; + } + } else if (data.value < data.ll2){ + if (data.invertLimits){ + dataclass = "danger"; + } else { + dataclass = "info"; + } + } else if (data.value < data.ll1){ + if (data.invertLimits){ + dataclass = "warning"; + } else { + dataclass = "success"; + } + } else { + dataclass = "success"; + } + + return dataclass; +} + +boolean ESP_HTTP::updatePage(DataSet dataset, String packet){ + + + content = "
"; + content += "
"; + content += "

" + stationName + "

"; + content += "

"; + + dataContent=""; + for (int i=0; i < dataset.nData; i++){ + if (dataset.data[i].name == "uptime"){ + uptime = dataset.data[i].value; + continue; + } + String dataclass=getStatus(dataset.data[i]); + + if (i==0) { + dataContent += "" + " "; + } else { + dataContent += "" + " "; + } + + } + dataContent += "
MeasurementValue
" + dataset.data[i].name + "" + String(dataset.data[i].value) + " " + dataset.data[i].unit + "
" + dataset.data[i].name + "" + String(dataset.data[i].value) + " " + dataset.data[i].unit + "
"; + + dataContent += "
\ + \ + \ +
"; + + dataContent += "
"; + + dataContent += "
"; + dataContent += ""; + dataContent += "
"; + dataContent += "
"; + + dataContent += "
"; + dataContent += ""; + dataContent += "
"; + dataContent += "
"; + + dataContent += "
"; + dataContent += ""; + dataContent += "
"; + dataContent += "
"; + + dataContent += ""; + dataContent += "

"; + + + buttons = "

JSON Data

"+ packet +"

"; + + + buttons += "
\ + \ + \ +
"; + + buttons += "
"; + buttons += "
"; + + buttons += ""; + +footer="
Hello from " + stationName + "
Station sensor: Version " + String(version) + "
Uptime: " + String(uptime) + " seconds
"; + + + return true; +} + + +String ESP_HTTP::page(void){ + String htmlpage = header + css + body + content + dataContent + buttons + jsonButton + endHTML + footer; + return htmlpage; +} + + + +/**************************************************************************/ +// Instantiates new ESP class +/**************************************************************************/ +ESP_httplib::ESP_httplib(){ +} + +/**************************************************************************/ +// Instantiates new ESP class +/**************************************************************************/ +boolean ESP_httplib::begin(const char* ssid, const char* password) { + + pinMode(RESET_PIN, OUTPUT); + digitalWrite(RESET_PIN, HIGH); + pinMode(NOTIFY_PIN, OUTPUT); + digitalWrite(NOTIFY_PIN, HIGH); + + http.begin(); + // // Connect to WiFi + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + Serial.begin(115200); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + + // Print the IP address + stationIP=WiFi.localIP().toString(); + Serial.println(stationIP); + + // tStart = millis(); + + + return true; +} + +// float uptime(void){ +// return 0.001 * ( millis() - tStart); +// } + +void ESP_httplib::triggerActivityLED(void){ + digitalWrite(NOTIFY_PIN, LOW); // turn the LED off by making the voltage LOW + delay(10); + digitalWrite(NOTIFY_PIN, HIGH); // turn the LED off by making the voltage LOW + +} + +void ESP_httplib::triggerReset(){ + digitalWrite(RESET_PIN, LOW); // turn the LED off by making the voltage LOW +} + + + +void ESP_httplib::formPacket(DataSet dataset){ + + String dataPacket = ""; + for (int i=0; i < dataset.nData; i++){ + dataPacket += "\"" + dataset.data[i].name + "\": { \"value\": " + String(dataset.data[i].value,3) + ", \"class\": \"" + http.getStatus(dataset.data[i]) + "\", \"unit\": \"" + dataset.data[i].unit + "\"}"; + if (i != dataset.nData - 1){ + dataPacket += ", "; + } + } + + String name = "\"name\": \"" + http.stationName + "\""; + String address = "\"ip\": \"" + stationIP + "\""; + + String rssiPacket = "\"rssi\": " + String(WiFi.RSSI()); + String network = "\"network\": { " + address + ", " + rssiPacket + "}"; + + packet = "{ " + name + ", " + network + ", \"data\" : {" + dataPacket + "} }"; +} diff --git a/esp8266/ESP_httplib.h b/esp8266/ESP_httplib.h new file mode 100644 index 0000000..bd07d2b --- /dev/null +++ b/esp8266/ESP_httplib.h @@ -0,0 +1,79 @@ +#if ARDUINO >= 100 + #include "Arduino.h" +#else + #include "WProgram.h" +#endif + +#ifdef __AVR_ATtiny85__ + #include "TinyWireM.h" + #define Wire TinyWireM +#else + #include +#endif + +#include "ESP8266WiFi.h" + +#define NOTIFY_PIN 16 +#define RESET_PIN 5 + +typedef struct { + String name; + float value; + String unit; + float ul1, ul2, ll1, ll2; + boolean invertLimits; +} Data; + +typedef struct{ + String udpIP; + int udpPort; + float udpFRQ; +} Settings; + +typedef struct { + int maxData=20; + int nData; + Data data[20]; + + +} DataSet; + + + +class ESP_HTTP { + public: + ESP_HTTP(); + boolean begin(void); + boolean updatePage(DataSet dataset, String packet); + Settings settings; + String header; + String stationName; + String css; + String body; + String content; + String dataContent; + String buttons, jsonButton; + String endHTML; + String footer; + String version; + String getStatus(Data data); + String page(void); + float uptime; +}; + +class ESP_httplib { + public: + ESP_httplib(); + ESP_HTTP http; + boolean begin(const char* ssid, const char* password); + void triggerActivityLED(void); + void triggerReset(void); + String stationIP; + String packet; + void formPacket(DataSet dataset); + void sendUDPPacket(void); +private: + unsigned long int tStart; + + +}; diff --git a/esp8266/ESP_station.cpp b/esp8266/ESP_station.cpp new file mode 100644 index 0000000..b487505 --- /dev/null +++ b/esp8266/ESP_station.cpp @@ -0,0 +1,1076 @@ +// Import required libraries +#include "ESP_Base64.h" +#include "ESP8266WiFi.h" +#include +#include +#include +#include +#include "weatherCalcs.h" +#include +#include "ESP_httplib.h" +#include +#include + +#include "FS.h" + +ESP8266HTTPUpdateServer httpUpdater; + + +#define QUOTE(name) #name +#define STR(macro) QUOTE(macro) + +ESP_httplib esp = ESP_httplib(); + +#define VERSION "1.0" +// #define BUILD "" + + +//////////////////////////////////////////////////////////////////////////////////////////////// +//////////// DEVICE SPECIFIC HEADERS AND DECLARATIONS ////////////////////////////////////////// + +#ifdef MCP + #include "Adafruit_MCP9808.h" + Adafruit_MCP9808 mcp = Adafruit_MCP9808(); +#endif +#ifdef HTU + #include "Adafruit_HTU21DF.h" + Adafruit_HTU21DF htu = Adafruit_HTU21DF(); +#endif + +#ifdef MPL + #include + Adafruit_MPL3115A2 mpl = Adafruit_MPL3115A2(); +#endif + +#ifdef INA + #include + Adafruit_INA219 ina219(0x44); +#endif + +#ifdef MLX + #include + Adafruit_MLX90614 mlx = Adafruit_MLX90614(); +#endif + + + +#ifdef SI + #include "Adafruit_SI1145.h" + Adafruit_SI1145 si = Adafruit_SI1145(); +#endif + +#ifdef TMP + #include "Adafruit_TMP007.h" + Adafruit_TMP007 tmp(0x43); // SCL to AD0 +#endif + +#ifdef TSL + #include "Adafruit_TSL2591.h" + Adafruit_TSL2591 tsl = Adafruit_TSL2591(2591); + void configureSensor(tsl2591Gain_t gainSetting, tsl2591IntegrationTime_t timeSetting); + float advancedLightSensorRead(void); +#endif + +#ifdef SSD + #include + #define OLED_RESET 15 + // #include + // Adafruit_SSD1306 display(OLED_RESET); + #include // Modification of Adafruit_SSD1306 for ESP8266 compatibility + ESP_SSD1306 display(OLED_RESET); // FOR I2C + +#endif + + +// #include "Adafruit_LSM9DS0.h" + +#ifdef BMP + #include "Adafruit_BMP183.h" + #define BMP183_CLK 12 // CLOCK + #define BMP183_SDO 13 // AKA MISO + #define BMP183_SDI 14 // AKA MOSI + #define BMP183_CS 15 // CHIP SELECT + Adafruit_BMP183 bmp = Adafruit_BMP183(BMP183_CLK, BMP183_SDO, BMP183_SDI, BMP183_CS); +#endif +#ifdef LED + #ifndef SSD + #include "Adafruit_LEDBackpack.h" + #endif + #include "Adafruit_GFX.h" + void write4Digit(Adafruit_AlphaNum4 alpha, float num); + Adafruit_AlphaNum4 alpha4 = Adafruit_AlphaNum4(); +#endif +// Adafruit_LSM9DS0 lsm = Adafruit_LSM9DS0(); +//////////////////////////////////////////////////////////////////////////////////////////////// + +float tLast = millis(); + +unsigned long int tBegin = millis(); +float uptime = 0.0; + +// WiFi parameters +const char* ssid = "WIFIAP"; +const char* password = "PASSWORD"; + + +ESP8266WebServer server(80); +boolean ledOn=true; + +WiFiUDP Udp; +int udpPort = 9990; // udp port +String udpIP = "XX.XX.XX.XX"; // app server ip address +float udpFRQ = 5.; // update frequency + +int rssi; +String dpTemp="mcp"; + +boolean hasMCP=false, + hasHTU=false, + hasBMP=false, + hasTMP=false, + hasMPL=false, + hasALPHNUM=false, + hasSI=false, + hasTSL=false, + hasMLX=false, + hasINA=false; + +static boolean hasLSM=false; + +float f,hum,htuT,dp,hi; +float bp, bmpT; +float mplbp,mplT,mplAlt; +float Vshunt, Vbus, Ima, Vload; +float uvI; +float objT,dieT; +float brightness; + +Settings settings; + +DataSet dataset; +void readSensors(void){ + + #ifdef MCP + f=mcp.readTempC() * 1.8 + 32.0; + #endif + + #ifdef HTU + hum=htu.readHumidity(); + htuT=htu.readTemperature() * 1.8 + 32.0; + #endif + + #ifdef TMP + objT=tmp.readObjTempC() * 1.8 + 32.0; + dieT=tmp.readDieTempC() * 1.8 + 32.0; + #endif + + #ifdef BMP + bmpT=bmp.getTemperature() * 1.8 + 32.0; + bp=bmp.getPressure() * 0.0002953; + float alt = 290.; + bp = meanSeaLevelPressure(bp, bmpT, alt); + + #endif + + + #ifdef INA + Vshunt = ina219.getShuntVoltage_mV(); + Vbus = ina219.getBusVoltage_V(); + Ima = ina219.getCurrent_mA(); + Vload = Vbus + (Vshunt / 1000.); + hasINA=true; + #endif + + #ifdef MPL + mplbp = mpl.getPressure() * 0.0002953; + mplT = mpl.getTemperature() * 1.8 + 32.; + mplAlt = mpl.getAltitude(); + float alt = 292.0; + mplbp = meanSeaLevelPressure(mplbp, mplT, alt); + #endif + + #ifdef SI + uvI = si.readUV(); + uvI /= 100.0; + #endif + + #ifdef TSL + brightness = advancedLightSensorRead(); + #endif + + #ifdef MLX + dieT=mlx.readAmbientTempF(); + objT=mlx.readObjectTempF(); + + #endif + + if (dpTemp=="mcp"){ + dp=dewPoint(f,hum); + hi=heatIndex(f,hum); + } else if (dpTemp=="htu"){ + dp=dewPoint(htuT,hum); + hi=heatIndex(htuT,hum); + } + rssi = WiFi.RSSI(); + uptime = 0.001 * (millis() - tBegin); + dataset.nData=0; + // #ifdef MCP + if (hasMCP){ + dataset.data[dataset.nData].value=f; + dataset.data[dataset.nData].name="Temperature"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + } + // #endif + // #ifdef HTU + if (hasHTU){ + dataset.data[dataset.nData].value=htuT; + dataset.data[dataset.nData].name="HTU Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=hum; + dataset.data[dataset.nData].name="Humidity"; + dataset.data[dataset.nData].unit="%"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 60.; + dataset.data[dataset.nData].ll1 = 40.; + dataset.data[dataset.nData].ll2 = 20.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=dp; + dataset.data[dataset.nData].name="Dewpoint"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 65.; + dataset.data[dataset.nData].ul1 = 60.; + dataset.data[dataset.nData].ll1 = 45.; + dataset.data[dataset.nData].ll2 = 30.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=hi; + dataset.data[dataset.nData].name="Heat Index"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 90.; + dataset.data[dataset.nData].ul1 = 80.; + dataset.data[dataset.nData].ll1 = 75.; + dataset.data[dataset.nData].ll2 = -100.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + } + // #endif + // #ifdef BMP + if (hasBMP){ + dataset.data[dataset.nData].value=bp; + dataset.data[dataset.nData].name="Pressure"; + dataset.data[dataset.nData].unit="in-Hg"; + dataset.data[dataset.nData].ul2 = 30.25; + dataset.data[dataset.nData].ul1 = 30.; + dataset.data[dataset.nData].ll1 = 29.75; + dataset.data[dataset.nData].ll2 = 29.5; + dataset.data[dataset.nData].invertLimits = true; + dataset.nData++; + + dataset.data[dataset.nData].value=bmpT; + dataset.data[dataset.nData].name="Pressure Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + } + if (hasMPL){ + dataset.data[dataset.nData].value=mplbp; + dataset.data[dataset.nData].name="Pressure"; + dataset.data[dataset.nData].unit="in-Hg"; + dataset.data[dataset.nData].ul2 = 30.25; + dataset.data[dataset.nData].ul1 = 30.; + dataset.data[dataset.nData].ll1 = 29.75; + dataset.data[dataset.nData].ll2 = 29.5; + dataset.data[dataset.nData].invertLimits = true; + dataset.nData++; + + dataset.data[dataset.nData].value=mplT; + dataset.data[dataset.nData].name="Pressure Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + } + if (hasINA){ + dataset.data[dataset.nData].value=Vload; + dataset.data[dataset.nData].name="Load"; + dataset.data[dataset.nData].unit="V"; + dataset.data[dataset.nData].ul2 = 5.5; + dataset.data[dataset.nData].ul1 = 5.0; + dataset.data[dataset.nData].ll1 = 3.4; + dataset.data[dataset.nData].ll2 = 3.2; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + + dataset.data[dataset.nData].value=Ima; + dataset.data[dataset.nData].name="Current"; + dataset.data[dataset.nData].unit="mA"; + dataset.data[dataset.nData].ul2 = 200; + dataset.data[dataset.nData].ul1 = 100; + dataset.data[dataset.nData].ll1 = 50; + dataset.data[dataset.nData].ll2 = 30; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + } + + + // #endif + // #ifdef SI + if (hasSI){ + dataset.data[dataset.nData].value=uvI; + dataset.data[dataset.nData].name="UV Index"; + dataset.data[dataset.nData].unit=""; + dataset.data[dataset.nData].ul2 = 8.0; + dataset.data[dataset.nData].ul1 = 6.5; + dataset.data[dataset.nData].ll1 = 5.0; + dataset.data[dataset.nData].ll2 = 3.0; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + } + // #endif + // #ifdef TMP + if (hasTMP){ + dataset.data[dataset.nData].value=objT; + dataset.data[dataset.nData].name="IR Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=dieT; + dataset.data[dataset.nData].name="TMP Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=dieT - objT; + dataset.data[dataset.nData].name="IR dT"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 25.; + dataset.data[dataset.nData].ul1 = 20.; + dataset.data[dataset.nData].ll1 = 15.; + dataset.data[dataset.nData].ll2 = 10.; + dataset.data[dataset.nData].invertLimits = true; + dataset.nData++; + + } + + if (hasMLX){ + dataset.data[dataset.nData].value=objT; + dataset.data[dataset.nData].name="IR Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=dieT; + dataset.data[dataset.nData].name="MLX Temp"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=dieT - objT; + dataset.data[dataset.nData].name="IR dT"; + dataset.data[dataset.nData].unit="°F"; + dataset.data[dataset.nData].ul2 = 25.; + dataset.data[dataset.nData].ul1 = 20.; + dataset.data[dataset.nData].ll1 = 15.; + dataset.data[dataset.nData].ll2 = 10.; + dataset.data[dataset.nData].invertLimits = true; + dataset.nData++; + + } +// #endif + // #ifdef TSL + if (hasTSL){ + dataset.data[dataset.nData].value=brightness; + dataset.data[dataset.nData].name="Brightness"; + dataset.data[dataset.nData].unit="lux"; + dataset.data[dataset.nData].ul2 = 100000.; + dataset.data[dataset.nData].ul1 = 10000.; + dataset.data[dataset.nData].ll1 = 1000.; + dataset.data[dataset.nData].ll2 = 100.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + } + // #endif + + dataset.data[dataset.nData].value=uptime; + dataset.data[dataset.nData].name="uptime"; + dataset.data[dataset.nData].unit="s"; + dataset.data[dataset.nData].ul2 = 80.; + dataset.data[dataset.nData].ul1 = 75.; + dataset.data[dataset.nData].ll1 = 70.; + dataset.data[dataset.nData].ll2 = 65.; + dataset.data[dataset.nData].invertLimits = false; + dataset.nData++; + + dataset.data[dataset.nData].value=rssi; + dataset.data[dataset.nData].name="RSSI"; + dataset.data[dataset.nData].unit="dBm"; + dataset.data[dataset.nData].ul2 = -20.; + dataset.data[dataset.nData].ul1 = -40.; + dataset.data[dataset.nData].ll1 = -60.; + dataset.data[dataset.nData].ll2 = -70.; + dataset.data[dataset.nData].invertLimits = true; + dataset.nData++; + + + + +} + + + +void udpSendPacket(void); + + + +//////////////////////////////////////////////////////////////////////// +// Webserver handling +//////////////////////////////////////////////////////////////////////// +void handle_root() { + Settings settings; + settings.udpFRQ=udpFRQ; + settings.udpIP=udpIP; + settings.udpPort=udpPort; + esp.http.settings = settings; + + + esp.http.updatePage(dataset, esp.packet); + server.send(200, "text/html", esp.http.page()); +} + +void handle_post(){ + esp.formPacket(dataset); + // server.send(200, "application/json", esp.packet); // send to someones browser when asked + server.send(200, "text/html", ""); // send to someones browser when asked + Serial.println("Sent packet for temp"); + + for (uint8_t i=0; i"); // send to someones browser when asked + Serial.println("Reset requested"); + esp.triggerReset(); +} + +void handle_ledOff(){ + ledOn=false; + server.send(200, "text/html", ""); +} +void handle_ledOn(){ + ledOn=true; + server.send(200, "text/html", ""); +} + + +void handle_spiffs(){ + SPIFFS.begin(); + + File f = SPIFFS.open("/test.html", "r"); + if (!f) { + Serial.println("file open failed"); + } else{ + + // Serial.println(f.readString()); + server.send(200, "text/html", f.readString()); + } +} + + +//////////////////////////////////////////////////////////////////////// +// Setup +//////////////////////////////////////////////////////////////////////// +void setup(void) +{ + Wire.begin(); + Wire.pins(4, 0); + // Start Serial + Serial.begin(115200); + + esp.begin(ssid, password); + // esp.http.begin(); + esp.http.stationName = STR(NAME);//"Development Server"; + esp.http.version = VERSION; + esp.http.begin(); + esp.http.updatePage(dataset, esp.packet); + +#ifdef SSD + display.begin(SSD1306_SWITCHCAPVCC, 0x3D); // initialize with the I2C addr 0x3D (for the 128x64) + display.setTextSize(0); + display.setTextColor(WHITE); + int line = 0; +// delay(2000); + + +#endif + + + server.on("/", handle_root); + + server.on("/data", handle_data); + + server.on("/post", handle_post); + + server.on("/reset", handle_reset); + + server.on("/ledOff", handle_ledOff); + server.on("/ledOn", handle_ledOn); + + server.on("/spiffs", handle_spiffs); + + httpUpdater.setup(&server); + server.begin(); + Serial.println("HTTP server started"); + + // MDNS.addService("http", "tcp", 80); + // if (!MDNS.begin("snazzy")) { + // Serial.println("Error setting up MDNS responder!"); + // while(1) { + // delay(1000); + // } + // } + // Serial.println("mDNS responder started"); + + +#ifdef MCP + if (mcp.begin()) { + Serial.println("Found MCP sensor"); + hasMCP=true; + } +#endif + +#ifdef HTU + if (htu.begin()) { + Serial.println("Found HTU sensor"); + hasHTU=true; + } +#endif + +#ifdef BMP + if (bmp.begin()) { + Serial.println("Found BMP sensor"); + hasBMP=true; + } +#endif + +#ifdef TMP + if (tmp.begin()) { + Serial.println("Found TMP sensor"); + hasTMP=true; + } +#endif + +#ifdef MLX + Serial.println("MLX sensor requested"); + mlx.begin(); + hasMLX=true; +#endif + +#ifdef SI + Serial.println("SI requested"); + if (si.begin()) { + Serial.println("Found SI sensor"); + hasSI=true; + } + hasSI=true; +#endif + +#ifdef TSL + if (tsl.begin()) { + Serial.println("Found TSL sensor"); + configureSensor(TSL2591_GAIN_LOW, TSL2591_INTEGRATIONTIME_100MS); + hasTSL=true; + } +#endif + +#ifdef INA + Serial.println("INA sensor requested"); + ina219.begin(); + hasINA=true; +#endif + +#ifdef MPL + if (mpl.begin()) { + Serial.println("Found MPL sensor"); + hasMPL=true; + } +#endif + +#ifdef LED + alpha4.begin(0x70); // pass in the address +#endif + + Serial.println("Finished uploading sensors"); + // + + // Hostname defaults to esp8266-[ChipID] + ArduinoOTA.setHostname("snazz-face"); + + ArduinoOTA.setPassword((const char *)"insecure"); + + ArduinoOTA.onStart([]() { + Serial.println("Start"); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + Serial.println("Ready"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + // String str = ""; + // Dir dir = SPIFFS.openDir("/"); + // while (dir.next()) { + // str += dir.fileName(); + // str += " / "; + // str += dir.fileSize(); + // str += "\r\n"; + // } + // Serial.print(str); +} + + +void udpSendPacket(void){ + if (millis() - tLast > int(1000*udpFRQ)){ + Udp.beginPacket(udpIP.c_str(),udpPort); + esp.formPacket(dataset); + Udp.print(esp.packet); + // Serial.println(esp.packet); + tLast=millis(); + Udp.endPacket(); + if (ledOn) { + esp.triggerActivityLED(); + } +#ifdef SSD + display.clearDisplay(); + display.setCursor(0,0); + display.print(esp.http.stationName); + display.setCursor(0,8); + display.print("Version: "); + display.print(VERSION); + display.setCursor(0,16); + display.print("RSSI: "); + display.print(rssi); + display.setCursor(0,24); + display.print("IP: "); + display.print(esp.stationIP); + + for (int i=0; i < dataset.nData; i++){ + display.setCursor(0,32+8*i); + display.print(dataset.data[i].name + " = "); + display.print(dataset.data[i].value); + // display.print(dataset.data[i].unit); + + } + + display.display(); + // display.setTextSize(1); + // display.setTextColor(WHITE); + // display.setCursor(0,0); + // display.println("Hello, world!"); + // display.setTextColor(BLACK, WHITE); // 'inverted' text + // display.println(3.141592); + // display.setTextSize(2); + // display.setTextColor(WHITE); + // display.print("0x"); display.println(0xDEADBEEF, HEX); + // display.display(); + // delay(2000); +#endif + + } +} + + + +float toggleTime=millis(); +float lastTime = toggleTime; +int count=0, toggle=0; +void loop() +{ + ArduinoOTA.handle(); + server.handleClient(); + readSensors(); + udpSendPacket(); + + +#ifdef LED + count++; + if(millis() - toggleTime > 2*1000){ + toggleTime=millis(); + toggle++; + } + if(millis() - lastTime > 250){ + lastTime = millis(); + if(toggle % 2 == 0){ + alpha4.setBrightness(15); + write4Digit(alpha4,dataset.data[0].value); + } else{ + alpha4.setBrightness(3); + write4Digit(alpha4,dataset.data[3].value); + } + } +#endif + +} + +#ifdef LED + + static const uint16_t alphafonttable[] PROGMEM = { + + 0b0000000000000001, + 0b0000000000000010, + 0b0000000000000100, + 0b0000000000001000, + 0b0000000000010000, + 0b0000000000100000, + 0b0000000001000000, + 0b0000000010000000, + 0b0000000100000000, + 0b0000001000000000, + 0b0000010000000000, + 0b0000100000000000, + 0b0001000000000000, + 0b0010000000000000, + 0b0100000000000000, + 0b1000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0000000000000000, + 0b0001001011001001, + 0b0001010111000000, + 0b0001001011111001, + 0b0000000011100011, + 0b0000010100110000, + 0b0001001011001000, + 0b0011101000000000, + 0b0001011100000000, + 0b0000000000000000, // + 0b0000000000000110, // ! + 0b0000001000100000, // " + 0b0001001011001110, // # + 0b0001001011101101, // $ + 0b0000110000100100, // % + 0b0010001101011101, // & + 0b0000010000000000, // ' + 0b0010010000000000, // ( + 0b0000100100000000, // ) + 0b0011111111000000, // * + 0b0001001011000000, // + + 0b0000100000000000, // , + 0b0000000011000000, // - + 0b0000000000000000, // . + 0b0000110000000000, // / + 0b0000110000111111, // 0 + 0b0000000000000110, // 1 + 0b0000000011011011, // 2 + 0b0000000010001111, // 3 + 0b0000000011100110, // 4 + 0b0010000001101001, // 5 + 0b0000000011111101, // 6 + 0b0000000000000111, // 7 + 0b0000000011111111, // 8 + 0b0000000011101111, // 9 + 0b0001001000000000, // : + 0b0000101000000000, // ; + 0b0010010000000000, // < + 0b0000000011001000, // = + 0b0000100100000000, // > + 0b0001000010000011, // ? + 0b0000001010111011, // @ + 0b0000000011110111, // A + 0b0001001010001111, // B + 0b0000000000111001, // C + 0b0001001000001111, // D + 0b0000000011111001, // E + 0b0000000001110001, // F + 0b0000000010111101, // G + 0b0000000011110110, // H + 0b0001001000000000, // I + 0b0000000000011110, // J + 0b0010010001110000, // K + 0b0000000000111000, // L + 0b0000010100110110, // M + 0b0010000100110110, // N + 0b0000000000111111, // O + 0b0000000011110011, // P + 0b0010000000111111, // Q + 0b0010000011110011, // R + 0b0000000011101101, // S + 0b0001001000000001, // T + 0b0000000000111110, // U + 0b0000110000110000, // V + 0b0010100000110110, // W + 0b0010110100000000, // X + 0b0001010100000000, // Y + 0b0000110000001001, // Z + 0b0000000000111001, // [ + 0b0010000100000000, // + 0b0000000000001111, // ] + 0b0000110000000011, // ^ + 0b0000000000001000, // _ + 0b0000000100000000, // ` + 0b0001000001011000, // a + 0b0010000001111000, // b + 0b0000000011011000, // c + 0b0000100010001110, // d + 0b0000100001011000, // e + 0b0000000001110001, // f + 0b0000010010001110, // g + 0b0001000001110000, // h + 0b0001000000000000, // i + 0b0000000000001110, // j + 0b0011011000000000, // k + 0b0000000000110000, // l + 0b0001000011010100, // m + 0b0001000001010000, // n + 0b0000000011011100, // o + 0b0000000101110000, // p + 0b0000010010000110, // q + 0b0000000001010000, // r + 0b0010000010001000, // s + 0b0000000001111000, // t + 0b0000000000011100, // u + 0b0010000000000100, // v + 0b0010100000010100, // w + 0b0010100011000000, // x + 0b0010000000001100, // y + 0b0000100001001000, // z + 0b0000100101001001, // { + 0b0001001000000000, // | + 0b0010010010001001, // } + 0b0000010100100000, // ~ + 0b0011111111111111, + + }; + void write4Digit(Adafruit_AlphaNum4 alpha, float num){ + int digit,indx; + int lognumIndx = int(log10(num)); + + + + for (indx=3; indx>=0; indx--){ + int digit = int(num * pow(10.0,-lognumIndx+indx)) % 10; + char buff[1]; + itoa(digit,buff,10); + // Serial.print(indx); + // Serial.print( buff[0]); + // Serial.println(); + alpha.writeDigitAscii(indx,buff[0]); + if (indx == lognumIndx) { + alpha.writeDigitRaw(indx,0x4000+pgm_read_word(alphafonttable+buff[0])); + } + } + // Serial.println(); + alpha4.clear(); + alpha.writeDisplay(); + + + } +#endif + + +#ifdef TSL +void configureSensor(tsl2591Gain_t gainSetting, tsl2591IntegrationTime_t timeSetting) +{ + // You can change the gain on the fly, to adapt to brighter/dimmer light situations + tsl.setGain(gainSetting); + //tsl.setGain(TSL2591_GAIN_LOW); // 1x gain (bright light) + //tsl.setGain(TSL2591_GAIN_MED); // 25x gain + //tsl.setGain(TSL2591_GAIN_HIGH); // 428x gain + + // Changing the integration time gives you a longer time over which to sense light + // longer timelines are slower, but are good in very low light situtations! + tsl.setTiming(timeSetting); + //tsl.setTiming(TSL2591_INTEGRATIONTIME_100MS); // shortest integration time (bright light) + //tsl.setTiming(TSL2591_INTEGRATIONTIME_200MS); + //tsl.setTiming(TSL2591_INTEGRATIONTIME_300MS); + //tsl.setTiming(TSL2591_INTEGRATIONTIME_400MS); + //tsl.setTiming(TSL2591_INTEGRATIONTIME_500MS); + //tsl.setTiming(TSL2591_INTEGRATIONTIME_600MS); // longest integration time (dim light) + + /* Display the gain and integration time for reference sake */ + // XBee.println("------------------------------------"); + // XBee.print ("Gain: "); + tsl2591Gain_t gain = tsl.getGain(); + // switch(gain) + // { + // case TSL2591_GAIN_LOW: + // XBee.println("1x (Low)"); + // break; + // case TSL2591_GAIN_MED: + // XBee.println("25x (Medium)"); + // break; + // case TSL2591_GAIN_HIGH: + // XBee.println("428x (High)"); + // break; + // case TSL2591_GAIN_MAX: + // XBee.println("9876x (Max)"); + // break; + // } + // XBee.print ("Timing: "); + // XBee.print((tsl.getTiming() + 1) * 100, DEC); + // XBee.println(" ms"); + // XBee.println("------------------------------------"); + // XBee.println(""); +} + +float advancedLightSensorRead(void) +{ + // More advanced data read example. Read 32 bits with top 16 bits IR, bottom 16 bits full spectrum + // That way you can do whatever math and comparisons you want! + uint32_t lum = tsl.getFullLuminosity(); + uint16_t ir, full; + float lux; + lum = tsl.getFullLuminosity(); // first reading will be incorrect of Gain or Time was changed + ir = lum >> 16; + full = lum & 0xFFFF; + lux = tsl.calculateLux(full, ir); +// XBee.println(); +// XBee.print(F("Lux ")); +// XBee.println(lux); + + if (full < 100){ + switch (tsl.getGain()) + { + case TSL2591_GAIN_LOW : + configureSensor(TSL2591_GAIN_MED, TSL2591_INTEGRATIONTIME_200MS); + // XBee.print(F("Gain raised to MED")); + break; + case TSL2591_GAIN_MED : + configureSensor(TSL2591_GAIN_HIGH, TSL2591_INTEGRATIONTIME_200MS); + // XBee.print(F("Gain raised to HIGH")); + break; + /* case TSL2591_GAIN_HIGH : + configureSensor(TSL2591_GAIN_MAX, TSL2591_INTEGRATIONTIME_200MS); + XBee.print("Gain raised to MAX"); + break; + case TSL2591_GAIN_MAX : + XBee.print("Gain already at MAX"); + break; + */ + default: + configureSensor(TSL2591_GAIN_MED, TSL2591_INTEGRATIONTIME_200MS); + break; + } + + } + + if (full > 30000){ + switch (tsl.getGain()) + { + case TSL2591_GAIN_LOW : + // XBee.print(F("Gain already at LOW")); + break; + case TSL2591_GAIN_MED : + configureSensor(TSL2591_GAIN_LOW, TSL2591_INTEGRATIONTIME_200MS); + // XBee.print(F("Gain lowered to LOW")); + break; + /* case TSL2591_GAIN_HIGH : + configureSensor(TSL2591_GAIN_MED, TSL2591_INTEGRATIONTIME_200MS); + XBee.print("Gain lowered to MED"); + break; + case TSL2591_GAIN_MAX : + configureSensor(TSL2591_GAIN_HIGH, TSL2591_INTEGRATIONTIME_200MS); + XBee.print("Gain lowered to MED"); + break; + */ + default: + configureSensor(TSL2591_GAIN_MED, TSL2591_INTEGRATIONTIME_200MS); + break; + } + + } + return lux; + +} +#endif diff --git a/esp8266/platformio.ini b/esp8266/platformio.ini new file mode 100644 index 0000000..a24bbc8 --- /dev/null +++ b/esp8266/platformio.ini @@ -0,0 +1,29 @@ +# +# Project Configuration File +# +# A detailed documentation with the EXAMPLES is located here: +# http://docs.platformio.org/en/latest/projectconf.html +# + +# A sign `#` at the beginning of the line indicates a comment +# Comment lines are ignored. + +# Simple and base environment +# [env:mybaseenv] +# platform = %INSTALLED_PLATFORM_NAME_HERE% +# framework = +# board = +# +# Automatic targets - enable auto-uploading +# targets = upload + +[env:dev] +platform = espressif +framework = arduino +board = nodemcu +; upload_port = IPADDRESS +; upload_flags = --auth=PASSWORD +targets = upload +upload_speed = 921600 +build_flags = -DNAME="Weather Station" -DMCP -DHTU -DINA -DSI -DMLX -DTSL +src_filter = "+" diff --git a/esp8266/weatherCalcs.cpp b/esp8266/weatherCalcs.cpp new file mode 100644 index 0000000..296ea41 --- /dev/null +++ b/esp8266/weatherCalcs.cpp @@ -0,0 +1,81 @@ +#include "weatherCalcs.h" +//#include + +float heatIndex(float T, float R){ + float c1,c2,c3,c4,c5,c6,c7,c8,c9; + + + float HI = 0.5 * (T + 61.0 + ((T-68.0)*1.2) + (R*0.094)); + + if (HI > 80.0){ + c1 = -42.379; + c2=2.04901523; + c3=10.144333127; + c4=-0.22475541; + c5=-6.83783e-3; + c6=-5.481717e-2; + c7=1.22874e-3; + c8=8.5282e-4; + c9=-1.99e-6; + float T2 = T*T; + float R2 = R*R; + HI = c1 + c2 * T + c3*R + c4 * T * R + c5 * T2 + + c6 * R2 + c7 * T2 * R + c8 * T * R2 + c9 * T2 * R2; + } + + + return HI; +} + + +float dewPoint(float farenheight, float humidity) +{ + // (1) Saturation Vapor Pressure = ESGG(T) + float celsius = (farenheight - 32.0)/1.8; + float RATIO = 373.15 / (273.15 + celsius); + float RHS = -7.90298 * (RATIO - 1); + RHS += 5.02808 * log10(RATIO); + RHS += -1.3816e-7 * (pow(10, (11.344 * (1 - 1/RATIO ))) - 1) ; + RHS += 8.1328e-3 * (pow(10, (-3.49149 * (RATIO - 1))) - 1) ; + RHS += log10(1013.246); + + // factor -3 is to adjust units - Vapor Pressure SVP * humidity + float VP = pow(10, RHS - 3) * humidity; + + // (2) DEWPOINT = F(Vapor Pressure) + float T = log(VP/0.61078); // temp var + return (241.88 * T) / (17.558 - T) * 1.8 + 32.0; +} + +float meanSeaLevelPressure(float P, float T, float h){ + float TK = (T - 32.)* 5./9. + 273.15; + float r1 = 1. - 0.0065 * h / ( TK + 0.0065 * h); + float r2 = P * pow(r1,-5.257); // P inHG to hPa + + return r2; +} + + + +const int Ndirections=8; +float voltFracByDir[Ndirections]={ + 0.77, 0.46, 0.09, 0.18, 0.29, 0.62, 0.92, 0.87}; +float windDirArray[Ndirections]={ + 0, 45, 90, 135, 180, 225, 270, 315}; + + + +float windDirByVoltageFraction( float inputVoltageFraction) +{ + float Vdiffmin=1.0; + float windDir; + for (int i=0; i Vdiff){ + Vdiffmin = Vdiff; + windDir = windDirArray[i]; + } + } + return windDir; + +} diff --git a/esp8266/weatherCalcs.h b/esp8266/weatherCalcs.h new file mode 100644 index 0000000..bfce9aa --- /dev/null +++ b/esp8266/weatherCalcs.h @@ -0,0 +1,5 @@ +#include + +float heatIndex(float T, float R); +float dewPoint(float farenheight, float humidity); +float meanSeaLevelPressure(float P, float T, float h); diff --git a/screenshots/FlaskWXServer.png b/screenshots/FlaskWXServer.png new file mode 100644 index 0000000..ad32f1e Binary files /dev/null and b/screenshots/FlaskWXServer.png differ diff --git a/screenshots/WXDevice_ESP8266.png b/screenshots/WXDevice_ESP8266.png new file mode 100644 index 0000000..956e233 Binary files /dev/null and b/screenshots/WXDevice_ESP8266.png differ