Can't open websocket on mobile devices - python
I am attempting to set up a websocket with server running on my raspberry pi. The following code is slightly modified from an example I found here.
I have built a whole webpage around this example allowing me to control gpio and send messages to a serial device plugged into the pi. Both that site and this example work perfectly from my laptop (windows 10 using Chrome or Firefox).
However when I connect from my phone (Android 5.0.1 using Chrome for android). It appears to never open the socket. In the example code it just displays "messages go here.
My first thought was the chrome on android didn't support websockets but I was able to connect and echo messages on this site http://www.websocket.org/echo.html. So it appears the functionality is there.
What else would prevent the socket from opening ?
pysocket.py
import tornado.httpserver
import tornado.websocket
import tornado.ioloop
import tornado.web
class WSHandler(tornado.websocket.WebSocketHandler):
def check_origin(self, origin):
return True
def open(self):
print 'New connection was opened'
self.write_message("Welcome to my websocket!")
def on_message(self, message):
print 'Incoming message:', message
self.write_message("You said: " + message)
def on_close(self):
print 'Connection was closed...'
application = tornado.web.Application([
(r'/ws', WSHandler),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(8888)
tornado.ioloop.IOLoop.instance().start()
pysocket.php
<!doctype html>
<html>
<head>
<title>WebSockets with Python & Tornado</title>
<meta charset="utf-8" />
<style type="text/css">
body {
text-align: center;
min-width: 500px;
}
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script>
$(function(){
var ws;
var logger = function(msg){
var now = new Date();
var sec = now.getSeconds();
var min = now.getMinutes();
var hr = now.getHours();
$("#log").html($("#log").html() + "<br/>" + hr + ":" + min + ":" + sec + " ___ " + msg);
//$("#log").animate({ scrollTop: $('#log')[0].scrollHeight}, 100);
$('#log').scrollTop($('#log')[0].scrollHeight);
}
var sender = function() {
var msg = $("#msg").val();
if (msg.length > 0)
ws.send(msg);
$("#msg").val(msg);
}
ws = new WebSocket("ws://raspberrypi-mike:8888/ws");
ws.onmessage = function(evt) {
logger(evt.data);
};
ws.onclose = function(evt) {
$("#log").text("Connection was closed...");
$("#thebutton #msg").prop('disabled', true);
};
ws.onopen = function(evt) { $("#log").text("Opening socket..."); };
$("#msg").keypress(function(event) {
if (event.which == 13) {
sender();
}
});
$("#thebutton").click(function(){
sender();
});
});
</script>
</head>
<body>
<h1>WebSockets with Python & Tornado</h1>
<div id="log" style="overflow:scroll;width:500px; height:200px;background-color:#ffeeaa; margin:auto; text-align:left">Messages go here</div>
<div style="margin:10px">
<input type="text" id="msg" style="background:#fff;width:200px"/>
<input type="button" id="thebutton" value="Send" />
</div>
www.LowPowerLab.com
</body>
</html>
ADDED SOME EXTRA CODE
I would recommend using node.js for that:
var express = require("express");
var app = express();
var http = require("http").Server(app);
var path = require("path");
var io = require('socket.io')(http);
var SerialPort = require('serialport');
var gpio = require('rpio');
http.listen(3000);
var serialPort = new SerialPort.SerialPort("/dev/ttyAMA0", {
baudrate: 115200,
dataBits: 8,
parity: "none",
stopBits: 1,
flowControl: false
});
io.on('connection', function(socket){
console.log('Connected');
socket.on("WriteSerial:get",function(data){
var hex = new Buffer (data, "hex"); //be careful passing data
writeSerial(serialPort, hex);
io.emit("WriteSerial:response", "Data writen!");
});
socket.on("ReadGPIO:get",function(data){
var input = readPin(data.pin);
io.emit("ReadGPIO:response", input);
});
socket.on("WriteGPIO:get",function(data){
writePin(data.pin, data.time);
io.emit("WriteGPIO:response", "Set!");
});
socket.on("unWriteGPIO:get",function(data){
unwritePin(data);
io.emit("unWriteGPIO:response", "Set!");
});
}
app.use(express.static(path.join(__dirname, '/')));
app.get("/home",function(req,res,next){
res.sendFile(path.join(__dirname + "/index.html"));
});
function writeSerial (port, data) {
port.write(data, function(err) {
if (err) {
return console.log('Error on write: ', err.message);
} else {
console.log('Data written: ' + data);
}
});
}
function readPin(pin){
rpio.open(pin, rpio.INPUT);
var read = rpio.read(pin) ? 'high' : 'low';
return read;
}
function writePin(pin, timeInMs){
rpio.open(pin, rpio.OUTPUT, rpio.LOW);
rpio.write(pin, rpio.HIGH);
if (timeInMs > 0) {
setTimeout(function(){
rpio.write(pin, rpio.LOW);
}, timeInMs);
} //You can put 0 if You want it to be high until You shut it down
}
function unWritePin(pin){
if(readPin(pin) === 'high') {
rpio.write(pin, rpio.LOW);
} else {
console.log("Pin already low!");
}
}
Be sure You have instaled the node.js with right version. If not do this in terminal:
sudo apt-get remove nodered && sudo apt-get remove nodejs nodejs-legacy &&
curl -sL https://deb.nodesource.com/setup_4.x | sudo bash - && sudo
apt-get install -y nodejs
Make a folder 'server' in '/home/pi/', add server.js to it. Add code I provided to server.js. Open that folder with terminal:
cd /home/pi/server/
Afther that install all modules used on server:
sudo npm install express && sudo npm install http && sudo npm install path &&
sudo npm install socket.io && sudo npm install serialport --unsafe-perm &&
sudo npm install rpio --unsafe-perm
Now all we have to do is create client side part. In folder '/home/pi/server' create index.html file and add folder called 'js'. In folder 'js' add socket.io.js for client side which You can find in folder '/home/pi/server/node_modules/socket.io/node_modules/socket.io-client/'.
Include socket.io.js for client side into Your index.html like this:
<script type="text/javascript" src="js/socket.io.js" /></script>
Also add main.js file to 'js' folder where You will put Your javascript code in and include it to index.html:
<script type="text/javascript" src="js/main.js" /></script>
<script type="text/javascript" src="js/jquery.js" /></script>
I will not make any graphics for but some main.js code is here:
$(document).ready(function() {
var socket = io.connect('http://your_ip_address_rpi:3000');
$( "#myButton" ).click(function(){
io.emit("WriteSerial:get", $("#myTextHolder").val()); //"FAAF531C" this is string of hex, should be added some filter to pass error when char is not part of HEX!
});
$( "#myButton2" ).click(function(){
io.emit("WriteGPIO:get", {"pin" : $("#myPinHolder").val(), "time" : $("#myTimeHolder").val()})
}
To run server on RPI startup add 'sudo node /home/pi/server/server.js &' to '/etc/rc.local' before 'exit 0' with 'sudo nano' editor.
It will work really good on any device.
For the hostnames of the devices to work across network, a device has to advertise its own hostname or just responding to DNS queries for its own hostname.
Whatever implementation the Raspberry Pi is using, your laptop is supporting it, but your phone isn't.
So, to be able to connect, you need to change your hostname raspberrypi-mike to your Raspberry Pi's IP address, inside your JavaScript code.
Related
Client-side calls server to run python
I'm quite a beginner and I've been looking for an answer for days now.. I am building a webapp using nodejs. Here is the app.js I node to start (my server-side) http = require("http"), path = require("path"), url = require("url"), fs = require("fs"); function sendError(errCode, errString, response) { response.writeHead(errCode, {"Content-Type": "text/plain"}); response.write(errString + "\n"); response.end(); return; } function sendFile(err, file, response) { if(err) return sendError(500, err, response); response.writeHead(200); response.write(file, "binary"); response.end(); } function getFile(exists, response, localpath) { if(!exists) return sendError(404, '404 Not Found', response); fs.readFile(localpath, "binary", function(err, file){ sendFile(err, file, response);}); } function getFilename(request, response) { var urlpath = url.parse(request.url).pathname; // following domain or IP and port var localpath = path.join(process.cwd(), urlpath); // if we are at root fs.exists(localpath, function(result) { getFile(result, response, localpath)}); } var server = http.createServer(getFilename); server.listen(1000); console.log("Server available..."); I can therefore access my app using localhost:1000/index.html My index.html (client-side) is a html page containing buttons i want to use to start a python script. <!DOCTYPE html> <html lang="fr"> <head> <meta charset="utf-8"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <title>Lights</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> </head> <body class="text-white bg-dark"> <center> <input type="submit" value="OFF" name="OFF" onclick="turnOff()"> <input type="submit" value="ON" name="ON" onclick="turnOn()"> </center> <div id="texttest"> </div> <div id="fil"> </div> <script src="funcs.js"></script> </body> </html> from there you can see i use a script to run the "onclick()" this script is calling a python run.. But obviously I can't do that 'cause I am trying to run python client side.. ERROR "require not definded" my funcs.js const { exec } = require('child_process'); function turnOff() { console.log("enter Off"); exec('python3 turnOff.py', (err, stdout, stderr) => { if (err) { document.getElementById("fil").innerHTML = "error..."+err; } else { document.getElementById("fil").innerHTML = "running python..."; } }); document.getElementById("texttest").innerHTML = "trying off sucess :<br> " ; } function turnOn() { console.log("enter On"); exec('python3 turnOn.py', (err, stdout, stderr) => { if (err) { document.getElementById("fil").innerHTML = "error..."+err; } else { document.getElementById("fil").innerHTML = "running python..."; } }); document.getElementById("texttest").innerHTML = "trying on sucess :<br> " ; } I should be using AJAX request, but i can't seem to find how to run python on server side using it.. because the ajax request is sent from html inside nodejs.. What can I do ? Thanks for your help
You're right about your errors require is defined on server side only. funcs.js is executed on client side, so you can't use require. And you'll need to make an AJAX call in order to reach your goal. // server.js const { exec } = require('child_process'); const http = require('http'); const server = http.createServer((req, res) => { /* When a request will call the http://localhost:5000/run url it will execute this code block */ if (req.url === '/run') { exec('python ...', () => { // ... res.write('Python script executed'); res.end(); }); } }); server.listen(5000); // funcs.js function turnOff() { console.log("enter Off"); // Turn off will send a request on the http://localhost:5000/run endpoint fetch("/run", { method: "POST" }) .then(() => { document.getElementById("fil").innerHTML = "running python..."; }) .catch(() => { document.getElementById("fil").innerHTML = "error..."+err; }) }
Render html table from csv using node.js, express and python
I have a simple csv file and want to render a html table from it using express and ejs. csv: ,Unnamed: 0,sign_type,r1,g1,b1,r2,g2,b2,r3,g3,b3,r4,g4,b4,r5,g5,b5,r6,g6,b6,r7,g7,b7,r8,g8,b8,r9,g9,b9,r10,g10,b10,r11,g11,b11,r12,g12,b12,r13,g13,b13,r14,g14,b14,r15,g15,b15,r16,g16,b16 0,1,pedestrian,155,228,251,135,188,101,156,227,245,145,211,228,166,233,245,212,254,52,212,254,11,188,229,117,170,216,120,211,254,3,212,254,19,172,235,244,172,235,244,172,228,235,177,235,244,22,52,53 1,2,pedestrian,142,217,242,166,204,44,142,217,242,147,219,242,164,228,229,84,116,17,217,254,26,155,203,128,213,253,51,217,255,21,217,255,21,158,225,237,164,227,237,182,228,143,171,228,196,164,227,237 2,3,pedestrian,57,54,50,187,201,68,51,51,45,59,62,65,156,171,50,254,255,36,211,226,70,78,73,64,220,234,59,254,255,51,253,255,44,66,68,68,69,65,59,76,84,22,82,93,17,58,60,60 3,4,pedestrian,22,35,41,171,178,26,19,27,29,19,27,29,42,37,3,217,228,19,221,235,20,181,183,73,237,234,44,251,254,2,235,243,12,19,27,29,20,29,34,64,61,4,211,222,78,19,27,29 4,5,pedestrian,169,179,170,231,254,27,97,107,99,123,147,152,221,236,117,205,225,80,235,254,60,90,110,9,216,236,66,229,255,12,235,254,60,163,168,152,124,117,91,188,205,78,125,147,20,160,183,187 my .html file is as follows: <!DOCTYPE html> <html lang="en" dir="ltr"> <head> <meta charset="utf-8"> <title>Table</title> </head> <body> <%= table %> </body> </html> my app.js is: const express = require('express'); const app = express(); const bodyParser = require('body-parser'); let {PythonShell} = require('python-shell'); app.set('view engine', 'ejs'); app.get('/', function(req, res){ var pyshell = new PythonShell('excel_to_html.py'); pyshell.on('message', function (message) { res.render('list', {table: message}) // ###### console.log(message); }); }); var port = 3000; app.listen(port, function(){ console.log('Server running on port ' + port) }); and the python file which converts the .csv to html is: import pandas as pd def convert(): csv_file = pd.read_csv('test.csv') html = csv_file.to_html() return html if __name__ == '__main__': var = convert() print(var) So summarizing: 1) I have a csv file and using a .py file and pandas to convert the csv into html. 2) I'm calling the python script inside the app.js file and getting the returned html. 3) I want to render that html table in my index.html using ejs. If I replace the res.render('list', {table: message}) with the commented part console.log(message); I'm able to see the html table in the terminal log. but when I try to render it using res.render('list', {table: message}) i get a not infinite looping error like this: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client at ServerResponse.setHeader (_http_outgoing.js:485:11) at ServerResponse.header (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\response.js:771:10) at ServerResponse.send (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\response.js:170:12) at done (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\response.js:1008:10) at tryHandleCache (E:\Documents\CODE\WebDev\todolist\v1\node_modules\ejs\lib\ejs.js:260:5) at View.exports.renderFile [as engine] (E:\Documents\CODE\WebDev\todolist\v1\node_modules\ejs\lib\ejs.js:459:10) at View.render (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\view.js:135:8) at tryRender (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\application.js:640:10) at Function.render (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\application.js:592:3) at ServerResponse.render (E:\Documents\CODE\WebDev\todolist\v1\node_modules\express\lib\response.js:1012:7) And I get only this in the browser: <table border="1" class="dataframe"> how should I render this table? Is it easily possible? Thank you!
Basically Express's res.render ends the response; which means we can only use it once, and it seems like the listener pyshell.on('message'... is being triggered more than once. You will need to prepare the table then render it. Example: const express = require('express'); const app = express(); const { PythonShell } = require('python-shell'); app.set('view engine', 'ejs'); app.get('/', function(req, res){ prepareHtmlTable(function(err, html) { if (err) return res.sendStatus(500); res.render('list', { table: html }); }) }); function prepareHtmlTable(callback) { const pyshell = new PythonShell('excel_to_html.py'); pyshell.on('message', function (message) { // prepare html table here let html = message; pyshell.end(function (err,code,signal) { if (err) return callback(err); callback(null, html); }); }); } const port = 3000; app.listen(port, function(){ console.log('Server running on port ' + port) });
How to use Gulp Browsersync with Django?
First time using Gulp and I'm following a couple of tutorials that don't seem to be working quite right for me. I have a real basic project and I just want to learn how to use Gulp for standard things like JavaScript/CSS minification, image reduction, and browser sync. When I run my watch task with Browsersync, it goes to the right URL of localhost:8000, but it shows Cannot GET / instead of rendering my page. How do I fix this so I can use Browsersync with Django? File directory: gulpfile.js: var gulp = require('gulp'); var sass = require('gulp-sass'); var browserSync = require('browser-sync').create(); gulp.task('sass', function() { return gulp.src('polls/static/polls/scss/**/*.scss') .pipe(sass()) .pipe(gulp.dest('polls/static/polls/css')) .pipe(browserSync.reload({ stream: true })) }); gulp.task('browserSync', function() { browserSync.init({ server: "mysite", port: 8000 }); }); gulp.task('watch', ['browserSync', 'sass'], function() { gulp.watch('polls/static/polls/scss/**/*.scss', ['sass']); })
Just had to make another task called runserver which runs in the cmd python manage.py runserver. Put the task as one of the dependencies for Browsersync, set the proxy and port, and I was set to go. var exec = require('child_process').exec didn't require any extra npm install. I think it's automatically built in. var gulp = require('gulp'); var sass = require('gulp-sass'); var browserSync = require('browser-sync').create(); var exec = require('child_process').exec; gulp.task('sass', function() { return gulp.src('polls/static/polls/scss/**/*.scss') .pipe(sass()) .pipe(gulp.dest('polls/static/polls/css')) .pipe(browserSync.reload({ stream: true })) }); gulp.task('runserver', function() { var proc = exec('python manage.py runserver') }) gulp.task('browserSync', ['runserver'], function() { browserSync.init({ notify: false, port: 8000, proxy: 'localhost:8000' }) }); gulp.task('watch', gulp.series('browserSync', 'sass'), function() { gulp.watch('polls/static/polls/scss/**/*.scss', ['sass']); gulp.watch('polls/static/polls/scripts/**/*.js', browserSync.reload); gulp.watch('polls/templates/**/*.html', browserSync.reload); })
Using Gulp 4, here is a copy of my code: const gulp = require('gulp'); const browserSync = require('browser-sync').create(); const exec = require('child_process').exec; gulp.task('runserver', function() { var proc = exec('python manage.py runserver'); return new Promise(function(resolve, reject) { console.log("HTTP Server Started"); resolve(); }); }) gulp.task('browserSync', gulp.series('runserver'), function() { browserSync.init({ notify: false, port: 8000, proxy: 'localhost:8000' }); }); gulp.task('watch', gulp.series('browserSync'), function() { gulp.watch('polls/static/polls/scripts/**/*.js', browserSync.reload); gulp.watch('polls/templates/**/*.html', browserSync.reload); })
gulp.task('browserSync', ['runserver'], function() { browserSync.init({ notify: false, proxy: 'localhost:8000' }) });
HTML Page that talk with a Python Server
I have a web server that manages the GPIO Pin of the Raspberry Pi but when I Start the server I have to click two times on the div to start the led.. What could be the problem? This is the code of the html page that I have write to manage the led <script> function onoff(led) { var xmlhttp; var v = document.getElementById("input_" + led).value; if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } else { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } xmlhttp.onreadystatechange=function() { if (xmlhttp.readyState==4 && xmlhttp.status==200) { if (v == 'on'){ document.getElementById("input_" + led).value = 'off'; document.getElementById("par_lamp").innerHTML = "<img class='lampadina' src='http://192.168.1.91:8080/lampadina_on.png' alt='lampadina' height='198' width='120'>"; } else{ document.getElementById("input_" + led).value = 'on'; document.getElementById("par_lamp").innerHTML = "<img class='lampadina' src='http://192.168.1.91:8080/lampadina_off.png' alt='lampadina' height='198' width='120'>"; } } } xmlhttp.open("GET","http://192.168.1.91:8080/remote.html?led=" + led + "&action=" + v,true); xmlhttp.send(); } </head> <body> <center> <img src="http://192.168.1.91:8080/logo.png" alt="logo_sito" width:"198" height:"120"> </center> <p align="center" id="par_lamp"> <img class="lampadina" src="http://192.168.1.91:8080/lampadina_off.png" alt="lampadina" height="198" width="120"> </p> <div id="input_11" class="button" onclick="onoff(11)" value="off"> </div>
To debug such things you should take a look at the development tools in your browser. All browsers let you step through your code to see exactly what happens. In this case your flow is like this: <div id="input_11" class="button" onclick="onoff(11)" value="off"> Div starts with value 'off'. If you click the code does: var v = document.getElementById("input_" + led).value; xmlhttp.open("GET","http://192.168.1.91:8080/remote.html?led=" + led + "&action=" + v,true); Which then sends the initial value of 'value' that is 'off' to your server. Only later it toggles it to 'on' so for the next click it actually will send on. So you either need to toggle the current status before sending the command or you need to set the initial state to 'on' so the first click sends the right command. The code in xmlhttp.onreadystatechange=function() {... will only run after the xmlhttp.send() because it's an event handler function bound to xmlhttp
Communicating Raspberry Pi with cloudfoundry
I have written a Node.js socket.io routine which will be called by a python socket io routine from my raspberry pi. It will communicate both ways. At the moment when I run these two routines on localhost it works fine. However when I deploy the server application to cloudfoundry and change the SocketIO connection link to cloudfoundry it does not work. Below is the client python from socketIO_client import SocketIO def on_updatepi_response(*args): print 'updatepi' def on_receivepi_response(*args): print 'receiveepi' with SocketIO('raspinode-server.cloudfoundry.com', 8080) as socketIO: socketIO.on('receivepi', on_receivepi_response) socketIO.on('updatepi', on_updatepi_response) socketIO.emit('sendrabbit','testdata') socketIO.wait(seconds=1) I know cloudfoundry can be a bit strange as my first idea was to use rabbitmq but it is tied to the VCAP_SERVICES idea. However I did not think such a restriction would be there on a Node.js page. Let me know if there is anything wrong with the above code and if not how can i get my external pi to send reading to my cloud app ? Server Code is listed below though it is not relevant. It responds on localhost...I know the rabbitmq code is not hooked up yet var express = require('express'); var app = express(); var server = require('http').createServer(app); var amqp = require('amqp'); var io = require('socket.io').listen(server) function rabbitUrl() { if (process.env.VCAP_SERVICES) { conf = JSON.parse(process.env.VCAP_SERVICES); return conf['rabbitmq-2.4'][0].credentials.url; } else { return "amqp://localhost"; } } var port = process.env.VCAP_APP_PORT || 3000; var messages = []; function setup() { var exchange = conn.exchange('cf-demo', {'type':'fanout', durable:false}, function(){ var queue = conn.queue('', {durable:false, exclusive:true}, function() { queue.subscribe(function(msg) { messages.push(htmlEscape(msg.body)); if (messages.length > 10) { messages.shift(); } }); queue.bind(exchange.name, ''); }); queue.on('queueBindOk', function() {httpServer(exchange);}); }); } server.listen(8080); io.sockets.on('connection', function(socket){ // when the client emits sendrabbit, this listens socket.on('sendrabbit', function(data) { // we tell the client to execute updatepi with 2 parameters io.sockets.emit('updatepi', socket.username, data) }); socket.on('disconnect', function() { socket.broadcast.emit('updatepi', 'SERVER', socket.username + ' has disconnected'); }); });
It's my understanding that your server should listen on the port Cloud Foundry assigns it (available in an env var). You can't assume it will be 8080. Then the client talks to raspinode-server.cloudfoundry.com (no port) and Cloud Foundry routes it to the correct place.