xenodius

AmPy2

Sep 3rd, 2020 (edited)
512
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 14.88 KB | None | 0 0
  1. import time
  2. import serial
  3. import minimalmodbus
  4. import os
  5. from xml.etree import ElementTree as et
  6. import tkinter as tk
  7. import tkinter.font as tkFont
  8. import threading
  9. # import numpy as np
  10. # import pandas as pd
  11.  
  12. # ASI Parameter Dictionary; check execution directory
  13. filename = 'ASIObjectDictionary.xml'
  14. filepath = os.path.abspath(os.path.join('', filename))
  15. xml = et.parse(filepath)
  16. Obdic = xml.iterfind('Parameters/ParameterDescription')
  17.  
  18. # Basic serial connection parameters for RS232/RTU MODBUS protocol
  19. BACport = "COM4"
  20. BACaddress = 1
  21. BACmode = minimalmodbus.MODE_RTU
  22. BACbaudrate = 115200
  23. BACbytesize = 8
  24. BACparity = serial.PARITY_NONE
  25. BACstopbits = 1
  26. BACtimeout = 0.2
  27.  
  28. instr = minimalmodbus.Instrument(BACport, BACaddress)
  29. instr.address = BACaddress
  30. instr.mode = BACmode
  31. instr.serial.baudrate = BACbaudrate
  32. instr.serial.bytesize = BACbytesize
  33. instr.serial.parity = BACparity
  34. instr.serial.stopbits = BACstopbits
  35. instr.serial.timeout = BACtimeout
  36.  
  37. # Fixed class(BAC) attributes
  38. # BACport = 'COM4'
  39. # BACaddress = 1
  40. # BACmode = minimalmodbus.MODE_RTU
  41. # BACbaudrate = 115200
  42. # BACbytesize = 8
  43. # BACparity = serial.PARITY_NONE
  44. # BACstopbits = 1
  45. # BACtimeout = 0.2
  46.  
  47. # Build dictionary definitions
  48. ObdicAddress = {}
  49. ObdicScale = {}
  50. ObdicUnit = {}
  51. ObdicEnum = {} # "enumerated" bitwise parameters, returns bit key/name as string
  52. ObdicBit = {} # "bit vector" bitwise parameters, returns bit key/name as
  53. RegsDic = {}
  54.  
  55. for parent in Obdic:  # InternalAppEntity/Parameters/ParameterDescription children
  56.     scale = parent.find('Scale').text
  57.     if scale == ('enum'):
  58.         key = parent.find('Key').text
  59.         address = int(parent.find('Address').text)
  60.         ObdicAddress.update({key: address})
  61.         Enumerations = parent.findall('Enumerations/string')  #Text string
  62.         # print('Parent: ', key)
  63.         for EnumBit, EnumKey in enumerate(Enumerations):  #returns loop number from 0 for first term EnumBit, while EnumKey = iteration of each Enumerations element
  64.             ObdicEnum.update({key: {EnumKey.text: EnumBit}})  # Ordered to return bit controlling key:string e.g.
  65.             # 'Speed_Regulator_Mode: Torque Mode with Speed Limiting'
  66.             # print('Parent: ', key, 'Enumbit: ', EnumBit, 'Enumstring: ', EnumKey.text) #et.tostring(EnumKey, encoding='unicode'))
  67.             # ***print('Address: ', address, 'EnumParent: ', key, 'bit: ', EnumBit, 'key: ',
  68.                  # EnumKey.text)  # ObdicEnum[key][EnumKey.text] to return positional bit
  69.     else:
  70.         if scale == ('bit vector'):
  71.             key = parent.find('Key').text
  72.             address = int(parent.find('Address').text)
  73.             ObdicAddress.update({key: address})
  74.             Bits = parent.findall('BitArray/Bit/Key')
  75.             for Bit, BitKey in enumerate(Bits):
  76.                 ObdicBit.update({key: {BitKey.text: Bit}})
  77.                 # print('BitParent: ', key, 'bit: ', Bit, 'key:', BitKey.text)
  78.                 # ****print('Address: ', address, 'BitParent: ', key, 'bit: ', Bit, 'key:', BitKey.text)
  79.                 # for Enum in EnumString:
  80.                 #    enu = Enumerations.find('string')
  81.                 #    ObdicEnum.update({key: {EnumBit: enu}})
  82.                 #    print('Key: ', key, 'Bit: ', EnumBit, 'String: ', enu)
  83.         else:
  84.             try: # Convert scale strings to integers where possible, floats where not
  85.                 scale = int(scale)
  86.             except ValueError:
  87.                 try:
  88.                     scale = float(scale)
  89.                 except ValueError:
  90.                     pass
  91.                     # print('Float scale error: ', scale, 'Scale type: ', type(scale))
  92.             key = parent.find('Key').text
  93.             address = int(parent.find('Address').text)
  94.             try:
  95.                 unit = parent.find('Units').text
  96.             except AttributeError:
  97.                 unit = 'None'
  98.             ObdicAddress.update({key: address})
  99.             ObdicUnit.update({key: unit})
  100.             ObdicScale.update({address: scale}) # originally key: scale, changed to Address for RegsRead;
  101.                                             # faster to avoid lookup of string for each address to lookup scale,
  102.                                             # than to just iterate through addresses.
  103.             print('Address: ', address, 'ValueParent: ', key, 'Unit: ', unit, 'Scale: ', scale)
  104.  
  105. # MODBUS I/O functions
  106. # Read MODBUS register address by input key
  107.  
  108. def RegRead(f1):
  109.     return (instr.read_register(int(ObdicAddress[f1]), 0) / float(ObdicScale[f1])) # 0 = no decimal scaling
  110. # print(RegRead('Maximum_Field_Weakening_Current'))
  111.  
  112. def RegsRead(f1, f2): # Consider unmapping from ObdicAddress? Will only be used a few times at specific address number ranges. Number may be preferable.
  113.     RegsList = (instr.read_registers(int(ObdicAddress[f1]), f2))
  114.     for c, d in enumerate(RegsList):
  115.         #return int(ObdicAddress[f1], d)
  116.         e = int(ObdicAddress[f1]) + c #MODBUS Address for each enumeration
  117.         f = (d/ObdicScale[e])
  118.         # print('Address: ', e, 'c: ', c, 'Scale: ', ObdicScale[e])
  119.         # print('Address: ', e, 'f: ', type(f), 'Scale: ', type(ObdicScale[e]))
  120.         RegsDic.update({e: f}) # Should return key address:value pairs
  121.         #print('Address: ', e, 'Value: ', d)
  122.  
  123. def RegWrite(f1, f2):
  124.     address = int(ObdicAddress[f1])
  125.     try:
  126.         instr.write_register(ObdicAddress[f1], f2)
  127.     except IOError:
  128.         print("Failed to write register of controller! (RegWrite)")
  129.     finally:
  130.         print('Address: ', address, 'Key: ', f1, 'Write value: ', f2)
  131.  
  132. def RegWriteScaled(f1, f2):
  133.     """Write MODBUS register address key/name (f1), for value f2 after scaling to value of that key"""
  134.     address = int(ObdicAddress[f1])
  135.     try:
  136.         instr.write_register(address, ObdicScale[(ObdicAddress[f1])] * f2)
  137.     except IOError:
  138.         print("Failed to write write register of controller! (RegWriteScaled)")
  139.     finally:
  140.         print('Address: ', address, 'Key:', f1, 'Write value: ', f2, 'Scale: ', ObdicScale[(ObdicAddress[f1])])
  141.  
  142. # Display Command function definitions
  143. # Serial context manager as class attempt
  144.  
  145. def ProfileWrite(f):
  146.     """Function to switch between profiles by integer"""
  147.     if f == 1:
  148.         RegWriteScaled('Maximum_Field_Weakening_Current', 50)
  149.         RegWriteScaled('Rated_Motor_Current', 450)
  150.         RegWriteScaled('Remote_Maximum_Battery_Current_Limit', 300)
  151.     else:
  152.         if f == 2:
  153.             RegWriteScaled('Maximum_Field_Weakening_Current', 0)
  154.             RegWriteScaled('Rated_Motor_Current', 220)
  155.             RegWriteScaled('Remote_Maximum_Battery_Current_Limit', 200)
  156.         else:
  157.             if f == 3:
  158.                 RegWriteScaled('Maximum_Field_Weakening_Current', 0)
  159.                 RegWriteScaled('Rated_Motor_Current', 220)
  160.                 RegWriteScaled('Remote_Maximum_Battery_Current_Limit', 15)
  161.  
  162. def ProfileSelect(f):
  163.     # a = f.cget('value')  # Get integer of attribute 'value' from button_profile1
  164.     # ProfileSelectVar = a
  165.     # print('Profile', a)
  166.     ProfileWrite(f.cget('value'))  # Enable to actually write value to profile
  167.  
  168. def AssistSelect(f):
  169.     # print('AssistScale variable: ', AssistSelectVar.get(), 'Type: ', type(AssistSelectVar.get()), 'f: ', f, 'ftype: ', type(f))
  170.     # scaleint = AssistSelectVar.get() + int(f)
  171.     # AssistScale.set(value = AssistSelectVar.get() + int(f))
  172.     AssistScale.set(int(f))
  173.     print("AssistScale:", f)
  174.     RegWrite('Remote_Assist_Mode', int(f))
  175.  
  176. ## TKINTER GUI init
  177. #Create main window
  178. root = tk.Tk()
  179. root.title("AmPy")
  180. root.configure(bg = 'white')
  181.  
  182. #root.geometry("1872x1404")
  183.  
  184. #Define font types
  185. BigFont = tkFont.Font(family='Arial Bold', size = '32', weight='bold')
  186. FrameFont = tkFont.Font(family='Helvetica', size = '16')
  187.  
  188. #Define Tk variables
  189. ProfileSelectVar = tk.IntVar(0)
  190. AssistSelectVar = tk.IntVar(0)
  191.  
  192. VehicleSpeedVar = tk.IntVar(0)
  193. MotorTempVar = tk.IntVar(0)
  194. MotorCurrentVar = tk.IntVar(0)
  195. BatteryVoltageVar = tk.IntVar(0)
  196. BatteryCurrentVar = tk.IntVar(0)
  197. BatterySOCVar = tk.IntVar(0)
  198. LastFaultVar = tk.IntVar(0)
  199.  
  200. # Profiles widget
  201. ProfileFrame = tk.LabelFrame(root, text="Global Profiles", font=FrameFont, padx=5, pady=5, relief='ridge',
  202.                              borderwidth=6, bg='white')
  203. ProfileFrame.pack(padx=10, pady=5)
  204.  
  205. # Convert to radio buttons!
  206. button_profile1 = tk.Radiobutton(ProfileFrame, text="1", bg='white', highlightcolor='white',
  207.                                  highlightbackground='black', padx=30, pady=25, font=BigFont,
  208.                                  indicatoron=0, value=1, variable='ProfileSelectVar',
  209.                                  command=lambda: ProfileSelect(button_profile1))
  210. button_profile2 = tk.Radiobutton(ProfileFrame, text="2", bg='white', highlightcolor='white',
  211.                                  highlightbackground='black', padx=30, pady=25, font=BigFont,
  212.                                  indicatoron=0, value=2, variable='ProfileSelectVar',
  213.                                  command=lambda: ProfileSelect(button_profile2))
  214. button_profile3 = tk.Radiobutton(ProfileFrame, text="3", bg='white', highlightcolor='white',
  215.                                  highlightbackground='black', padx=30, pady=25, font=BigFont,
  216.                                  indicatoron=0, value=3, variable='ProfileSelectVar',
  217.                                  command=lambda: ProfileSelect(button_profile3))
  218. button_profile1.pack(side="left")
  219. button_profile2.pack(side="left")
  220. button_profile3.pack(side="left")
  221. # When adding control functionality, create function to set active profile to 'sunken' border and others to 'raised'
  222.  
  223. # Digital assist widget
  224. AssistFrame = tk.LabelFrame(root, text="Assist Profiles", font=FrameFont, padx=5, pady=5, relief='ridge',
  225.                             borderwidth=6, bg='white')
  226.  
  227. # Digital assist scale widget
  228. AssistScaleY = 55
  229. AssistScaleX = 200  # Define all scale factors and group together up in #Setup
  230. AssistScale = tk.Scale(AssistFrame, font=BigFont, orient=tk.HORIZONTAL, from_=0, to=9,
  231.                        length=AssistScaleX, width=AssistScaleY, variable=AssistSelectVar,
  232.                        command=AssistSelect, bg='white', highlightbackground='white')
  233.  
  234. # Digital assist increment buttons (Look for image in this folder)
  235. DownbtnFilepath = os.path.abspath(os.path.join('', 'L.png'))
  236. Downbtn = tk.PhotoImage(file=DownbtnFilepath)
  237. UpbtnFilepath = os.path.abspath(os.path.join('', 'R.png'))
  238. Upbtn = tk.PhotoImage(file=UpbtnFilepath)
  239. AssistDownbtn = tk.Button(AssistFrame, image=Downbtn, border=0, bg='white',
  240.                           command=lambda: AssistScale.set(AssistScale.get() - 1))
  241. AssistUpbtn = tk.Button(AssistFrame, image=Upbtn, border=0, bg='white',
  242.                         command=lambda: AssistScale.set(AssistScale.get() + 1))
  243.  
  244. AssistDownbtn.pack(side='left', anchor=tk.S)
  245. AssistScale.pack(side='left')
  246. AssistUpbtn.pack(side='left', anchor=tk.S)
  247.  
  248. #Read fastloop/core variables to labelframe # Update with list and use for loops to generate... DRY!!!
  249. DisplayFrame = tk.LabelFrame(root, text="Fastloop Variables", font=FrameFont, padx=5, pady=5, relief='ridge',
  250.                              borderwidth=6, bg='white')
  251. VehicleSpeed = tk.Label(DisplayFrame, textvariable=VehicleSpeedVar, font=BigFont, padx=5, pady=5, bg='white')
  252. MotorTemp = tk.Label(DisplayFrame, textvariable=MotorTempVar, font=BigFont, padx=5, pady=5, bg='white')
  253. MotorCurrent = tk.Label(DisplayFrame, textvariable=MotorCurrentVar, font=BigFont, padx=5, pady=5, bg='white')
  254. BatteryVoltage = tk.Label(DisplayFrame, textvariable=BatteryVoltageVar, font=BigFont, padx=5, pady=5, bg='white')
  255. BatteryCurrent = tk.Label(DisplayFrame, textvariable=BatteryCurrentVar, font=BigFont, padx=5, pady=5, bg='white')
  256. BatterySOC = tk.Label(DisplayFrame, textvariable=BatterySOCVar, font=BigFont, padx=5, pady=5, bg='white')
  257. LastFault = tk.Label(DisplayFrame, textvariable=LastFaultVar, font=BigFont, padx=5, pady=5, bg='white')
  258.  
  259. VehicleSpeedLabel = tk.Label(DisplayFrame, text='VehicleSpeed: ', font=BigFont, padx=5, pady=5, bg='white')
  260. MotorTempLabel = tk.Label(DisplayFrame, text='MotorTemp', font=BigFont, padx=5, pady=5, bg='white')
  261. MotorCurrentLabel = tk.Label(DisplayFrame, text='MotorCurrent', font=BigFont, padx=5, pady=5, bg='white')
  262. BatteryVoltageLabel = tk.Label(DisplayFrame, text='BatteryVoltage', font=BigFont, padx=5, pady=5, bg='white')
  263. BatteryCurrentLabel = tk.Label(DisplayFrame, text='BatteryCurrent', font=BigFont, padx=5, pady=5, bg='white')
  264. BatterySOCLabel = tk.Label(DisplayFrame, text='BatterySOC', font=BigFont, padx=5, pady=5, bg='white')
  265. LastFaultLabel = tk.Label(DisplayFrame, text='LastFault', font=BigFont, padx=5, pady=5, bg='white')
  266.  
  267. VehicleSpeedUnits = tk.Label(DisplayFrame, text='Mph ', font=BigFont, pady=5, bg='white')
  268. MotorTempUnits = tk.Label(DisplayFrame, text='C', font=BigFont, pady=5, bg='white')
  269. MotorCurrentUnits = tk.Label(DisplayFrame, text='A', font=BigFont, pady=5, bg='white')
  270. BatteryVoltageUnits = tk.Label(DisplayFrame, text='V', font=BigFont, pady=5,bg='white')
  271. BatteryCurrentUnits = tk.Label(DisplayFrame, text='A', font=BigFont, pady=5, bg='white')
  272. BatterySOCUnits = tk.Label(DisplayFrame, text='%', font=BigFont, pady=5, bg='white')
  273.  
  274. VehicleSpeedLabel.grid(column=0, row=0)
  275. MotorTempLabel.grid(column=0, row=1)
  276. MotorCurrentLabel.grid(column=0, row=2)
  277. BatteryVoltageLabel.grid(column=0, row=3)
  278. BatteryCurrentLabel.grid(column=0, row=4)
  279. BatterySOCLabel.grid(column=0, row=5)
  280. LastFaultLabel.grid(column=0, row=6)
  281.  
  282. VehicleSpeed.grid(column=1, row=0)
  283. MotorTemp.grid(column=1, row=1)
  284. MotorCurrent.grid(column=1, row=2)
  285. BatteryVoltage.grid(column=1, row=3)
  286. BatteryCurrent.grid(column=1, row=4)
  287. BatterySOC.grid(column=1, row=5)
  288. LastFault.grid(column=1, row=6)
  289.  
  290.  
  291. VehicleSpeedUnits.grid(column=2, row=0)
  292. MotorTempUnits.grid(column=2, row=1)
  293. MotorCurrentUnits.grid(column=2, row=2)
  294. BatteryVoltageUnits.grid(column=2, row=3)
  295. BatteryCurrentUnits.grid(column=2, row=4)
  296. BatterySOCUnits.grid(column=2, row=5)
  297.  
  298. # Speed gauge
  299. #canvas.create_arc()
  300. # Fast loop refresher tasks
  301. def Fastloop():
  302.     RegsRead('Vehicle_Speed',8)
  303.     # print(RegsDic)
  304.     VehicleSpeedVar.set(int(round(0.621371*RegsDic[260], 0))) #Km/h to MPH conversion is 0.621371
  305.     MotorTempVar.set(RegsDic[261])
  306.     MotorCurrentVar.set(round(RegsDic[262], 1))
  307.     BatteryVoltageVar.set(round(RegsDic[265], 2))
  308.     BatteryCurrentVar.set(round(RegsDic[266], 1))
  309.     BatterySOCVar.set(int(RegsDic[267]))
  310.     #LastFaultVar.set(RegsDic[269]) #Read by itself, as this is Bit Vector register! Requires own dict {bit, string}
  311.     root.update()
  312.     time.sleep(0.005)
  313.     Fastloop()
  314.     #DisplayFrame.update()
  315. floop = threading.Thread(target=Fastloop)
  316.  
  317.  
  318. # Debug button
  319. #Debutton = tk.Button(root, text='Debug me!', font=FrameFont, command=floop.start())
  320.  
  321. # Lay out
  322. ProfileFrame.grid(column=0, row=0)
  323. # ProfileFrame.place(relx=0.5, rely=0.5, anchor = tk.CENTER)
  324. AssistFrame.grid(column=1, row=0)
  325. DisplayFrame.grid(column=0, row=1, columnspan=2)
  326. #Debutton.grid(column=2, row=0)
  327.  
  328. floop.start
  329. tk.mainloop()
Add Comment
Please, Sign In to add comment