Free Your Model Train (FYMT) — Python3 und Arduino - Teil 2

Das Konzept von FYMT

Dokumentation zu FYMT

Zum ersten Teil

Thema

Es geht noch einmal um Steuerung von zwei LEDs per GUI.

Dieses Mal soll das Python3-Skript die Helligkeit der LEDs quasi stufenlos dimmen können.

Der bisherige Schaltungsaufbau und der Arduino-Sketch werden weiter verwendet.

Python3 GUI

Diese ist etwas anders.

Bildschirmfoto

Bildschirmfoto des grafischen Benutzeroberflaeche

Quellcode

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
#  smoothdim.py
#  
#  Copyright 2017 Dr. Michael <info@rechtsanwalt-stehmann.de>
#  
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 3 of the License, or
#  (at your option) any later version.
#  
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#  
#  
#    
# This program works with the sketch 'MoreLEDs.ino'
#
# For Debian you have to install these additional packages
# -- python3-tk
# -- python3-serial


import serial

# for the GUI
from tkinter import * 
from tkinter import messagebox
from tkinter import scrolledtext 

class DialogMaker(object):

	def importantvalues(self):
		"""self.fs contains the font size
		   10 seems to be a good default value"""
		self.fs = 10
		"""colors for backgrounds"""
		self.darkbgcolor = "#EDF25D"
		self.brightcolor = "#F6F9AC"
		
	"""Creates and destroys dialog"""		
	def makedia(self):
		self.importantvalues()
		"""Creates a Tkinter dialog"""
		self.dia=Tk()
		return self.dia

	def diatitle (self, title):
		"""Setting the title of the dialog"""
		self.dia.title(title)
		"""Stores a short name of the running application"""

	def diaminsize(self, diah=600, diav=250):
		"""Minimum size of the dialog window
		   diah = horizontal minimum size
		   diav = vertical minimum size"""
		self.dia.minsize(diah, diav)

	def geometry(self, geostring="+50+0"):
		"""Places dialog on the desktop,
		   the first number is horizontal, the second vertical"""
		self.dia.geometry(geostring)

	def mainloop(self):
		"""Creates the dialog"""
		self.dia.mainloop()
		
	"""Creates menu"""
	def menu(self):
		# self.fs contains the font size
		self.menu = Menu(self.dia) #, font = "SansSerif, "+str(self.fs))
		self.dia.config(menu=self.menu)

	def menuitems(self, menulabel, itemdict):
		submenu = Menu(self.menu)
		self.menu.add_cascade(label=menulabel, menu=submenu, font = "SansSerif, "+str(self.fs))
		itemlist = list(itemdict.keys())
		itemlist.sort()
		for entry in itemlist:
			valuetup = itemdict[entry]
			itemlabel = valuetup[0]
			if itemlabel == "Separator":
				submenu.add_separator()
			else:
				itemcommand = valuetup[1]
				submenu.add_command(label=itemlabel, command=itemcommand, font = "SansSerif, "+str(self.fs))

	"""Creates frame"""	
	def makeframe(self):
		self.frame = Frame(self.dia, borderwidth=2, bg = self.darkbgcolor)
		self.frame.pack(fill="both",expand=1)
		return self.frame

	"""Destroys the frame"""
	def endframe(self):
		self.frame.destroy()
		
	"""General elements to create Tkinter dialogs"""
		
	def button(self, buttontext, command_, row_, column_):
		"""Creates a button"""
		button = Button(self.frame, text = buttontext, command = command_)
		button.grid(row=row_, column=column_, padx=10)
		return button

	def label(self, ausgabetext, row_, column_, columnspan_=1):
		"""Creates a label field"""
		lb = Label(self.frame, text = ausgabetext, bg=self.brightcolor)
		lb.grid(row=row_, column=column_, columnspan=columnspan_, sticky=E, padx=5, pady=10)
		return lb

	def scrtxtfield(self, row_, column_, columnspan_=1, width_=75, height_=18):
		stf = scrolledtext.ScrolledText(self.frame, width=width_)
		stf.grid(row=row_, column=column_, columnspan=columnspan_)
		stf["height"] = height_
		return stf


class LEDsGui(DialogMaker):

	def __init__(self):
		"""instantiate class ArduCom"""
		self.ardu = ArduCom()
		"""Set some initial values"""
		self.brightness_1 = 100
		self.brightness_2 = 100
		self.stop1 = 0
		self.stop2 = 0
		"""Create the dialog"""
		self.dia()

	def dia(self, flag=1):

		"""Creates the dialog"""
		self.makedia()
		self.diaminsize()
		self.geometry()
		self.diatitle("Dimming LEDs with Python")

		"""Creates the menu"""
		self.menu()
		tasklabel = "Tasks"
		taskitemsdict = {
			1 : ("Connect to Arduino", self.makeconnection),
			2 : ("Arduino info", self.arduinoinfo),
			3 : ("Close", self.finishprogram)
			}
		self.menuitems(tasklabel, taskitemsdict)

		"""Creates a frame"""
		self.makeframe()

		"""Creates elements"""

		"""First set"""
		self.label("LED 1", 0, 0)
		self.darkb_1 = self.button("<", self.darker_1, 0, 1)
		self.darkb_1.bind("<Button-1>", self.countdark_1)
		self.darkb_1.bind("<ButtonRelease-1>", self.cstop_1)

		self.vtxt_1 = StringVar()
		self.b_label_1 = Label(self.frame, textvariable = self.vtxt_1, bg = "white")
		self.b_label_1.grid(row=0, column=2, padx=5, pady=10, sticky=EW)
		self.vtxt_1.set(str(self.brightness_1))

		self.brigntb_1 = self.button(">", self.brighter_1, 0, 3)
		self.brigntb_1.bind("<Button-1>", self.countbright_1)
		self.brigntb_1.bind("<ButtonRelease-1>", self.cstop_1)

		self.stopbutton1 = self.button("off", self.stop_1, 0, 4)

		"""Second set"""
		self.label("LED 2", 1, 0)
		self.darkb_2 = self.button("<", self.darker_2, 1, 1)
		self.darkb_2.bind("<Button-1>", self.countdark_2)
		self.darkb_2.bind("<ButtonRelease-1>", self.cstop_2)

		self.vtxt_2 = StringVar()
		self.b_label_2 = Label(self.frame, textvariable = self.vtxt_2, bg = "white")
		self.b_label_2.grid(row=1, column=2, padx=5, pady=10, sticky=EW)
		self.vtxt_2.set(str(self.brightness_2))

		self.brigntb_2 = self.button(">", self.brighter_2, 1, 3)
		self.brigntb_2.bind("<Button-1>", self.countbright_2)
		self.brigntb_2.bind("<ButtonRelease-1>", self.cstop_2)

		self.stopbutton2 = self.button("off", self.stop_2, 1, 4)

		"""Field for messages"""
		self.messagebox = self.scrtxtfield(2, 0, 5)
		self.messageboxAddText("Messages of the Arduino")
		self.messageboxAddText("-----------------------")

		self.add_1 = 0
		self.add_2 = 0
		"""initialize timers for the PWD-labels"""
		self.b_label_1.after(0, self.dimmer_1)
		self.b_label_2.after(0, self.dimmer_2)

		self.mainloop()

	"""Method for the field for messages"""
	def messageboxAddText(self, text):
		self.messagebox.configure(state = "normal")
		text = text + "\n"
		self.messagebox.insert("end", text)
		self.messagebox.configure(state = "disabled")

	"""Common method"""
	def brightnessString(self, brightnessvalue):
		if brightnessvalue == 0:
			bstring = "000"
		elif brightnessvalue > 99 and brightnessvalue <= 225:
			bstring = str(brightnessvalue)
		elif brightnessvalue > 225:
			bstring = "255"
		else:
			bstring = "0"+str(brightnessvalue)

		return bstring

	"""Methods for the first set"""
	def countbright_1(self, event):
		self.add_1 = 1

	def cstop_1(self,event):
		self.add_1 = 0

	def countdark_1(self, event):
		self.add_1 = -1

	def dimmer_1(self):
		if self.brightness_1 == 0 or self.brightness_1 == 255:
			self.add_1 = 0
		self.brightness_1 = self.brightness_1 + self.add_1
		self.vtxt_1.set(str(self.brightness_1))
		self.b_label_1.after(20, self.dimmer_1)

	def darker_1(self):
		if self.brightness_1 > 25:
			self.brightness_1 = self.brightness_1 - 1
			self.vtxt_1.set(str(self.brightness_1))
		bstring =  self.brightnessString(self.brightness_1)
		messageText = self.ardu.comm2ardino("1"+bstring)
		self.messageboxAddText(messageText)


	def brighter_1(self):
		if self.brightness_1 == 0: self.brightness_1 = 25
		if self.brightness_1 < 255:
			self.brightness_1 = self.brightness_1 + 1
			self.vtxt_1.set(str(self.brightness_1))

		bstring =  self.brightnessString(self.brightness_1)
		messageText = self.ardu.comm2ardino("1"+bstring)
		self.messageboxAddText(messageText)

		if self.stopbutton1["text"] == "on":
			self.stopbutton1["text"] = "off"
			self.stop1 = 0

	def stop_1(self):
		if self.stop1 == 0:
			self.brightness_1 = 0
			self.vtxt_1.set(str(self.brightness_1))
			bstring =  self.brightnessString(self.brightness_1)
			messageText = self.ardu.comm2ardino("1"+bstring)
			self.stopbutton1["text"] = "on"
			self.stop1 = 1
		else:
			self.brightness_1 = 125
			self.vtxt_1.set(str(self.brightness_1))
			bstring =  self.brightnessString(self.brightness_1)
			messageText = self.ardu.comm2ardino("1"+bstring)
			self.stopbutton1["text"] = "off"
			self.stop1 = 0
		self.messageboxAddText(messageText)

	"""Methods for the second set"""
	def countbright_2(self, event):
		self.add_2 = 1

	def cstop_2(self,event):
		self.add_2 = 0

	def countdark_2(self, event):
		self.add_2 = -1

	def dimmer_2(self):
		if self.brightness_2 == 0 or self.brightness_2 == 255:
			self.add_2 = 0
		self.brightness_2 = self.brightness_2 + self.add_2
		self.vtxt_2.set(str(self.brightness_2))
		self.b_label_2.after(20, self.dimmer_2)

	def darker_2(self):
		if self.brightness_2 > 25:
			self.brightness_2 = self.brightness_2 - 1
			self.vtxt_2.set(str(self.brightness_2))
		bstring =  self.brightnessString(self.brightness_2)
		messageText = self.ardu.comm2ardino("2"+bstring)
		self.messageboxAddText(messageText)

	def brighter_2(self):
		if self.brightness_2 == 0: self.brightness_2 = 25
		if self.brightness_2 < 255:
			self.brightness_2 = self.brightness_2 + 1
			self.vtxt_2.set(str(self.brightness_2))

		bstring =  self.brightnessString(self.brightness_2)
		messageText = self.ardu.comm2ardino("2"+bstring)
		self.messageboxAddText(messageText)

		if self.stopbutton2["text"] == "on":
			self.stopbutton2["text"] = "off"
			self.stop2 = 0

	def stop_2(self):
		if self.stop2 == 0:
			self.brightness_2 = 0
			self.vtxt_2.set(str(self.brightness_2))
			bstring =  self.brightnessString(self.brightness_2)
			messageText = self.ardu.comm2ardino("2"+bstring)
			self.stopbutton2["text"] = "on"
			self.stop2 = 1
		else:
			self.brightness_2 = 125
			self.vtxt_2.set(str(self.brightness_2))
			bstring =  self.brightnessString(self.brightness_2)
			messageText = self.ardu.comm2ardino("2"+bstring)
			self.stopbutton2["text"] = "off"
			self.stop2 = 0
		self.messageboxAddText(messageText)

	"""Methods for the menu"""
	def makeconnection(self):
		conn = self.ardu.connect2arduino()
		self.messageboxAddText(conn)

	def arduinoinfo(self):
		messageText = self.ardu.comm2ardino("vvv")
		self.messageboxAddText(messageText)

	def finishprogram(self):
		"""Destroys the frame"""
		self.endframe()
		"""Destroys the dialog"""
		self.dia.destroy()


class ArduCom(object):

	def connect2arduino(self):
		locations=['/dev/ttyUSB0','/dev/ttyUSB1','/dev/ttyUSB2','/dev/ttyUSB3',
'/dev/ttyS0','/dev/ttyS1','/dev/ttyS2','/dev/ttyS3']  
		messageboxText = ""
		for device in locations:  
			try:  
				messageboxText = (messageboxText+"Trying "+device+"\n")
				self.arduino = serial.Serial(device, 9600)
				messageboxText = (messageboxText+"successfully!")
				break
			except:  
				messageboxText = (messageboxText+"Failed to connect on "+device+"\n")
		return messageboxText

	def comm2ardino(self, commandstr):
		try:  
			self.arduino.write(commandstr.encode('utf-8'))
			messageText = self.arduinoanswer()
			if commandstr == "vvv":
				self.arduino.write("".encode('utf-8'))
				messageText = messageText + self.arduinoanswer()
		except:   
			messageText = "Failed to send!"
		return messageText

	def arduinoanswer(self):
		answer = self.arduino.readline()
		answer = answer.decode("utf-8", "ignore")
		return answer


def main():
	gui = LEDsGui()
	return 0

if __name__ == '__main__':
	main()

Anmerkungen

Dieses Skript arbeitet mit dem zuvor vorgestellten Sketch "MoreLEDs.ino" zusammen, wenn sich dessen Kompilat auf dem Arduino befindet.

Zentrale Neuerung ist die quasi stufenlose Regelbarkeit. Diese erfordert eigentlich eine "Schleife in der Schleife" (mainloop()). Diese lässt sich aber nur schwerlich realisieren.

Zum Einsatz kommt daher der die Methode after() in Tkinter.

Die meisten Widgets — so auch die hier verwandten Label — verfügen aber über eine Methode after(ms,func[,argl[,..]]), die nach ms Millisekunden die Funktion func aufruft. Mit einem solchen (rekursiv verwendeten) "alarm handler" kann die gewünschte Funktion realisiert werden.

In der Methode dia() der Klasse LEDsGui() erfolgt am Ende vor dem Start der Schleife mainloop() die Implementierung dieser "alarm handler". Diese werden dann in den Methoden dimmer_1() und dimmer_2() derselben Klasse mit einem konkreten Wert für die Zeit (20 ms) aktiviert, sodass ein rekursiver Aufruf dieser Methoden nach jeweils 20 ms erfolgt..

Im Übrigen gelten die Anmerkungen zum Python-GUI-Skript der ersten Teils.

Zum ersten Teil

Das Konzept von FYMT

Dokumentation zu FYMT