kopia lustrzana https://github.com/gshau/wxserver
Initial commit
commit
02e008ab25
|
@ -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/
|
|
@ -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)
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
font-size: 320px;
|
||||||
|
line-height: 2;
|
||||||
|
}
|
|
@ -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)
|
|
@ -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)
|
|
@ -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()
|
|
@ -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>
|
|
@ -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>
|
|
@ -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\">×</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 + "} }";
|
||||||
|
}
|
|
@ -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
|
@ -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>"
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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 |
Ładowanie…
Reference in New Issue