I am trying to get this webpage to update asynchronously using a python flask server and MQTT values coming in from Arduino devices. All values are coming through and getting saved correctly however the values do not show up on the webpage.
I think it might have something to do with trying to write to the id tag using the $('#humidity').text
Here's the relevant HTML:
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
var socket = io.connect('http://' + document.domain + ':' + location.port);
socket.on('connect', function() {
socket.emit('my event', {data: 'I\'m connected!'});
});
socket.on('dht_temperature', function(msg) {
var nDate = new Date();
$('#readingsUpdated').text(nDate.getHours() + 'h:' + nDate.getMinutes() +
'm:' + nDate.getSeconds() + 's').html();
$('#temperature').text(msg.data).html();
});
socket.on('dht_humidity', function(msg) {
$('#humidity').text(msg.data).html();
});
});
</script>
<body>
<h1>RPi Web Server - ESP8266 MQTT</h1>
{% for pin in pins %}
<h2>{{ pins[pin].name }}
{% if pins[pin].state == 'True' %}
is currently <strong>on</strong></h2><div class="row"><div class="col-md-2">
Turn off</div></div>
{% else %}
is currently <strong>off</strong></h2><div class="row"><div class="col-md-2">
Turn on</div></div>
{% endif %}
{% endfor %}
<h3>DHT Readings (updated <span id="readingsUpdated"></span>)</h3>
<h3>Temperature: <span id="temperature"></span>ÂșC</h3>
<h3>Humidity: <span id="humidity"></span>%</h3>
</body>
</html>
And here is the Python Script
import paho.mqtt.client as mqtt
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
print("Connected with result code "+str(rc))
# Subscribing in on_connect() means that if we lose the connection and
# reconnect then subscriptions will be renewed.
client.subscribe("/esp8266/temperature")
client.subscribe("/esp8266/humidity")
# The callback for when a PUBLISH message is received from the ESP8266.
def on_message(client, userdata, message):
#socketio.emit('my variable')
print("Received message '" + str(message.payload) + "' on topic '"
+ message.topic + "' with QoS " + str(message.qos))
if message.topic == "/esp8266/temperature":
print("temperature update")
socketio.emit('dht_temperature', {'data': message.payload})
if message.topic == "/esp8266/humidity":
print("humidity update")
socketio.emit('dht_humidity', {'data': message.payload})
mqttc=mqtt.Client()
mqttc.on_connect = on_connect
mqttc.on_message = on_message
mqttc.connect("localhost",1883,60)
mqttc.loop_start()
# Create a dictionary called pins to store the pin number, name, and pin state:
pins = {
4 : {'name' : 'GPIO 4', 'board' : 'esp8266', 'topic' : 'esp8266/4', 'state' : 'False'},
5 : {'name' : 'GPIO 5', 'board' : 'esp8266', 'topic' : 'esp8266/5', 'state' : 'False'}
}
# Put the pin dictionary into the template data dictionary:
templateData = {
'pins' : pins
}
#app.route("/")
def main():
# Pass the template data into the template main.html and return it to the user
return render_template('main.html', async_mode=socketio.async_mode, **templateData)
# The function below is executed when someone requests a URL with the pin number and action in it:
#app.route("/<board>/<changePin>/<action>")
def action(board, changePin, action):
# Convert the pin from the URL into an integer:
changePin = int(changePin)
# Get the device name for the pin being changed:
devicePin = pins[changePin]['name']
# If the action part of the URL is "1" execute the code indented below:
if action == "1" and board == 'esp8266':
mqttc.publish(pins[changePin]['topic'],"1")
pins[changePin]['state'] = 'True'
if action == "0" and board == 'esp8266':
mqttc.publish(pins[changePin]['topic'],"0")
pins[changePin]['state'] = 'False'
# Along with the pin dictionary, put the message into the template data dictionary:
templateData = {
'pins' : pins
}
return render_template('main.html', **templateData)
#socketio.on('my event')
def handle_my_custom_event(json):
print('received json data here: ' + str(json))
if __name__ == "__main__":
socketio.run(app, host='0.0.0.0', port=8181, debug=True)
Related
I'm testing websockets to work with cookies and trying to get them in fast api. I manually installed them in chrome but I get an empty dictionary inside the application. I used the fast api documentation templates and slightly redesigned it
My html
html = """
<!DOCTYPE html>
<html>
<head>
<title>Chat</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<form action="" onsubmit="sendMessage(event)">
<label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
<label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
<button onclick="connect(event)">Connect</button>
<hr>
<label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
<button>Send</button>
</form>
<ul id='messages'>
</ul>
<script>
var ws = null;
function connect(event) {
var itemId = "1000"
var token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MjlmYmU3ZGNjMzQxMGFiNWE2MmZkOWYiLCJ1c2VyIjoiQmk4eVVhOG5TS1dFRm8weEJjYWkwRUtDU2E3TyJ9.7hE3qIcFoLLoDqQSliaHXhSPs4FW75fNafumPdKHPmI"
ws = new WebSocket("ws://localhost:8000/ws/" + itemId + "/?token=" + token);
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
event.preventDefault()
}
function sendMessage(event) {
var input = document.getElementById("messageText")
ws.send(input.value)
input.value = ''
event.preventDefault()
}
</script>
</body>
</html>
"""
My websocket
class ConnectionManager:
def __init__(self):
self.active_connections: list = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
#app.websocket("/ws/{project_id}/")
async def test_websocket(websocket: WebSocket,
project_id: int,
token: str = Depends(authorization.get_user_websocket_token)
):
print(websocket.cookies)
await manager.connect(websocket)
try:
while True:
project = await db["storages"].find_one({"project_id": project_id})
if token in project["users"]:
print(True)
data = await websocket.receive_text()
await manager.send_personal_message(f"You wrote: {data}", websocket)
await manager.broadcast(f"Client says: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"Client left the chat")
print(websocket.cookies) returns an empty dictionary {}
Cookies are domain-defined, so you should point at localhost:8000 and there define that Cookie, not 127.0.0.1:8000.
Maybe it's easier to check them in Postman.
With such crafted request:
They will be available at websocket.cookies for sure.
websocket.cookies under the hood checks Cookie header:
#property
def cookies(self) -> typing.Dict[str, str]:
if not hasattr(self, "_cookies"):
cookies: typing.Dict[str, str] = {}
cookie_header = self.headers.get("cookie")
if cookie_header:
cookies = cookie_parser(cookie_header)
self._cookies = cookies
return self._cookies
this code is supposed to work, but it does not work
As soon as a client connects, the session 'keyo' is assigned to the value 'example'
I tried to make an equivalent of socket.myvariable with session['myvariable']
#app.route("/a")
def helljo():
return render_template('index.html')
#socketio.on('connect')
def handleMessagae():
session['keyo'] = request.args.get('session') # /a?session=example
emit('connected', session.get('keyo'))
#socketio.on('message')
def handleMessage(message):
emit('message', {'pseudo':session.get('keyo'),'message':message} , broadcast=True, include_self=False)
The correct way to do what you want is:
#app.route("/a")
def helljo():
session['keyo'] = request.args.get('session') # /a?session=example
return render_template('index.html')
#socketio.on('connect')
def handleMessagae():
emit('connected', session.get('keyo'))
#socketio.on('message')
def handleMessage(message):
emit('message', {'pseudo':session.get('keyo'),'message':message} , broadcast=True, include_self=False)
The problem is that you are mixing up the HTTP request with the Socket.IO requests. If you invoke the /a endpoint with some query string args, you can only access those arguments in the handler for that endpoint. But saving them in the session makes them accessible later by the Socket.IO event handlers.
#app.route("/a", methods=['GET'])
def helljo():
session['keyo'] = request.args.get('session')
socketio.emit('EVENT', {'var': session.get('keyo')})
return render_template('index.html')
#socketio.on('connect')
def handleMessagae():
emit('connected', session.get('keyo'))
#socketio.on('message')
def handleMessage(message):
emit('message', {'pseudo':session.get('keyo'),'message':message} , broadcast=True, include_self=False)
The problem here is, that you're trying to get request arguments within a socketio event, this isn't going to work as request arguments are only accessible inside of an #app.route() function.
the fourth line of this code sends to all clients except to sender of the request. I would like to send it only to sender
#app.route("/a", methods=['GET'])
def helljo():
session['keyo'] = request.args.get('session')
socketio.emit('alert_sender', {'resp': session.get('keyo')})
return render_template('index.html')
#socketio.on('connect')
def handleMessagae():
emit('connected', 'hi')
#socketio.on('message')
def handleMessage(message):
emit('message', {'pseudo':session.get('keyo'),'message':message} , broadcast=True, include_self=False)
on index.html (when I go on /a?session=example)
...
socket.on('alert_sender', function(data) {
alert(data.resp);
// others receive alert : example sender receive : nothing
});
...
The Alternative Solution :
#app.route("/a", methods=['GET'])
def index():
resp = request.args.get('session')
return render_template( 'index.html' , resp=resp )
#socketio.on('My_Event')
def message(msg):
print(msg)
on index.html
<script>
// ...
{% if resp %}
variable = '{{ resp }}';
{% else %}
variable = 'null';
{% endif %}
socket.emit( 'My_Event' , variable );
// ...
</script>
I'm creating a web application using Flask and the Google Maps Directions API, where I ask the user to enter a starting point, waypoints, and a destination, and pass this data to the Google API to render a map illustrating the user's route. No matter what these data points are, however, the API keeps interpreting all 3 data points as a single location, and renders the map accordingly. The rendered map is
here, where you can also see what this exact wrong address is.
The relevant source files are as follows:
run.py (where I call my template to render the Google Map in the "/finalize_new_trip" route):
from os.path import abspath, dirname, join
from flask import flash, Flask, Markup, redirect, render_template, url_for, request
from flask.ext.sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm
from wtforms import StringField
from wtforms.validators import DataRequired
import os
import googlemaps
from datetime import datetime
import json
_cwd = dirname(abspath(__file__))
SECRET_KEY = 'flask-session-insecure-secret-key'
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + join(_cwd, 'TripLogger.db')
SQLALCHEMY_ECHO = True
WTF_CSRF_SECRET_KEY = 'this-should-be-more-random' #TODO: RANDOMIZE THIS KEY
app = Flask(__name__)
app.config.from_object(__name__)
db = SQLAlchemy(app)
class Trip(db.Model):
__tablename__ = 'trips'
id = db.Column(db.Integer, primary_key=True)
trip_name = db.Column(db.String, unique=True)
origin = db.Column(db.String)
destination = db.Column(db.String)
waypoints = db.Column(db.String) #SEE IF ANOTHER DATA TYPE IS BETTER- IDEALLY WANT TO STORE LIST OF STRINGS
def __init__(self, trip_name, origin, destination, waypoints):
self.trip_name = trip_name
self.origin = origin
self.destination = destination
self.waypoints = waypoints
def __repr__(self):
return '<Trip %r(name = %r, origin = %r, destination = %r)>' % (self.id, self.trip_name, self.origin, self.destination)
def __str__(self):
return self.trip_name
#CONSIDER CUSTOM VALIDATORS TO ENSURE THAT TRIP_NAME IS UNIQUE FOR A SPECIFIC USER/ACCOUNT
class TripForm(FlaskForm):
trip_name = StringField('Trip Name', validators=[DataRequired()])
origin = StringField('Origin', validators=[DataRequired()])
destination = StringField('Destination', validators=[DataRequired()])
waypoints = StringField('Waypoint')
#app.route("/", methods = ["GET", "POST"])
def index():
trip_form = TripForm(request.form)
if trip_form.validate_on_submit():
flash("Submitted new trip form, now redirecting to /finalize_new_trip to render map...")
return redirect(url_for("complete_trip", trip_name = trip_form.trip_name.data, origin = trip_form.origin.data, destination = trip_form.destination.data, waypoints = trip_form.waypoints.data))
return render_template("index.html", trip_form = trip_form)
# Route to display trip on map (in future, to modify trip details, e.g. waypoints)
#app.route("/finalize_new_trip", methods=["POST"])
def complete_trip():
return render_template("finish_new_trip.html", trip_name = request.args.get("trip_name"), origin = request.args.get("origin"), destination = request.args.get("destination"), waypoints = request.args.get("waypoints"))
# Store new trip data in database
#app.route("/store_trip", methods=["POST"])
def store_trip():
trip_data = request.form("trip_data")
return json.loads(trip_data)[0]
def query_to_list(query, include_field_names=True):
"""Turns a SQLAlchemy query into a list of data values."""
column_names = []
for i, obj in enumerate(query.all()):
if i == 0:
column_names = [c.name for c in obj.__table__.columns]
if include_field_names:
yield column_names
yield obj_to_list(obj, column_names)
def obj_to_list(sa_obj, field_order):
"""Takes a SQLAlchemy object - returns a list of all its data"""
return [getattr(sa_obj, field_name, None) for field_name in field_order]
if __name__ == "__main__":
app.debug = True
db.create_all()
app.run()
finish_new_trip.html (the template using the Google Maps API to render the map):
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<script async defer
src="https://maps.googleapis.com/maps/api/js?v=3.exp&key=<MY_API_KEY>&callback=getOriginCoordinates" type="text/javascript">
</script>
<title>Finish New Trip</title>
</head>
<body>
<div id="trip_name">
<h1> {{ trip_name }} </h1>
</div>
<div id="map"></div>
<div id="right-panel">
<div>
<form>
<input type="submit" id="finish-new-trip-button" value="Add My Trip!">
</form>
</div>
<div id="directions-panel"></div>
</div>
<script>
//Wrapper function to AJAX POST request that passes data back to server.
/*function passTripData() {
document.getElementById("finish-new-trip-button").onclick = function() {
// Retrieve data from displayed map (after user makes changes) after testing
$.post( "{{ url_for('store_trip') }}", {
trip_data: {
"trip_name": "{{ trip_name }}",
"origin": "{{ origin }}",
"destination": "{{ destination }}",
"waypoints": [
"{{ waypoints }}"
]
}
});
}
}*/
//Use Geocoding API to get coordinates for origin, destination, and waypoints.
function getOriginCoordinates() {
//passTripData(); // function that sets onClick event for "Submit" button
var geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': "{{ origin }}"}, function(results, status) {
if (status == 'OK') {
originCoordinates = results[0].geometry.location;
console.log("Origin coords: " + "lat = " + originCoordinates.lat() + ", lng = " + originCoordinates.lng());
getDestCoordinates(originCoordinates);
} else {
alert('Geocode of origin was not successful for the following reason: ' + status);
}
});
}
function getDestCoordinates(originCoords) {
var geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': "{{ destination }}"}, function(results, status) {
if (status == 'OK') {
destCoordinates = results[0].geometry.location;
console.log("Dest coords: " + "lat = " + destCoordinates.lat() + ", lng = " + destCoordinates.lng());
getWaypointCoordinates(originCoords, destCoordinates);
} else {
alert('Geocode of destination was not successful for the following reason: ' + status);
}
});
}
function getWaypointCoordinates(originCoords, destCoords) {
var geocoder = new google.maps.Geocoder();
geocoder.geocode( { 'address': "{{ waypoints }}"}, function(results, status) {
if (status == 'OK') {
waypointCoordinates = results[0].geometry.location;
console.log("Waypoint coords: " + "lat = " + waypointCoordinates.lat() + ", lng = " + waypointCoordinates.lng());
initMap(originCoords, destCoords, waypointCoordinates);
} else {
alert('Geocode of waypoints was not successful for the following reason: ' + status);
}
});
}
function initMap(originCoords, destCoords, waypointCoords) {
var directionsService = new google.maps.DirectionsService;
var directionsDisplay = new google.maps.DirectionsRenderer;
//Center map between origin and destination.
//TEST LATLNG OBJECTS FIRST
console.log("Origin: lat=" + originCoords.lat() + ", lng=" + originCoords.lng());
console.log("Destination: lat=" + destCoords.lat() + ", lng=" + destCoords.lng());
var mapLatitudeCenter = (originCoords.lat() + destCoords.lat())/2;
var mapLongitudeCenter = (originCoords.lng() + destCoords.lng())/2;
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 6,
center: {lat: mapLatitudeCenter, lng: mapLongitudeCenter}
});
directionsDisplay.setMap(map);
calculateAndDisplayRoute(directionsService, directionsDisplay, originCoords, destCoords, waypointCoords);
}
function calculateAndDisplayRoute(directionsService, directionsDisplay, originCoords, destCoords, waypointCoords) {
var waypts = [];
waypts.push({
location: waypointCoords,
stopover: true
});
directionsService.route({
origin: originCoords,
destination: destCoords,
waypoints: waypts,
optimizeWaypoints: true,
travelMode: 'DRIVING'
}, function(response, status) {
if (status === 'OK') {
directionsDisplay.setDirections(response);
var route = response.routes[0];
var summaryPanel = document.getElementById('directions-panel');
summaryPanel.innerHTML = '';
// For each route, display summary information.
for (var i = 0; i < route.legs.length; i++) {
var routeSegment = i + 1;
summaryPanel.innerHTML += '<b>Route Segment: ' + routeSegment +
'</b><br>';
summaryPanel.innerHTML += route.legs[i].start_address + ' to ';
summaryPanel.innerHTML += route.legs[i].end_address + '<br>';
summaryPanel.innerHTML += route.legs[i].distance.text + '<br><br>';
}
} else {
window.alert('Directions request failed due to ' + status);
}
});
}
</script>
</body>
</html>
When I test my app locally, the console displays the latitude/longitude coordinates of each data point (origin, destination, and waypoints), and they all correspond to this incorrect address in Italy. I also previously received a "404" error message, but I am not receiving that now.
I attempted to regenerate my API key and use this new key, but this didn't fix the problem. How can I go about resolving this issue?
When I change complete_trip(): to
#app.route("/finalize_new_trip", methods=["GET"])
def complete_trip():
return render_template("finish_new_trip.html", trip_name ='new trip', origin='new york, ny', destination='los angeles, california', waypoints='topeka, kansas')
and request the page, the results are perfect (a trip from NY to LA that goes through Topeka). You've got a lot of commented out code everywhere. I have a feeling that somewhere you're not getting the data you expect (for example, we can't see the form HTML used to post this data).
I am using the following code to implement the backend for a javascript EventSource
from flask import Flask, Response
from time import sleep
import time
class ServerSentEvent(object):
def __init__(self, data):
self.data = data
self.event = None
self.id = None
self.desc_map = {
self.data : "data",
self.event : "event",
self.id : "id",
self.retry: 500
}
def encode(self):
if not self.data:
return ""
lines = ["%s: %s" % (v, k)
for k, v in self.desc_map.items() if k]
return "%s\n\n" % "\n".join(lines)
def stream():
while True:
ev = ServerSentEvent('hi ' + str(int(round(time.time()))))
yield ev
sleep(0.1)
app = Flask(__name__)
#app.route("/events")
def streamSessionEvents():
return Response(stream(), mimetype="text/event-stream")
#app.route("/")
def index():
template = """
<!doctype html>
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script>
var source = new EventSource('/events')
source.onmessage = function (event) {
$('#log').append(event.data + '</br>')
}
</script>
</head>
<body>
<div id="log"></div>
</body>
</html>
"""
return(template)
app.run(threaded=True)
However the EventSource keeps reconnecting every 3 seconds (which is the default) because the connection is closed by the server after every event. How can I establish a continuous connection?
I'm trying to handle disconnect / connect states using Presence in the Channel API.
Here are some of my code.
app.yaml
handlers:
- url: /(.*\.(gif|png|jpg|ico|js|css))
static_files: \1
upload: (.*\.(gif|png|jpg|ico|js|css))
- url: .*
script: main.py
inbound_services:
- channel_presence
main.py
class onConnect(webapp.RequestHandler):
def post(self):
for user in users:
users = User.all().fetch(1000)
client = client_id = self.request.get('from')
channel.send_message(user.channel,' connected');
class onDisconnect(webapp.RequestHandler):
def post(self):
Mainpage()
for user in users:
users = User.all().fetch(1000)
client = client_id = self.request.get('from')
channel.send_message(user.channel, ' disconnected');
application = webapp.WSGIApplication(
[('/', MainPage),
('/_ah/channel/connected/',onConnect),
('/_ah/channel/disconnected/',onDisconnect),
('/chat',handleChat)],
debug=True)
Javascript
<script>
openChannel = function(){
var token = '{{ token }}';
var channel = new goog.appengine.Channel(token);
var handler = {
'onopen': onOpened,
'onmessage': onMessage,
'onerror': function() {},
'onclose': function() {}
};
var socket = channel.open(handler);
socket.onopen = onOpened;
socket.onmessage = onMessage;
var chat = document.getElementById('chatinput');
chat.onkeyup = function(e){
if(e.keyCode == 13){
sendChat(this.value);
this.value = '';
}
}
}
sendMessage = function(path, opt_param) {
if (opt_param) {
path += '?' + opt_param;
}
var xhr = new XMLHttpRequest();
xhr.open('POST', path, true);
xhr.send();
};
onOpened = function(){
console.log('Channel Opened');
var chatlog = document.getElementById('chatlog');
var msg = document.createElement('div');
msg.innerHTML = 'Channel Opened';
chatlog.appendChild(msg);
sendMessage('/chat','m='+'A User Joined.');
}
onMessage = function(m){
console.log('Message Recieved');
var chatlog = document.getElementById('chatlog');
var msg = document.createElement('div');
var d = new Date();
msg.innerHTML = d.toLocaleTimeString() + ': ' + m.data;
chatlog.appendChild(msg);
}
sendChat = function(msg){
console.log(msg);
sendMessage('/chat','m='+msg);
}
openChannel();
</script>
Using this code, connnect and disconnect is not triggering when a user closes their browser or whatever.
Are there anything wrong with this code?
Yes, route list is wrong. Put ('/', MainPage) in the end of the routes list. From webapp2 URI routing guide:
When the application receives a request, it tries to match each one in order until one matches, and then call the corresponding handler.