Skip to content

Commit

Permalink
initial repository added
Browse files Browse the repository at this point in the history
  • Loading branch information
arlinjv committed Jul 31, 2016
0 parents commit 1c97c38
Show file tree
Hide file tree
Showing 31 changed files with 900 additions and 0 deletions.
258 changes: 258 additions & 0 deletions flask-socketio/alarm_monitor/alarm_monitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
#!/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 time
from threading import Thread
from flask import Flask, render_template, session, request, jsonify
from flask_socketio import SocketIO, emit, join_room, leave_room, \
close_room, rooms, disconnect

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!' #Seems to be needed for using session: http://flask.pocoo.org/docs/0.10/api/#sessions
socketio = SocketIO(app, async_mode=async_mode)
thread = None

#Alarm Monitor:
# - Basic concept: serve webpage that shows realtime status of remote alarm nodes
# - Arduino:
# - Send alarm messages upon activation
# - Respond to pings or keepalive messages
# - send a 'good morning' message when booting
# - Flask (flask-socketio):
# - Listen for incoming alarm messages from Arduino
# - Send message to client when alarm received
# - Send keepalive messages and send result to client (maybe do processing here and send desired output only)
#
# - Client (javascript / socketio):
# - display status of alarm(s)
# - display time since last keepalive (maybe green circle, then yellow and red depending on time since last)
# - implement a 'ping' button to check on status of Arduino
# - have a heartbeat symbol that responds to heartbeats from Arduino - size or color of logo would represent freshness
# - how about a heart bracketed on left and right by audio symbols - number of arcs repressent freshness - 0 arcs represent overdue heartbeat

#Progress:
#1. Display incoming messages from Arduino (no socketio needed)
#2. constantly update page with latest values as they come in (socketio needed here)
# - keep straight flask route for incoming data ( @app.route("/alarm") )
# - in /alarm route use socketio.emit to send broadcast to all clients listening on alarms
#
#Next steps:
#1. improve reliability of Arduino server connection
# - after restarting server Arduino messages sometimes don't go through
# - need to flesh this out more. starting Arduino first then server works OK
# - also am able to restart server and reconnect sometimes
# ideas:
# - On Arduino side capture 'message received' portion (last line) of server response
# - consider issuing reset when connection fails (this isn't always necessary, though <-- gotta figure out when it is.)
# - add status handler on Arduino
# - send json list of sensors
# - may need to use json.dumps for formatting response for javascript use (http://stackoverflow.com/questions/7907596/json-dumps-vs-flask-jsonify)
#2. Develop server:
# - build a list of connected sensor nodes (dicts): device_id, sensor, pingback address (get from GET request?), status
# - each sensor node element contains a list of dicts of sensors
# - incoming messaged parsed for GET values and remote_addr
# - pack last_rx_data with new info
# - update nodes
# - find and update matching node
# - new node element if not already there
# - Use list of nodes to populate table (as in logger.py)
# - for each node have node name as header followed by table of sensors
# - maybe have each element of table have a custom id or just match sensor name in text (for finding by means of jquery selector)
# - update table elements as appropriate based on new data
# - refresh page when list of sensors or nodes changes ( location.reload(true) )
#


nodes = [] # node format {'device_ID':None, 'remote_addr':None, 'sensors':[]}
#sensor format: {'sensor_ID':None, 'sensor_type':None, 'sensor_value':None, 'units':None}
last_node = {}
last_rx_data = {'device_ID':None, 'sensor_ID':None, 'sensor_type':None, 'sensor_value':None, 'units':None,'remote_addr':None}

def updateNodes(data): # maybe this should just be part of alarm_message()
# assumes this format: {'device_ID':None, 'sensor_ID':None, 'sensor_type':None, 'sensor_value':None, 'units':None,'remote_addr':None}

#create a new sensor - if it already exists this will be used to overwrite (update) it
new_sensor = {'sensor_ID':data['sensor_ID'], 'sensor_type':data['sensor_type'],'sensor_value':data['sensor_value'], 'units':data['units']}

match = {}
reload_page = False
node_count = 0 # for debugging/error catching - there should only be one node per device_ID
sensor_count = 0

# look for matching node, then look for matching sensor
for node in nodes:
if data['device_ID'] == node['device_ID']: # --- found a matching node ---
print "Matching node found for device_ID - ", data['device_ID']
node_count += 1
sensors = node['sensors'] # get list of sensors for this node (replace the existing list with this list before finishing)

for index, sensor in enumerate(sensors):
if sensor['sensor_ID'] == data['sensor_ID']:# --- found a matching sensor ---
sensor_count += 1
sensors[index] = new_sensor # replace the old sensor entry with the new one
print "matching sensor found for sensor_ID - ", data['sensor_ID']
if sensor_count == 0:# --- matching node but no matching sensor ---
sensors.append(new_sensor)
print "no matching sensor found. appending new sensor: ", new_sensor
#reload_page = True #reload page to show new sensor

node['sensors'] = sensors # replace existing list with new (updated) list
print "updated node: ", node

if node_count > 1:
raise Exception('Too many node matches found for device')

if node_count == 0: # --- no matching node ---
# create new node with new sensor:
new_node = {'device_ID':data['device_ID'],'remote_addr':data['remote_addr'], 'sensors':[new_sensor]}
nodes.append(new_node)
print "no matches found. added new node: ", new_node
# reload_page = True

# find node with matching device_ID
# if no match
# - create new node
# - reload_page = True
# if match look for matching sensor
# - update matching sensor / check for matching remote addr
# - if no matching sensor
# - create
# - reload_page = True
# update

return reload_page


@app.route("/test")
def test():
# see :
# http://flask.pocoo.org/docs/0.10/reqcontext/
# http://flask.pocoo.org/docs/0.10/api/#incoming-request-data
# http://werkzeug.pocoo.org/docs/0.11/datastructures/#werkzeug.datastructures.MultiDict
if 'reload' in request.args.values()[0]: #for testing
print('issuing reload signal')
socketio.emit('reload_page')
print "query_string: ", request.query_string
print "args: ", request.args.items()
print "keys: ", request.args.keys()
print "values: ", request.args.listvalues() # there is also a values() method
print "args as a dict: ", request.args.to_dict()
print "remote address: ", request.remote_addr
#print "request.event", request.event.keys()
return jsonify({'ip': request.remote_addr, "args: ": request.args.items()}), 200

@app.route('/')
def index():
return render_template('index.html')

# To do: should change alarm to alarms. On GET provide, say, a list of current nodes. On POST do some sort of update of alarm
@app.route('/alarm')
def alarm_message():
print
print "----------- alarm message received -----------"
print "--- query_string: ", request.query_string
print "--- remote address: ", request.remote_addr
print "----------------------------------------------"

request_dict = request.args.to_dict()

#pack query string into last_rx_data:
last_rx_data = {'device_ID':None, 'sensor_ID':None, 'sensor_type':None, 'sensor_value':None, 'units':None,'remote_addr':None} # reset dict
last_rx_data['remote_addr'] = request.remote_addr # could use dict.update() but I think I want to catch it when message keys don't match
for k,v in request_dict.items():
last_rx_data[k] = v

reload_page = False

# if node update indicates new node or new sensor, toggle flag to indicate reload needed
reload_page = updateNodes(last_rx_data)

if reload_page == True:
# reload page to catch and display new sensors or nodes
socketio.emit('reload_page',{}) # shouldn't need payload but add ,{} if necessary

socketio.emit('alarm_received', 'motion sensed') #hope it's ok to send this right after reload

return 'message received'

@socketio.on('connection event')
def handleConnectionEvent(message):
print "connection event: ", message

@app.route("/debug")
def debug():
raise Exception('Nothing to see here. Move along') #used for debugging in browser - click on little console logo at the right
'''Then you can do cool stuff like:
>>> cur = g.db.execute('select device_ID, sensor_ID, sensor_type, sensor_value, units, timestamp from entries order by id desc')
>>> entry = cur.fetchone()
also: dump(g), dump(session), dir(), globals(), locals(), dir(g), dir(session),
'''
return

@socketio.on('message')
def handle_message(message):
print 'received message: ', message['data']


if __name__ == '__main__':
socketio.run(app, host='0.0.0.0',debug=True)


#Snippets:
"""
def background_thread():
# Example of how to send server generated events to clients.
# Should probably just drop this for now.
# might be interesting to implement some sort of heartbeat function on Arduino but that wouldn't require
# flask based thread.
# might be cool to add a ping button to webpage that would change state based on response from Arduino
count = 0
while True:
time.sleep(10)
count += 1
socketio.emit('my response',
{'data': 'Server generated event', 'count': count},
namespace='/test') # namespace should
def index():
global thread
if thread is None:
thread = Thread(target=background_thread)
thread.daemon = True
thread.start()
return render_template('index.html')
"""
55 changes: 55 additions & 0 deletions flask-socketio/alarm_monitor/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Alarm Monitor</title>
<style>
p { margin: 0; }
</style>
<script type="text/javascript" src="//code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){
namespace = ''; // change to an empty string to use the global namespace
// using alarms room but no specific namespace
msgCount = 0;

// the socket.io documentation recommends sending an explicit package upon connection
// this is specially important when using the global namespace
// See also: http://stackoverflow.com/questions/35384285/flask-socketio-server-using-polling-instead-of-websockets
var socket = io.connect('http://' + document.domain + ':' + location.port + namespace);

// event handler for server sent data
// the data is displayed in the "Received" section of the page
socket.on('alarm_received', function(msg) {
msgCount++;
var d = new Date();
var t = d.getTime();
var newLine = '<p> ' + '(' + msgCount + ')' + t + ': ' + msg + '</p>';

$('#log').prepend(newLine);

if ( msgCount > 10) {
$('#log p:last').remove();// remove last paragraph
}

//$('#log').append('<br>' + $('<div/>').text('(' + msgCount + ')' + t + ': ' + msg).html());
//$('#log').prepend('<p> ' + '(' + msgCount + ')' + t + ': ' + msg + '</p>') // consider moving this to a seperate frame later
});
socket.on('reload_page', function(){
location.reload(true);
});
socket.on('connect', function() {
socket.emit('connection event', {data: 'I\'m connected!'});
});
});
</script>
</head>
<body>
<h1>Flask-SocketIO Test</h1>
<h2>Received:</h2>
<div id="log"></div>
<br>
<a href="{{ url_for('debug') }}">debug flask</a>

</body>
</html>
Binary file added flask-socketio/flask-socketio.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions flask-socketio/github example/Flask-SocketIO
Submodule Flask-SocketIO added at 04bb4a
23 changes: 23 additions & 0 deletions flask-socketio/sliders/sliders.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from flask import Flask, render_template
from flask.ext.socketio import SocketIO, emit

app = Flask(__name__)
socketio = SocketIO(app)

values = {
'slider1': 25,
'slider2': 0,
}

@app.route('/')
def index():
return render_template('index.html', **values)

@socketio.on('value changed')
def value_changed(message):
values[message['who']] = message['data']
print "values: ", values
emit('update value', message, broadcast=True)

if __name__ == '__main__':
socketio.run(app, host='0.0.0.0', debug = True)
37 changes: 37 additions & 0 deletions flask-socketio/sliders/templates/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Synchronized Sliders</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(document).ready(function(){

var socket = io.connect();

socket.on('connect', function() {
socket.emit('connect', {data: 'I\'m connected!'});
});

$('input.sync').on('input', function(event) {
socket.emit('value changed', {who: $(this).attr('id'), data: $(this).val()});
return false;
});

socket.on('update value', function(msg) {
$('input#'+msg.who).val(msg.data)
});

});
</script>
</head>
<body>
<h1>Synchronized Sliders!</h1>

<input id="slider1" class="sync" type="range" min="0" max="50" value="{{slider1}}" /> <br>
<input id="slider2" class="sync" type="range" min="0" max="50" value="{{slider2}}" />

<input id="txt1" class="sync" type="text" />

</body>
</html>
Loading

0 comments on commit 1c97c38

Please sign in to comment.