Yun server: Bottle (python) and Bridge (SOLVED)

Hello all, I need some help or ideas for my webserver task. I just want to light on a led with a webserver running on the linux side of Yun. The client is going to be just a simple button which toggles a led. I will communicate linux side with Arduino using bridge.put() and bridge.get().

This is my Arduino code:

#include <Bridge.h>

char* t={(0)};
int aux=0;

void setup() {
  pinMode(13,OUTPUT);
  digitalWrite(13, HIGH);
  delay(60000);  
  Bridge.begin();
  digitalWrite(13, LOW);
  
}

void loop() {

  aux=Bridge.get("on",t,1);
  if(t[1]==1) digitalWrite(13,HIGH);
  
}

I’ve succesfully made a small webserver (shows date and time) running on Yun using bottle framework and html with javascript.

Here’s the bottle code:

#!/usr/bin/python

import sys
from bottle import run, route, template

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()

index_html = open('hola_mundo.html').read()

@route('/home')
def indice():
   return template(index_html)

if __name__ == '__main__':
   run(host='192.168.50.13', port=5555, debug=True)

Here’s HTML/Javascript code

<!DOCTYPE html>
<html>
<body>

<h1>My first Javascript app</h1>

<button type="button"
onclick="document.getElementById('demo').innerHTML = Date()">
Click me to display Date and Time.</button>

<p id="demo"></p>

</body>
</html>

I don’t know how can I get Javascript talk to Bottle and use bridglient.put() to send command to Arduino, if someone could guide me I’d be very grateful.

I think yo need to get your Javascript to make a call to another "route" in the server and the code in the function associated with that route will talk to the Arduino.

I have used bottle in a small PC webserver to control my model trains. But it's a while back and I am rusty.

...R

Robin2:
I think yo need to get your Javascript to make a call to another "route" in the server and the code in the function associated with that route will talk to the Arduino.

I have used bottle in a small PC webserver to control my model trains. But it's a while back and I am rusty.

...R

Thanks for your reply. I like your idea, but how do I use route without redirecting my webpage? For example, a button displayed on "/home" calls a python function through route stored in "/function", but then the browser will load the "/function" route interface, wont it?

mart256:
but how do I use route without redirecting my webpage

Sorry, I have had a quick look at my code but it has been a long time and I can't immediately make sense of it.

As far as I know a function representing a route can send back any web page you want.
But I was really thinking of an AJAX call that just updates part of the page. I know I have done that with Ruby and Sinatra and AFAIK Bottle can do much the same stuff as Sinatra.

This has probably not been much help.

I only picked this up because I have been dithering about buying a Yun.

...R

I have now had another look at my train control code and managed to get the browser part to work (I have changed my OS so some of my absolute pathnames have changed)

I have used the jquery library jquery.js and a small piece of javascript that I wrote myself. When I click an onscreen button it calls my javascript buttonClick() function which formulates a call to the server including the ID of the button that was clicked. The function associated with the "route" in the server gets my Arduino to do stuff and resends the template to the browser. However it does not use any layout so that the content of the browser page is changed without the whole page being changed.

When the webpage is first called the same template is rendered, but then there is a layout so that a complete html page is sent.

(At least that is how I think it works - I would need to spend more time and more refresher reading to be certain)

If any of this sounds useful I will be happy to try to help further. My code associated with Bottle is quite short. The complexity is in the train control background Thread which is unlikely to interest you.

If I forget to check this Thread please send me a PM to wake me up.

...R

That sounds very useful to me, could you show me the bottle code and the javascript code please?
Thanks in advance.

If you can afford to wait 24hrs I will make a simplified application that is complete in itself.
It will do me no harm to refresh my own knowledge.

The alternative is to post a few files from the existing project that may not make a lot of sense without other parts.

...R

Robin2:
If you can afford to wait 24hrs I will make a simplified application that is complete in itself.
It will do me no harm to refresh my own knowledge.

The alternative is to post a few files from the existing project that may not make a lot of sense without other parts.

...R

I'd be happy to wait 24h so you can refresh your knowledge and send me a simple example. Thanks a lot.

Sooner than I had expected.

I realized that it would be too difficult to make it as simple as I had originally envisaged - mainly to remove the use of the file trainViewsHelper. So I have left it in, and I hope it is not too confusing. The way things are laid out and named owes something to Ruby on Rails and the Model View Controller (MVC) idiom.

I have it all in the attached ZIP file. I did extract the file and test it and it seems to work.

To start the server run the file locoDrive.py and then it should appear in your browser at localhost:8085

One thing you will HAVE to change is the path reference in locoDrive.py - there is a comment about it so you should be able to find it.

I hope it works. Feel free to ask questions.

…R

BottleDemo.zip (121 KB)

Robin2:
Sooner than I had expected.

I realized that it would be too difficult to make it as simple as I had originally envisaged - mainly to remove the use of the file trainViewsHelper. So I have left it in, and I hope it is not too confusing. The way things are laid out and named owes something to Ruby on Rails and the Model View Controller (MVC) idiom.

I have it all in the attached ZIP file. I did extract the file and test it and it seems to work.

To start the server run the file locoDrive.py and then it should appear in your browser at localhost:8085

One thing you will HAVE to change is the path reference in locoDrive.py - there is a comment about it so you should be able to find it.

I hope it works. Feel free to ask questions.

...R

Thanks a lot, I'll work on this on weekend :grin:

Thanks a lot Robin2, I could toggle Led13 on and off thanks to your example and I learned interesting stuff.

Here’s what i did:

Arduino Code

#include <Bridge.h>

char t[1]={'3'}; //random value
int aux=0;
long tiempo = 1000;
long tiempo_actual=0;

void setup() {
  pinMode(13,OUTPUT);
  digitalWrite(13, HIGH);
  delay(1000);  
  Bridge.begin();
  //Serial.begin(9600);
  digitalWrite(13, LOW);
  
}

void loop() {

  if(millis()-tiempo_actual > tiempo){
  tiempo_actual=millis();
  aux=Bridge.get("on",t,1);
  //Serial.println(t[0]);
  //Serial.println(aux);
  if(t[0]=='2') digitalWrite(13,HIGH);//turns on when button clicked
  else digitalWrite(13,LOW); //turn off when refresh page
  }
}

Bottle Code

from controllers import cabController as cC

#!/usr/bin/python
import sys
from lib.bottle import route, post, run, template, request, static_file

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()

@route('<path:path>')
def server_static(path):
    print "PATH"
    return static_file(path, root=
    '/mnt/sda2/scripts/BottleDemo/public')
    # previous line must reflect the actual absolute path

@route('/')
@route('/start')
def starting():
    cC.firstLoco()
    print "STARTING"
    try:
        bc.put('on','0')
    except:
        print "Oops!\n"

    return template('cabview')
    
@post('/lococmd')
def continuing():
    cC.locoRun()
    print "CONTINUING"
    try:
        bc.put('on','2')
    except:
        print "Oops!\n"
    return template('cabview')

run(host='localhost', port=5555)
# previous line may need to be changed

Now I gotta keep learning javascript.
Here’s my last question, could it have worked with an html view instead of a .tpl file? .tpl is kinda confusing to me.

Thanks again :wink:

mart256:
Here’s my last question, could it have worked with an html view instead of a .tpl file? .tpl is kinda confusing to me.

My example probably makes the TPL more confusing because of my use of the helper code to build the HTML. This was converted from an earlier Ruby version.

IIRC TPL is just a means of allowing Bottle to build a HTML page programmatically. You could probably just have plain HTML in the TPL file. I think the point about the system is that Bottle can combine the TPL and the LAYOUT code to make a complete web page. In my example that is done when the page is first loaded. But subsequent calls don’t bother with the LAYOUT stuff.

I found the Bottle documentation reasonably easy to follow but that was probably because I had already done stuff with Ruby on Rails and Ruby Sinatra so I was asking “how do I do X with Bottle”

I have taken the plunge and ordered a YUN - hope it will arrive tomorrow.

…R

Robin2, I’m still learning quite a lot from your example. And I wonder if you can explain me some things in order to search more info.

From Javascript you call the function “buttonClick(event)”, this function looks like this:

function buttonClick(event){
		$('input[name="btnclick"]').val(event.target.id);
    var params = $('#cabForm').serialize();

    $.post('/lococmd',
    	params,
    	function (response){
        $('#content').html(response);
    	});
		};

Also, the cabview.tpl (where the buttons are defined) is this:

% from helpers import trainViewsHelper as tvH
% from models import dataClasses as dC


{{ ! tvH.form_h({'id': "cabForm" }) }}
  {{ ! tvH.input_hidden_h({'name':  "loconame", 'value':  dC.loconame}) }}
	{{ ! tvH.input_hidden_h({'name':  "step", 'value':  dC.step}) }}
	{{ ! tvH.input_hidden_h({'name':  "dirn", 'value':  dC.dirn}) }}
	{{ ! tvH.input_hidden_h({'name':  "btnclick", 'value':  "btnclick"}) }}

	<div>
	{{ ! tvH.button_h({'value':  "STOP ALL", 'id':  "Stop_Stop" , 'onclick':  "buttonClick(event)"})}}
				


	</div>


	<table>
	% stepNum = dC.numSpeedSteps - 1
		<tr>
				
		</tr>
		% for n in range(dC.numSpeedSteps):
			<tr>
					% if dC.step == stepNum:
					% 		bstyle = "btnON"
					% else:
					%     bstyle = "btnOFF"
					% end
					<td align="center">{{ ! tvH.button_h({'value':  stepNum, 'id':  "Step_" + str(stepNum), 'onclick':  "buttonClick(event)", 'class':  bstyle}) }}</td>
					<td></td>
	
			</tr>
			% stepNum = stepNum - 1
		% end
		<tr>
			

				% if dC.dirn == "F":
					% 	bupstyle = "btnON"
					%		bdnstyle = "btnOFF"
				% else:
					% 	bupstyle = "btnOFF"
					%		bdnstyle = "btnON"
				% end
				<td align="center">{{ ! tvH.button_h({'value': "< FWD", 'id':  "Dirn_F", 'onclick':  "buttonClick(event)", 'class':  bupstyle} )}}
				{{ ! tvH.button_h({'value': "REV >", 'id':  "Dirn_R", 'onclick':  "buttonClick(event)",	'class':  bdnstyle} )}}
				</td>
			
		</tr>
	</table>
	
	
</form>
% if dC.layout != 'none':
  %rebase layout

And the called function, locoRun(), is this:

def locoRun():
    print "BRq "
    for nn in request.forms:
      print nn
#    print "BTNCLICK " + request.forms.btnclick
   
    dC.layout = 'layout'
    
    dC.loconame = request.forms.loconame
    dC.step = int(request.forms.step)
    dC.dirn = request.forms.dirn
    dC.drivercode = request.cookies.get('controllerID')
    
    # browser javascript will have a change for one of step, dirn, heading, or loconame
    newloconame = dC.loconame # this is still the old name
    btnclick = request.forms.btnclick.split("_")
    x = btnclick[1]
    y = btnclick[0]

    newDirn = dC.dirn
    if y == "Step":
       dC.step = int(x)
    elif y == "Dirn":
        newDirn = x
    elif y == "NewLoco":
        newloconame = x
    else:
        somethingElse = x

    # browser javascript will have changed one of step, dirn, heading or newloconame
    if newDirn != dC.dirn and dC.step == 0:
      dC.dirn = newDirn

    if (newloconame != dC.loconame) and (dC.step == 0):
      locoChanged = changeloco(newloconame, dC.drivercode)
      if locoChanged == True:
        dC.loconame = newloconame
        
      print "NEW NAME " + dC.loconame
 

    driveloco(dC.drivercode)

    
    if btnclick[0] == 'Stop':
      stopall()

    dC.layout = 'none'
   
    return 

#================


def driveloco(drcode):

    return
#======================

def stopall():

  print "=========STOPALL"

  return

I understand that jquery somehow identifies the id from the button pressed and gives this information to the server through an Ajax post, then the server calls locoRun(), and this functions receives the id from the button pressed and responds accordingly.

My questions are about the implementation, I’m trying to replicate this task on my example but I get stuck in the three codes because of my lack of knowledge (the tvH. implementation makes things very confusing :drooling_face:).

(Continuing in next post)

Here’s what I’ve got so far:

buttonClick function (train.js):

function buttonClick(event){
		$('input[name="btnclick"]').val(event.target.id);
    var params = $('#cabForm').serialize();

    $.post('/lococmd',
    	params,
    	function (response){
        $('#content').html(response);
    	});
		};

html and javascript code:

<!DOCTYPE html>
<html>
<body>

<h1>Toggle LED 13</h1>

<script type="text/javascript" src="javascript/jquery.js"></script>
<script type="text/javascript" src="javascript/train.js"></script>

<button type="button" id="bon" 
onclick="buttonClick(event)">
ON</button>

<button type="button" id="boff"
onclick="buttonClick(event)">
OFF</button>


</body>
</html>

Bottle code:

from controllers import cabController as cC

#!/usr/bin/python
import sys
from lib.bottle import route, post, run, template, request, static_file

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()

index_html = open('index2.html').read()

@route('<path:path>')
def server_static(path):
    print "PATH"
    return static_file(path, root=
    '/mnt/sda2/scripts/BottleDemo/public')
    # previous line must reflect the actual absolute path

@route('/')
@route('/start')
def starting():
    print "STARTING"
    cC.firstLoco()
    return template(index_html)

@post('/lococmd')
def continuing():
    cC.locoRun()
    print "CONTINUING"
    return template(index_html)

run(host='192.168.50.13', port=5555)
# previous line may need to be changed

cabController.py :

#!/usr/bin/python
import sys

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()

from lib.bottle import route, run, template, request, response
from helpers import trainViewsHelper as tvH
import time
from models import dataClasses as dC


def firstLoco():
   
   print "comenzando"
   bc.put('on','0')
   return 

#================

def locoRun():
    print "Hola que tal"
    bc.put('on','2')
    return

The webpages renders fine, I only have to figure out how to tell the server wether button ON or OFF was pressed. I want to use your method recognizing the button id. Can you help me please.

The webpages renders fine, I only have to figure out how to tell the server wether button ON or OFF was pressed. I want to use your method recognizing the button id. Can you help me please.

I'm not clear whether you are referring to a Button connected to an Arduino pin, or a button on your web page. While I now have my Yun I have done almost nothing with it because I am doing another project.

Assuming you are trying to send info from the web page to the Arduino I think your locoRun() function needs to access the Bottle request object to get info that came from the browser.

The project I am working on is a derivative of the code I suggested to you, so now is a good time to ask questions. I am hoping the project will work on the Yun - but I will get it working on my PC first.

One thing in my code that confused me was the Javascript $('#content').html(response); because I had forgotten that the word "content" is just the ID of a DIV. In my own code I have changed the ID and the code to contentDiv so I won't confuse myself next time. (This is doubly annoying because I complain about other people writing code using variable names that give the impression they have some special significance)

By the way the JQuery website has good documentation.

...R

I just came across this W3Schools JQuery web page. I have saved a copy of the code as a HTML file. It is a great tool for exploring how JQuery works - everything is on one page and is very simple.

…R

Please mark this thread as Solved.

Ok, it’s been a while but I figured out how to set a simple server running on the Linux side (python server), rendering a local webpage using html and javascript, and using Jquery Ajax to transfer data between client and server, and then using bridge to share data with Arduino!

I will share this little example that may be very useful to beginners like me.

What’s this project about: You can turn on or off Led 13 of Arduino Yun from any webrowser, from any gadget (cellphone or pc) connected to the local network. When you understand this example, you can set sensors to gather data or controll lightbulbs at home and other interesting stuff.

Software Parts of the proyect:

1.- Python server running on Linux, made with Bottle framework.
2.- HTML interface (with javascript code)
3.- Arduino Sketch

Hardware Parts:

1.- Arduino Yun
2.- Any cellphone with browser
3.- PC to program Arduino

Thanks to Robin2 for sharing his previous project.

Python Server Code :

from controllers import ledController as led

#!/usr/bin/python
import sys
from lib.bottle import route, post, run, template, request, static_file

index_html = open('index2.html').read()

@route('<path:path>')
def server_static(path):
    print "PATH"
    return static_file(path, root=
    '/mnt/sda2/scripts/BottleDemo_led13/public')
    # previous line must reflect the actual absolute path

@route('/')
@route('/start')
def starting():
    print "STARTING"
    return template(index_html)

@post('/ledCmd')
def continuing():
    led.set()
    print "toggle"
    return template(index_html)

run(host='192.168.50.13', port=5555)
# previous line may need to be changed

Python server led controller

#!/usr/bin/python
import sys

sys.path.insert(0, '/usr/lib/python2.7/bridge/')
from bridgeclient import BridgeClient as bridgeclient
bc = bridgeclient()

from lib.bottle import route, run, template, request, response

def set():
    
    x=request.forms.get('boton')
    print x
    bc.put('on',x)
    return

HTML/Javascript Code

<!DOCTYPE html>
<html>
<body>

<h1>Toggle LED 13</h1>

<script type="text/javascript" src="javascript/jquery.js"></script>
<script type="text/javascript" src="javascript/led.js"></script>

<script> 
var ledOn = 2;
var ledOff = 1;
</script>

<button type="button" id="bon" 
onclick="buttonClick(ledOn)">
ON</button>

<button type="button" id="boff"
onclick="buttonClick(ledOff)">
OFF</button>


</body>
</html>

Javascript custom function

function buttonClick(y){
    
    $.post('/ledCmd',{
    	boton: y
	},
    	function (response){
        $('#content').html(response);
    	});
		};

Arduino Yun Sketch:

#include <Bridge.h>

char t[1]={'5'}; //random value
int aux=0;
long tiempo = 500;     //refresh time
long tiempo_actual=0;

void setup() {
  pinMode(13,OUTPUT);
  digitalWrite(13, HIGH); //Lights goes off when Linux is ready
  delay(60000);     //wait until Linux side boots
  Bridge.begin();
  //Serial.begin(9600);
  digitalWrite(13, LOW);
  
}

void loop() {

  if(millis()-tiempo_actual > tiempo){
  tiempo_actual=millis();
  aux=Bridge.get("on",t,1);
  //Serial.println(t[0]);
  //Serial.println(aux);
  if(t[0]=='2') digitalWrite(13,HIGH);
  else digitalWrite(13,LOW); 
  }
}

I’ll attach the project, you may add this example to Yun’s playground as a linux server demo.

BottleDemo_led13.zip (162 KB)

mart256:
Please mark this thread as Solved.

That is your job.
Go back to your original post and click modify, then you can change the Title.

Good to hear you have got it working.

...R