First commit, working skeleton

Tested to perform smoothly over a 'ssh -XYC' connection. Hard-coded /dev/ttyUSBn location, ideally it isn't changed too often for a given setup.
This commit is contained in:
Sudarsan Balakrishnan 2026-02-06 14:17:59 -05:00
parent 1204dd6c4d
commit ad4bbf52e8

138
CrateControl.py Normal file
View File

@ -0,0 +1,138 @@
'''
v0.1 notes
---------
Relies on /dev/ttyUSB<N> channel being available to the user. Do it by running the command
sudo adduser <username> dialout
and logging out, then logging back in. Look at the output of ls /dev/ttyUSB* on a terminal before
and after plugging in the USB cable to the PC, to find out which channel to use in the input below.
'''
import serial
import time
import tkinter as tk
import ttkbootstrap as ttk
from ttkbootstrap.constants import *
port_name = '/dev/ttyUSB0'
baud_rate = 9600
timeout = 2
commands = {
"turn on": "$CMD:SET,CH:8,PAR:ON\n".encode('utf-8'),
"turn off": "$CMD:SET,CH:8,PAR:OFF\n".encode('utf-8'),
"get name": "$CMD:MON,CH:8,PAR:CRNAME\n".encode('utf-8')
}
class CrateControl(ttk.Frame):
def __init__(self,master):
super().__init__(master, padding=20)
try:
self.ser = serial.Serial(port=port_name,baudrate=baud_rate,timeout=timeout)
except serial.SerialException as e:
print(f"Error opening serial port: {e}")
self.destroy()
quit()
except Exception as e:
print(f"An unexpected error occurred: {e}")
self.destroy()
quit()
print(self.ser)
self.freeze_frame=False
self.last_return_string=""
self.style = ttk.Style()
self.style.configure('big.success-round-toggle.TCheckbutton',font=('Verdana',18),indicatorsize=40,padding=10)
self.name_of_crate = self.poll(8,"CRNAME","")
self.onoff_status = tk.IntVar(value=0)
self.status_label = ttk.Label(self, text="Ready",bootstyle="info",justify=LEFT)
self.onoff_button = ttk.Checkbutton(self,
text=self.name_of_crate,
variable=self.onoff_status,
onvalue=1,
offvalue=0,
width=20,
#style='success-round-toggle.Checkbutton',
command=self.toggle_onoff,
bootstyle='big success-round-toggle')
self.status_label.pack(expand=YES,fill=X,pady=10)
self.onoff_button.pack(expand=YES,fill=X,pady=10)
self.refresh_monitors()
#print('test')
#self.after(200,self.eventloop)
#self.refresh_monitors()
def poll(self, channel, polstring,replystring=""):
#while(self.ser.in_waiting > 0):
# self.ser.readline()
self.ser.write(str("$CMD:MON,CH:"+str(channel)+",PAR:"+polstring+"\n").encode('utf-8'))
retval = self.ser.readline().decode("utf-8").rstrip()
#retval = self.read_reply()
retval = retval.replace("#CMD:OK,VAL:","")
return "%s\t%s\t"%(retval,replystring)
#return self.read_reply()
def refresh_monitors(self):
#fullstatus="name: "
fullstatus="name\t\tset1\t\tmon1\t\tmon2\n"
for i in range(0,3):
reply=""
name = self.poll(i,"NAME")
setv = self.poll(i,"VSET","V")
monv = self.poll(i,"VMON","V")
moni = self.poll(i,"IMON","A")
#reply = name #.replace("#CMD:OK,VAL:"," ")
#reply = reply+"\n"
fullstatus += (name+setv+monv+moni+"\n")
fullstatus+="\t"
fullstatus+=self.poll(8,"FAN1","rpm\n")
fullstatus+=self.poll(8,"FAN2","rpm\n")
fullstatus+=self.poll(8,"FAN3","rpm\n")
fullstatus+=self.poll(8,"FUTEMP","C\n")
fullstatus+=self.poll(8,"PSTEMP","C\n")
status = self.poll(8,"CRST","\n")
fanstat = (int(status)&0x200==512)
crstat = int(status)&0x1
fullstatus+=("fan: %d\t crate: %d"%(fanstat,crstat))
if(crstat == 1 and fanstat == 1 and self.onoff_status.get() == 0):
self.onoff_status.set(1)
self.onoff_button.state(['selected'])
while(self.freeze_frame):
fullstatus = fullstatus
#print(fullstatus,self.freeze_frame)
self.status_label.config(text=fullstatus, bootstyle="info")
self.after(4000,self.refresh_monitors)
def read_reply(self):
totresp = ""
while(self.ser.in_waiting > 0):
response = self.ser.readline().decode('utf-8').rstrip() #rstrip gets rid of trailing whitespace
totresp = totresp+response
self.last_return_string=totresp
return totresp
def toggle_onoff(self):
self.freeze_frame=True
if(self.onoff_status.get()==1):
print("Turn ON")
self.ser.write(commands['turn on'])
time.sleep(0.2)
elif(self.onoff_status.get()==0):
print("Turn OFF")
self.ser.write(commands['turn off'])
time.sleep(0.2)
#print(self.read_reply())
while(self.ser.in_waiting > 0):
self.ser.readline()
self.freeze_frame=False
if __name__ == "__main__":
root = ttk.Window(themename='flatly',title="CrateControl")
app = CrateControl(root) #ttk.Window("Crate Control")
app.pack(expand=True,fill=BOTH)
root.mainloop()