pull/2/head
Gabe Shaughnessy 2016-09-28 11:31:38 -05:00
commit 02e008ab25
17 zmienionych plików z 2263 dodań i 0 usunięć

14
README.md 100644
Wyświetl plik

@ -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/

66
app/app.py 100644
Wyświetl plik

@ -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)

Wyświetl plik

@ -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

Wyświetl plik

@ -0,0 +1,4 @@
{
font-size: 320px;
line-height: 2;
}

198
app/stationDB.py 100644
Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -0,0 +1,153 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Home Conditions</title>
<meta name="viewport" content="width=device-width, initial-scale=0.6">
<script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.0/socket.io.js"></script>
<LINK href="https://bootswatch.com/slate/bootstrap.min.css" rel="stylesheet" type="text/css">
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="{{ url_for('static', filename='js/lib/jquery.bsAlerts.min.js')}}"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
namespace = '/web'; // change to an empty string to use the global namespace
// the socket.io documentation recommends sending an explicit package upon connection
// this is specially important when using the global namespace
var socket = io.connect('http://' + document.domain + ':' + location.port + namespace);
$("#success-alert").hide();
// event handler for server sent data
// the data is displayed in the "Received" section of the page
socket.on('dataPacket', function(msg) {
console.log(msg.packet);
var parsedData= msg.packet;//JSON.parse(msg.packet);
console.log('from: '+parsedData.name);
var name = parsedData.name.replace(/ /g,'')
var text='';
var options = new Array();
$.each(parsedData.data, function(index, option) {
if ((index != 'uptime') && (index != 'RSSsI')){
options.push("<tr class='"+option.class+"'> <td class='col-md-4'><h6>" + index + "</h6></td> <td class='col-xs-8'> <h4>"+ option.value + " " + option.unit + "</h4></td></tr>");
if (option.class == 'danger' || option.class == 'info'){
console.log('options: ' + options);
// console.log('test');
$(document).trigger("add-alerts", [
{
'message': name + " " + index + " is outside of bounds: "+ option.value,
'priority': option.class
}
]);
}
}
});
// get total seconds between the times
var delta = parsedData['data']['uptime']['value'];
console.log(parsedData)
var days = Math.floor(delta / 86400);
delta -= days * 86400;
var hours = Math.floor(delta / 3600) % 24;
delta -= hours * 3600;
var minutes = Math.floor(delta / 60) % 60;
delta -= minutes * 60;
var seconds = Math.round(delta % 60,1);
uptimeString =
$('#'+name).html("<table class='table table-striped table-hover'><thead><tr><th>" + parsedData.name + " <br> uptime: " + days + "d " + hours + "h " + minutes + "m " + seconds + "s " +" </th><th>Value</th><th></th></tr></thead><tbody>" + options.join('')+ "</body></table>");
var nameIP = parsedData.name.replace(/ /g,'')+'IP'
var ipAddr = parsedData.network.ip
$('a[href="' + name + '"]').attr('href','http://'+ipAddr)
});
// event handler for new connections
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'});
});
});
</script>
</head>
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- <a class="navbar-brand" href="#">Room Conditions</a> -->
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav ">
<li><a href="#" target="_blank">Ephemeris</a></li>
<li><a href="http://10.0.1.62:5006" target="_blank">Bokeh Plots</a></li>
<li><a href="http://www.ssec.wisc.edu/data/wisc/" target="_blank">Satellite</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> Sensor pages <span class="caret"></span></a>
<ul class="dropdown-menu">
<li><a href="DevelopmentServer" target="_blank">Dev</a></li>
<li><a href="WeatherStation" target="_blank">Weather Station</a></li>
<li role="separator" class="divider"></li>
<li><a href="Garage" target="_blank">Garage</a></li>
<li><a href="Office" target="_blank">Office</a></li>
<li><a href="ServerRoom" target="_blank">Server Room</a></li>
<li><a href="Shop" target="_blank">Shop</a></li>
<li><a href="FamilyRoom" target="_blank">Family Room</a></li>
<li><a href="RecRoom" target="_blank">Rec Room</a></li>
<li><a href="MasterBedroom" target="_blank">Master Bed</a></li>
<li><a href="LivingRoom" target="_blank">Living Room</a></li>
<li><a href="Kitchen" target="_blank">Kitchen</a></li>
<li><a href="GuestRoom" target="_blank">Guest Room</a></li>
<li><a href="Loft" target="_blank">Loft</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
<body>
<div class="container-fluid">
<div class="col-xs-12">
<div class="page-header"><h1>Room Conditions<h1></div>
<table class="table">
<tr>
<td class='col-xs-3'><div id="WeatherStation"></div></td>
<td class='col-xs-3'><div id="Office"></div><div id="ServerRoom"></div><div id="RecRoom"></div></td>
<td class='col-xs-3'><div id="Garage"></div><div id="GuestRoom"></div></td>
<td class='col-xs-3'><div id="MasterBedroom"></div><div id="Loft"></div></td>
<!-- <td class='col-xs-3'><div id="DevelopmentServer"></div><div id="WifiMonitor"></div></td> -->
<!-- <td class='col-xs-4'><h3>Alerts:</h3><div data-alerts="alerts" data-fade="500"></div></td> -->
</tr>
<tr>
</tr>
<tr>
<td class='col-xs-3'><div id="Shop"></div></td>
<td class='col-xs-3'><div id="FamilyRoom"></div></td>
<td class='col-xs-3'><div id="LivingRoom"></div></td>
<td class='col-xs-3'><div id="Kitchen"></div></td>
</tr>
</table>
</div>
</div>
</body>
</html>

Wyświetl plik

@ -0,0 +1,132 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Home Conditions</title>
<meta name="viewport" content="width=device-width, initial-scale=0.6">
<script type="text/javascript" src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.4.0/socket.io.js"></script>
<LINK href="https://bootswatch.com/slate/bootstrap.min.css" rel="stylesheet" type="text/css">
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<!-- <script src="{{ url_for('static', filename='js/lib/jquery.bsAlerts.min.js')}}"></script> -->
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
namespace = '/web'; // change to an empty string to use the global namespace
// the socket.io documentation recommends sending an explicit package upon connection
// this is specially important when using the global namespace
var socket = io.connect('http://' + document.domain + ':' + location.port + namespace);
$("#success-alert").hide();
// event handler for server sent data
// the data is displayed in the "Received" section of the page
socket.on('dataPacket', function(msg) {
console.log(msg.packet);
var parsedData= msg.packet;//JSON.parse(msg.packet);
console.log('from: '+parsedData.name);
var name = parsedData.name.replace(/ /g,'')
var text='';
var options = new Array();
$.each(parsedData.data, function(index, option) {
if ((index != 'uptime') && (index != 'RSSsI')){
options.push("<tr class='"+option.class+"'> <td class='col-md-4'><h6>" + index + "</h6></td> <td class='col-xs-8'> <h4>"+ option.value + " " + option.unit + "</h4></td></tr>");
if (option.class == 'danger' || option.class == 'info'){
console.log('options: ' + options);
// console.log('test');
$(document).trigger("add-alerts", [
{
'message': name + " " + index + " is outside of bounds: "+ option.value,
'priority': option.class
}
]);
}
}
});
// get total seconds between the times
var delta = parsedData['data']['uptime']['value'];
console.log(parsedData)
var days = Math.floor(delta / 86400);
delta -= days * 86400;
var hours = Math.floor(delta / 3600) % 24;
delta -= hours * 3600;
var minutes = Math.floor(delta / 60) % 60;
delta -= minutes * 60;
var seconds = Math.round(delta % 60,1);
uptimeString =
$('#'+name).html("<table class='table table-striped table-hover'><thead><tr><th>" + parsedData.name + " <br> uptime: " + days + "d " + hours + "h " + minutes + "m " + seconds + "s " +" </th><th>Value</th><th></th></tr></thead><tbody>" + options.join('')+ "</body></table>");
var nameIP = parsedData.name.replace(/ /g,'')+'IP'
var ipAddr = parsedData.network.ip
$('a[href="' + name + '"]').attr('href','http://'+ipAddr)
});
// event handler for new connections
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'});
});
});
</script>
</head>
<nav class="navbar navbar-default">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- <a class="navbar-brand" href="#">Room Conditions</a> -->
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav ">
<li><a href="#" target="_blank">Ephemeris</a></li>
<li><a href="http://10.0.1.62:5006" target="_blank">Bokeh Plots</a></li>
<li><a href="http://www.ssec.wisc.edu/data/wisc/" target="_blank">Satellite</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> Sensor pages <span class="caret"></span></a>
<ul class="dropdown-menu">
<!-- For every station sending data with name "Station Name", add following:
<li><a href="StationName" target="_blank">Station Name</a></li>
-->
<li><a href="WeatherStation" target="_blank">Weather Station</a></li>
</ul>
</li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
</div>
<body>
<div class="container-fluid">
<div class="col-xs-12">
<div class="page-header"><h1>Conditions<h1></div>
<table class="table">
<tr>
<!-- For every station sending data with name "Station Name", add following:
<td class='col-xs-3'><div id="StationName"></div></td> -->
<td class='col-xs-3'><div id="WeatherStation"></div></td>
</tr>
</table>
</div>
</div>
</body>
</html>

Wyświetl plik

@ -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 <Wire.h>
#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="<!DOCTYPE html><html><head><title>" + stationName +"</title>";
// refresh HTML page
header +="<meta http-equiv=\"refresh\" content=\"30\">";
header +="</head>";
css="";
css+="<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
css+="<link rel=\"stylesheet\" href=\"http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css\">";
css+="<script src=\"https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js\"></script>";
css+="<script src=\"http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js\"></script>";
css+="<style>";
css+="footer {";
css+="color: brown;";
css+="font-style: oblique;";
css+="}";
css+="</style>";
css+="<LINK href=\"https://bootswatch.com/slate/bootstrap.min.css\" rel=\"stylesheet\" type=\"text/css\">";
body="<header>";
endHTML="</header></body></html>";
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 = "<div class=\"container\">";
content += "<div class=\"page-header\">";
content += "<h1>" + stationName + "<h1>";
content += "</div>";
dataContent="<table class=\"table table-striped table-hover\"><thead><tr><th>Measurement</th><th>Value</th><th></th></tr></thead><tbody>";
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 += "<tr data-toggle='collapse' data-target='.collapseTest' class=\"" + dataclass + "\">" + " <td class=\"col-md-6\">" + dataset.data[i].name + "</td><td class=\"col-md-5\">" + String(dataset.data[i].value) + " " + dataset.data[i].unit + "</td><td class=\"col-md-1\"><button data-toggle='collapse' data-target='.collapseTest' class='btn btn-default btn-xs'><span class='glyphicon glyphicon-menu-up'></span></button></td></tr>";
} else {
dataContent += "<tr class=\"" + dataclass + " collapse in collapseTest\">" + " <td class=\"col-md-6\">" + dataset.data[i].name + "</td><td class=\"col-md-5\">" + String(dataset.data[i].value) + " " + dataset.data[i].unit + "</td><td class='col-md-1'></td></tr>";
}
}
dataContent += "</tbody></table>";
dataContent += " <div class=\"dropdown\"> \
<button class=\"btn btn-primary dropdown-toggle\" type=\"button\" data-toggle=\"dropdown\">Dewpoint Calculation \
<span class=\"caret\"></span></button> \
<ul class=\"dropdown-menu\"> \
<li><a href=\"post?dpTemp=mcp\">High Res Temperature</a></li> \
<li><a href=\"post?dpTemp=htu\">Humidity Temperature</a></li> \
</ul> \
</div>";
dataContent += "<form action=\"post\" method=\"post\" >";
dataContent += "<div class=\"form-group row\">";
dataContent += "<label class=\"col-sm-4\" for=\"udpFRQ\">UDP Packet Frequency: </label>";
dataContent += "<div class=\"col-sm-4\"><input type=\"text\" name=\"udpFRQ\"; value=\""+String(settings.udpFRQ)+"\"class=\"btn-link\"></div>";
dataContent += "</div>";
dataContent += "<div class=\"form-group row\">";
dataContent += "<label class=\"col-sm-4\" for=\"udpIP\">UDP IP: </label>";
dataContent += "<div class=\"col-sm-4\"><input type=\"text\" name=\"udpIP\"; value=\""+String(settings.udpIP)+"\"class=\"btn-link\"></div>";
dataContent += "</div>";
dataContent += "<div class=\"form-group row\">";
dataContent += "<label class=\"col-sm-4\" for=\"udpPort\">UDP Port: </label>";
dataContent += "<div class=\"col-sm-4\"><input type=\"text\" name=\"udpPort\"; value=\""+String(settings.udpPort)+"\"class=\"btn-link\"></div>";
dataContent += "</div>";
dataContent += "<input type=\"submit\" class=\"btn btn-info col-sm-4\" value=\"Submit\">";
dataContent += "</form><br>";
buttons = "<button type=\"button\" class=\"btn btn-info btn-block\" data-toggle=\"modal\" data-target=\"#myModal\">JSON Data</button><!-- Modal --><div class=\"modal fade\" id=\"myModal\" role=\"dialog\"><div class=\"modal-dialog\"><!-- Modal content--><div class=\"modal-content\"><div class=\"modal-header\"><button type=\"button\" class=\"close\" data-dismiss=\"modal\">&times;</button><h4 class=\"modal-title\">JSON Data</h4></div><div class=\"modal-body\"><p>"+ packet +"</p></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-default\" data-dismiss=\"modal\">Close</button></div></div></div></div>";
buttons += "<form method='POST' action='/update' enctype='multipart/form-data'>\
<input type='file' name='update' class='btn btn-info col-xs-8'>\
<input type='submit' value='Update Firmware' class='btn btn-danger col-xs-4'> \
</form>";
buttons += "<br><form action='ledOff' method='GET'><button class='btn-danger col-xs-4' name=\"led\" value=\"off\">LED Off</button></form>";
buttons += "<form action='ledOn' method='GET'><button class='btn-success col-xs-4' name=\"led\" value=\"off\">LED On</button></form>";
buttons += "<button type=\"button\" class=\"btn btn-danger btn-block\"><a href=\"reset\">Reset Device</a></button>";
footer="<br<br><div class=\"container\"><div class=\"panel-footer\" class=\"container-fluid\"><div class=\"row\"></div>Hello from " + stationName + "<br>Station sensor: Version " + String(version) + " <br> Uptime: " + String(uptime) + " seconds </div></div></div>";
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 + "} }";
}

Wyświetl plik

@ -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 <Wire.h>
#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;
};

Plik diff jest za duży Load Diff

Wyświetl plik

@ -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 = "+<ESP_station.cpp>"

Wyświetl plik

@ -0,0 +1,81 @@
#include "weatherCalcs.h"
//#include <math.h>
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<Ndirections; i++){
float Vdiff = fabs(inputVoltageFraction - voltFracByDir[i]);
if (Vdiffmin > Vdiff){
Vdiffmin = Vdiff;
windDir = windDirArray[i];
}
}
return windDir;
}

Wyświetl plik

@ -0,0 +1,5 @@
#include <math.h>
float heatIndex(float T, float R);
float dewPoint(float farenheight, float humidity);
float meanSeaLevelPressure(float P, float T, float h);

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 102 KiB

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 114 KiB