jamiejako
Posts: 7
Joined: Sat Oct 17, 2015 1:50 am

Speedometer for cars using Pi

Tue Feb 02, 2016 1:36 pm

Hi guys,
I am designing a heads up display for cars using the Pi.
So far I have been able to get the speed from the car using an OBD adapter and PyOBD for the Pi. I need to route this data on to an animated speedometer GUI to display it from the Pi. Do you have any ideas on what to use to create the GUI? All my research points to Pygame. Where would I begin? Is there a better option? Thank you.

scotty101
Posts: 3228
Joined: Fri Jun 08, 2012 6:03 pm

Re: Speedometer for cars using Pi

Tue Feb 02, 2016 1:41 pm

Pygame would work.

There are some predefined gauges for QT which you might be able to customise.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
RogerW
Posts: 270
Joined: Sat Dec 20, 2014 12:15 pm
Location: London UK

Re: Speedometer for cars using Pi

Tue Feb 02, 2016 1:59 pm

if you want to use tkinter this may be of use.

Code: Select all

# meter.py
# class to show a gauge or panel meter
# written by Roger Woollett

from sys import version_info
if version_info[0] < 3:
    import Tkinter as tk
    import tkFont as tkf
else:
    import tkinter as tk
    import tkinter.font as tkf
    
import math

class Meter(tk.Canvas):
    def __init__(self,master,*args,**kwargs):
        tk.Canvas.__init__(self,master,*args,**kwargs)
        
        self.layoutparams()
        self.graphics()
        self.createhand()
        
        self.setrange()
        
    def layoutparams(self):
        # set parameters that control the layout
        height = int(self['height'])
        width = int(self['width'])
        
        # find a square that fits in the window
        if(height*2 > width):
            side = width
        else:
            side = height*2
        
        # set axis for hand
        self.centrex = side/2
        self.centrey = side/2
        
        # standard with of lines
        self.linewidth = 2
        
        # outer radius for dial
        self.radius = int(0.40*float(side))
        
        # set width of bezel
        self.bezel = self.radius/15
        self.bezelcolour1 = '#c0c0c0'
        self.bezelcolour2 = '#808080'
    
        # set lengths of ticks and hand
        self.majortick = self.radius/8
        self.minortick = self.majortick/2
        self.handlen = self.radius - self.majortick - self.bezel - 1
        self.blobrad = self.handlen/6
             
    def graphics(self):
        # create the static components
        self.create_oval(self.centrex-self.radius
        ,self.centrey-self.radius
        ,self.centrex+self.radius
        ,self.centrey+self.radius
        ,width = self.bezel
        ,outline = self.bezelcolour2)
        
        self.create_oval(self.centrex-self.radius - self.bezel
        ,self.centrey-self.radius - self.bezel
        ,self.centrex+self.radius + self.bezel
        ,self.centrey+self.radius + self.bezel
        ,width = self.bezel
        ,outline = self.bezelcolour1)
        
        for deg in range(-60,241,6):
            self.createtick(deg,self.minortick)
        for deg in range(-60,241,30):
            self.createtick(deg,self.majortick)
        
    def createhand(self):
        # create text display
        self.textid = self.create_text(self.centrex
        ,self.centrey - 3*self.blobrad
        ,fill = 'red'
        ,font = tkf.Font(size = -int(2*self.majortick)))
        
        
        # create moving and changeable bits
        self.handid = self.create_line(self.centrex,self.centrey
        ,self.centrex - self.handlen,self.centrey
        ,width = 2*self.linewidth
        ,fill = 'red')
        
        self.blobid = self.create_oval(self.centrex - self.blobrad
        ,self.centrey - self.blobrad
        ,self.centrex + self.blobrad
        ,self.centrey + self.blobrad
        ,outline = 'black', fill = 'black')
        
    def createtick(self,angle,length):
        # helper function to create one tick
        rad = math.radians(angle)
        cos = math.cos(rad)
        sin = math.sin(rad)
        radius = self.radius - self.bezel
        self.create_line(self.centrex - radius*cos
        ,self.centrey - radius*sin
        ,self.centrex - (radius - length)*cos
        ,self.centrey - (radius - length)*sin
        ,width = self.linewidth)
        
    def setrange(self,start = 0, end=100):
        self.start = start
        self.range = end - start
        
    def set(self,value):
        # call this to set the hand
        # convert value to range 0,100
        deg = 300*(value - self.start)/self.range - 240
        
        self.itemconfigure(self.textid,text = str(value))
        rad = math.radians(deg)
        # reposition hand
        self.coords(self.handid,self.centrex,self.centrey
        ,self.centrex+self.handlen*math.cos(rad), self.centrey+self.handlen*math.sin(rad))
        
    def blob(self,colour):
        # call this to change the colour of the blob
        self.itemconfigure(self.blobid,fill = colour,outline = colour)
The following is a test program. Put both files in the same directory.

Code: Select all

# trymeter.py
# test program to try out the meter class
# written by Roger Woollett

from sys import version_info
if version_info[0] < 3:
	import Tkinter as tk
else:
	import tkinter as tk

import meter as m

class Meterframe(tk.Frame):
	def __init__(self,master,text = '',scale=(0,100),*args,**kwargs):
		tk.Frame.__init__(self,master,*args,**kwargs)
		
		width = kwargs.get('width',100)
		self.meter = m.Meter(self,height = width,width = width)
		self.meter.setrange(scale[0],scale[1])
		self.meter.pack()
		
		tk.Label(self,text=text).pack()
		
		tk.Scale(self,length = width,from_ = scale[0], to = scale[1]
		,orient = tk.HORIZONTAL
		,command = self.setmeter).pack()
		
	def setmeter(self,value):
		value = int(value)
		self.meter.set(value)
		
class Mainframe(tk.Frame):
	def __init__(self,master,*args,**kwargs):
		tk.Frame.__init__(self,master,*args,**kwargs)
		
		Meterframe(self,text = 'Meter1',width = 200).grid(row = 0,column = 0)
		Meterframe(self,text = 'Meter2',width = 200,scale = (-50,50)).grid(row = 0,column = 1)
				
		tk.Button(self,text = 'Quit',width = 15,command = master.destroy) \
		.grid(row = 1,column = 0)

class App(tk.Tk):
	def __init__(self):
		tk.Tk.__init__(self)
		
		self.title('Try Meter')
	
		Mainframe(self).pack()
		
App().mainloop()

User avatar
paddyg
Posts: 2243
Joined: Sat Jan 28, 2012 11:57 am
Location: UK

Re: Speedometer for cars using Pi

Mon Oct 03, 2016 2:07 pm

also https://groups.google.com/forum/?hl=en-GB&fromgroups=#!forum/pi3d

foxrider83
Posts: 3
Joined: Wed Nov 25, 2015 10:32 am
Location: FRANCE

Re: Speedometer for cars using Pi

Tue Nov 13, 2018 7:01 pm

Dear all,

I know this post is 2 years old but I would like to make a gauge meter for a RPi project.
The code presented above interests me but I would like to vary the needles from an external variable and not from a scale object.

Here is the code for the meter library. Modified to have 2 needles by gauge meter and an analog value.

Code: Select all

# fxmeter.py
# class to show a gauge or panel meter
# by Jeff Salvo
# inspired by meter.py from Roger Woollett

from sys import version_info
if version_info[0] < 3:
    import Tkinter as tk
    import tkFont as tkf
else:
    import tkinter as tk
    import tkinter.font as tkf
    
import math

class MeterTemp(tk.Canvas):
    def __init__(self,master,*args,**kwargs):
        tk.Canvas.__init__(self,master,*args,**kwargs)
        
        self.layoutparams()
        self.graphics()
        self.createhand()
        self.setrange()
        
    def layoutparams(self):
        # set parameters that control the layout
        height = int(self['height'])
        width = int(self['width'])
        
        # find a square that fits in the window
        if(height*2 > width):
            side = width
        else:
            side = height*2
        
        # set axis for hand
        self.centrex = side/2
        self.centrey = side/2
        
        # standard with of lines
        self.linewidth = 2
        
        # outer radius for dial
        self.radius = int(0.40*float(side))
        
        # set width of bezel
        self.bezel = self.radius/15
        self.bezelcolour1 = '#c0c0c0'
        self.bezelcolour2 = '#808080'
    
        # set lengths of ticks and hand
        self.majortick = self.radius/8
        self.minortick = self.majortick/2
        self.handlen = self.radius - self.majortick - self.bezel - 1
        self.blobrad = self.handlen/6
             
    def graphics(self):
        """ create the static components """
        self.create_oval(self.centrex-self.radius
        ,self.centrey-self.radius
        ,self.centrex+self.radius
        ,self.centrey+self.radius
        ,width = self.bezel
        ,outline = self.bezelcolour2)
        
        self.create_oval(self.centrex-self.radius - self.bezel
        ,self.centrey-self.radius - self.bezel
        ,self.centrex+self.radius + self.bezel
        ,self.centrey+self.radius + self.bezel
        ,width = self.bezel
        ,outline = self.bezelcolour1)
        # create the graduations
        for deg in range(-60,40,6):
            self.createtick(deg, self.minortick, 'blue')
        for deg in range(41,140,6):
            self.createtick(deg,self.minortick, 'green')
        for deg in range(141,241,6):
            self.createtick(deg,self.minortick, 'red')
        for deg in range(-60,241,30):
            self.createtick(deg,self.majortick, 'yellow')
        
    def createhand(self):
        # create text display
        self.textid = self.create_text(self.centrex
        ,self.centrey + 3*self.blobrad
        ,fill = 'red'
        ,font = tkf.Font(size = -int(2*self.majortick)))
        
        # create moving and changeable bits (needle)
        self.handidOil = self.create_line(self.centrex,self.centrey
        ,self.centrex - self.handlen,self.centrey
        ,width = 2*self.linewidth
        ,fill = 'red')
        
        self.handidCool = self.create_line(self.centrex,self.centrey
        ,self.centrex - self.handlen,self.centrey
        ,width = 2*self.linewidth
        ,fill = 'green')
        
        # create the blob (center of the meter)
        self.blobid = self.create_oval(self.centrex - self.blobrad
        ,self.centrey - self.blobrad
        ,self.centrex + self.blobrad
        ,self.centrey + self.blobrad
        ,outline = 'black', fill = 'black')
        
    def createtick(self,angle,length, color = 'black'):
        # helper function to create one tick
        rad = math.radians(angle)
        cos = math.cos(rad)
        sin = math.sin(rad)
        radius = self.radius - self.bezel
        self.create_line(self.centrex - radius*cos
        ,self.centrey - radius*sin
        ,self.centrex - (radius - length)*cos
        ,self.centrey - (radius - length)*sin
        ,width = self.linewidth, fill = color)
        
    def setrange(self,start = 0, end=100):
        self.start = start
        self.range = end - start
        
    def set(self,valueOil, valueCool, valueAmb):
        # call this to set the hand
        # convert value to range 0,100
        degOil = 300*(valueOil - self.start)/self.range - 240
        degCool = 300*(valueCool - self.start)/self.range - 240
        
        self.itemconfigure(self.textid,text = str(valueAmb))
        radOil = math.radians(degOil)
        radCool = math.radians(degCool)
        # reposition hands
        self.coords(self.handidOil,self.centrex,self.centrey
        ,self.centrex+self.handlen*math.cos(radOil), self.centrey+self.handlen*math.sin(radOil))
        self.coords(self.handidCool,self.centrex,self.centrey
        ,self.centrex+self.handlen*math.cos(radCool), self.centrey+self.handlen*math.sin(radCool))
        
    def blob(self,colour):
        # call this to change the colour of the blob
        self.itemconfigure(self.blobid,fill = colour,outline = colour)

class Meterframe(tk.Frame):
    """Create the meter frame"""
    #define global variables.
    valueOil = 0
    valueCool = 0
    valueAmb = 0
    
    """Create the frame object"""
    def __init__(self,master,text = '',scale=(0,100),*args,**kwargs):
        tk.Frame.__init__(self,master,*args,**kwargs)

        width = kwargs.get('width',100)
        self.meter = MeterTemp(self,height = width,width = width)
        self.meter.setrange(scale[0],scale[1])
        self.meter.pack()
        self.scalemin = scale[0]
        self.scalemax = scale[1]
                
        tk.Label(self,text=text).pack()
        self.setmeter(0 ,0, 0)

        """tk.Scale(self,length = width,from_ = scale[0], to = scale[1]
        ,orient = tk.HORIZONTAL
        ,command = self.setmeter).pack()"""

    def setmeter(self,valueOil, valueCool, valueAmb):
        """ Modify the value, change the blob color depend on the value."""
        '''valueOil = int(valueOil)
        valueCool = int(valueCool)
        valueAmb = int(valueAmb)'''
        self.meter.set(valueOil, valueCool, valueAmb)
        plage = self.scalemax - self.scalemin
        #Debug print
        #print('min %s ; max %s ; value %s ; plage %s' % (self.scalemin, self.scalemax, value, plage))
        
        if (valueOil < (plage)/3 + self.scalemin):
            self.meter.blob('blue')
        elif ((valueOil > (plage)/3 + self.scalemin) and (value < 2*(plage)/3 + self.scalemin)):
            self.meter.blob('green')
        elif (valueOil > 2*(plage)/3 + self.scalemin):
            self.meter.blob('red')

'''    # create a timer function to update the values with self.meter.set.
    def updateMeterTimer(self):
        """Fade over time"""
        self.var.set(self.var.get())
        self.after(20, self.updateMeterTimer)
''
And the test function :

Code: Select all

# trymeter.py
# test program to try out the meter class
# written by Jeff Salvo
# inspired by trymeter.py from Roger Woollett

from sys import version_info
if version_info[0] < 3:
    import Tkinter as tk
else:
    import tkinter as tk

import fxmeter as m
import random
from threading import Thread
import time

class Mainframe(tk.Frame):
    def __init__(self,master,*args,**kwargs):
        tk.Frame.__init__(self,master,*args,**kwargs)

        self.tempframe = m.Meterframe(self,text = 'TempMeter',width = 200,scale = (30,120)).grid(row = 0,column = 0)
        tk.Button(self,text = 'Quit',width = 15,command = self.stop) \
        .grid(row = 0,column = 1)
        self._thread = Thread(target = self.updateValues)
        self._thread.start()
     
    def updateValues(self):
        self._active = True
        while(self._active):
            valOil = random.randint(30,120)
            valCool = random.randint(30,120)
            valAmb = random.randint(-30,50)
            self.tempframe.setmeter(valOil, valCool, valAmb)    #Error here !
            #print('%s %s %s' % (valOil, valCool, valAmb))
            time.sleep(2)   

    def stop(self):
        print('bye bye')
        self._active = False
        self.master.destroy()

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.title('Try fxMeter')
        Mainframe(self).pack()
        
App().mainloop()
This will give me an error at the line #Error here !.
I think the object self.tempframe is not implemented but I can't find the solution.

Anybody as an answer ?

Thanks a lot
Jeff

scotty101
Posts: 3228
Joined: Fri Jun 08, 2012 6:03 pm

Re: Speedometer for cars using Pi

Wed Nov 14, 2018 9:28 am

Error Number 1 - Line 21 - trymeter.py

Code: Select all

self.tempframe = m.Meterframe(self,text = 'TempMeter',width = 200,scale = (30,120)).grid(row = 0,column = 0)
should be

Code: Select all

self.tempframe = m.Meterframe(self,text = 'TempMeter',width = 200,scale = (30,120))
self.tempframe.grid(row = 0,column = 0)
Putting grid on the same line as the definition of a tkinter widget is ALWAYS a bad idea (it will bite you later even if it seems to work at first)

Error Number 2 - Line 179 - fxmeter.py

Code: Select all

elif ((valueOil > (plage)/3 + self.scalemin) and (value < 2*(plage)/3 + self.scalemin)):
should be

Code: Select all

elif ((valueOil > (plage)/3 + self.scalemin) and (valueOil < 2*(plage)/3 + self.scalemin)):
There was no parameter called value. I've assumed you meant valueOil
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
Gavinmc42
Posts: 2181
Joined: Wed Aug 28, 2013 3:31 am

Re: Speedometer for cars using Pi

Wed Nov 14, 2018 10:12 am

Raspban and Pygame was my first method 6 yrs ago but it was not that fast and it was buggy, mainly reading the i2c sensors.

I had been using JavaFX for dash type displays on my old stuff.
At the time JavaFx was accelerated but it really needed Pi2-3's to get fast enough.
Development was done with Netbeans and I started with the Stopwatch example.

My favorite javafx gauges https://github.com/HanSolo/Medusa
More recent versions had the dials as an overlay over videos played by omxplayer., still Java based.

Latest versions are totally different.
https://ultibo.org/forum/viewtopic.php?f=15&t=1169
I'm dancing on Rainbows.
Raspberries are not Apples or Oranges

foxrider83
Posts: 3
Joined: Wed Nov 25, 2015 10:32 am
Location: FRANCE

Re: Speedometer for cars using Pi

Thu Nov 15, 2018 12:59 pm

Thanks for your help.

@Scotty101 : It works perfectly. It was a poor error but a new view is always good for debugging.

@Gavinmc42 : Thanks for the reference of this new library but I want to use a low level library.
It's easier to maintain the code.

Bye.
Jeff

User avatar
Gavinmc42
Posts: 2181
Joined: Wed Aug 28, 2013 3:31 am

Re: Speedometer for cars using Pi

Fri Nov 16, 2018 2:22 am

Thanks for the reference of this new library but I want to use a low level library.
It's easier to maintain the code.
That's why I use Ultibo ;)
The only lower level is assembly :lol:
Source code is only a few 100k for my bigger apps.
Most of that is reusable libraries with the main application only 3k.
Lots of the libraries are written by better coders than me, whew. :oops:
I only need to write the main app, which generally only takes a few hours.

Big advantage is 2 sec boot times.
I'm dancing on Rainbows.
Raspberries are not Apples or Oranges

Return to “Graphics, sound and multimedia”