pwm_client 7.55 KB
#!/usr/bin/python3
"""
author Benoit Dubois
copyright FEMTO ENGINEERING
license GPL v3.0+
brief Basic GUI to deal with "PWM controller" of STM32 board.
Need to connect USB cable to CN5 to open a serial link with board
(usually /dev/ttyACM0).
"""
# Ctrl-c closes the application
import signal
signal.signal(signal.SIGINT, signal.SIG_DFL)
import os
import sys
import struct
import argparse
import configparser
import tkinter as tk
from tkinter import messagebox
import serial
APPLICATION = "pwm_client"
BASE_CONFIG_DIR = os.path.expanduser("~") + "/.config/" + APPLICATION
PARAMETERS = (
{'mnemo': "value", 'default': 0, 'min': -100, 'max': 100, 'format': None},
)
#==============================================================================
class ClientStmGui(tk.Frame):
"""Basic GUI to deal with "PWM handler" on STM32 board.
"""
def __init__(self, master=None):
"""
master: the parent window of the tk.Frame
"""
super().__init__(master)
self.cctrl = tk.IntVar()
self._build_ui()
def _build_ui(self):
lbl = (dict(), dict())
self.sbox = (dict(), dict())
self.box_val = (dict(), dict())
self.btn = (dict(), dict())
for pwmh in range(2):
for param in PARAMETERS:
lbl[pwmh][param['mnemo']] = tk.Label(text=param['mnemo'])
self.box_val[pwmh][param['mnemo']] = tk.IntVar()
self.sbox[pwmh][param['mnemo']] = tk.Spinbox(from_=param['min'],
to=param['max'],
textvariable=self.box_val[pwmh][param['mnemo']],
format=param['format'])
self.btn[pwmh][param['mnemo']] = tk.Button(text="Set")
# Layout
tk.Label(text='Controller 1').grid(row=0, column=1)
tk.Label(text='Controller 2').grid(row=0, column=4)
for pwmh in range(2):
for idx, param in enumerate(PARAMETERS):
lbl[pwmh][param['mnemo']].grid(row=idx+len(PARAMETERS)+1,
column=pwmh*3+0)
self.sbox[pwmh][param['mnemo']].grid(row=idx+len(PARAMETERS)+1,
column=pwmh*3+1)
self.btn[pwmh][param['mnemo']].grid(row=idx+len(PARAMETERS)+1,
column=pwmh*3+2)
# Global look
self.config(bd=3, relief='groove')
#==============================================================================
class ClientPwm(object):
def __init__(self, port, config_file, master=None):
"""
port: serial port device file (e.g. /dev/ttyACMx)
config_file: a configuration file used to initialise the client
master: the parent window (see ClientStmGui())
"""
self.stm = serial.Serial(port=port,
baudrate=9600,
timeout=1,
xonxoff=False,
rtscts=False,
dsrdtr=True)
self.stm.reset_input_buffer()
self.stm.reset_output_buffer()
self.ui = ClientStmGui(master)
for pwmh in range(2):
for param in PARAMETERS:
self.ui.btn[pwmh][param['mnemo']].configure(
command=lambda x=pwmh, y=param['mnemo']:
self.btn_clicked(x, y))
self.config_file = config_file
self.config_to_device()
self.config_to_ui()
self.ui.mainloop()
def btn_clicked(self, pwmh, mnemo):
"""Action when button is clicked
"""
try:
value = self.ui.box_val[pwmh][mnemo].get()
except Exception as ex:
print("Value error: {}".format(ex))
return
self.send_cmd(mnemo, pwmh, value)
print("Send to STM:", mnemo, pwmh, value)
retval = self.get_data()
print("STM returns:", retval)
self.save_setting('pwm_handler'+str(pwmh), mnemo, value)
def config_to_device(self):
"""Get value of parameters from configuration file and
set them to device.
"""
for pwmh in range(2):
config = self.get_pwmh_config(pwmh)
for param in PARAMETERS:
mnemo = param['mnemo']
value = config[param['mnemo']]
self.send_cmd(mnemo, pwmh, value)
def config_to_ui(self):
"""Get value of parameters of the controller from configuration
file and set them to UI.
"""
for pwmh in range(2):
config = self.get_pwmh_config(pwmh)
for param in PARAMETERS:
self.ui.box_val[pwmh][param['mnemo']]. \
set(config[param['mnemo']])
def send_cmd(self, mnemo, pwmh, value):
"""High level data sending process
"""
msg = "{} {} {}".format(mnemo, pwmh, value)
header = struct.pack('<L', len(msg))
self.stm.write(header + msg.encode('utf-8'))
def get_data(self):
try:
data = self.stm.readline().decode('utf-8')
except:
print("Get data error: serial read error")
return
return data
def get_pwmh_config(self, pwmh):
configp = configparser.ConfigParser()
configp.read(self.config_file)
pwmh_cfg = dict()
for param in PARAMETERS:
pwmh_cfg[param['mnemo']] = configp.getint('pwm_handler'+str(pwmh),
param['mnemo'])
return pwmh_cfg
def save_setting(self, section, parameter, value):
"""Write a specific setting to the configuration file.
"""
configp = configparser.ConfigParser()
configp.read(self.config_file)
configp.set(section, parameter, str(value))
with open(self.config_file, 'w') as fd:
configp.write(fd)
#==============================================================================
def reset_settings(settings_file):
"""Resets the "settings" file with default values.
:param settings_file: file containing setting data value (str)
:returns: None
"""
config = configparser.ConfigParser()
for pwmh in range(2):
config.add_section('pwm_handler'+str(pwmh))
for param in PARAMETERS:
config.set('pwm_handler'+str(pwmh),
param['mnemo'],
str(param['default']))
# Write modification to setting file
with open(settings_file, 'w') as fd:
fd.truncate(0) # Reset file contents
config.write(fd)
#==============================================================================
def handle_args():
parser = argparse.ArgumentParser(
prog="client_stm",
usage="%(prog)s [options]",
description="Client for \"double PID\" on STM32",
)
parser.add_argument(
"-p", "--port",
dest="port",
action="store",
help="Set the port to connect to",
default="/dev/ttyACM0"
)
return parser.parse_args(sys.argv[1:])
#==============================================================================
def main():
args = handle_args()
ini_file = BASE_CONFIG_DIR + '/' + APPLICATION + ".ini"
if not os.path.isfile(ini_file):
os.makedirs(BASE_CONFIG_DIR, exist_ok=True)
reset_settings(ini_file)
root = tk.Tk()
root.resizable(0, 0)
root.title("PWM handler client")
app = ClientPwm(args.port, ini_file, root)
#==============================================================================
main()