usefbs=0
if usefbs:
from fbs_runtime.application_context.PyQt5 import ApplicationContext
from PyQt5.QtCore import QRunnable,pyqtSlot,QThreadPool,QMetaObject,Qt,Q_ARG,QTimer,QTime,QProcess,QItemSelection,QItemSelectionModel,QSignalBlocker,QRectF,pyqtSignal
from PyQt5.QtWidgets import (QMainWindow, QApplication, QWidget, QPushButton, QStackedLayout,QItemDelegate,
QSlider,
QHBoxLayout, QVBoxLayout, QGridLayout, QApplication, QAction,QAbstractItemView,
QTabWidget,QLineEdit,QLabel,QComboBox,QDialogButtonBox,QPlainTextEdit,QProgressBar,
QSizePolicy,QMessageBox,QTableWidget,QTableWidgetItem,QTextEdit,QHeaderView,QMenu)
from PyQt5.QtGui import QPalette,QColor,QTextCursor,QPainter,QFont
import csv
import numpy as np
import traceback
import sys,os,time,struct,math,itertools,json
import pyqtgraph as pg
from PC410Controller import PC410Controller
"""
class PlotDataset(object):
class PlotDataItem(GraphicsObject):
"""
class QCycleThread(QRunnable):
# Custom threader for running cycle() in pc410controller in a thread
def __init__(self, name, pc410controller):
QRunnable.__init__(self)
self.name=name
self.pc410controller=pc410controller
def run(self):
try:
self.pc410controller.running=1
print("Starting ct " + self.name)
self.pc410controller.cycle(self.pc410controller.tstart0)
print("Exiting ct " + self.name)
except:
print(traceback.format_exc())
finally:
self.pc410controller.running=0
def start(self):
QThreadPool.globalInstance().setObjectName(self.name)
QThreadPool.globalInstance().start(self)
def join(self):
pass
class QRunnableThread(QRunnable):
def __init__(self, name, parent):
QRunnable.__init__(self)
self.name=name
self.parent=parent
def run(self):
try:
self.parent.running=1
print("Starting ct " + self.name)
self.parent.cycle(self.parent.tstart0)
print("Exiting rt " + self.name)
except:
print(traceback.format_exc())
finally:
self.parent.running=0
def start(self):
QThreadPool.globalInstance().setObjectName(self.name)
QThreadPool.globalInstance().start(self)
def join(self):
pass
class ProcessRunnable(QRunnable):
def __init__(self, target, args=None,start=True):
QRunnable.__init__(self)
self.name="ProcessRunnable"
self.t = target
if args==None: args=()
self.args = args
if start: self.start()
def run(self):
try:
self.t(*self.args)
except:
print(traceback.format_exc())
finally:
print("exiting",self.t)
def start(self):
QThreadPool.globalInstance().setObjectName(self.name)
QThreadPool.globalInstance().start(self)
class TableView(QTableWidget):
def __init__(self, headers, data, *args):
QTableWidget.__init__(self, *args)
self.data = data
self.headers = headers
self.setData()
self.resizeColumnsToContents()
self.resizeRowsToContents()
self.cbSelectionChanged=None
self.cbCellChanged=None
self.cellChanged.connect(self.sCellChanged)
def sCellChanged(self,row,column):
if self.state()==QAbstractItemView.EditingState:
txt=self.item(row,column).text()
if self.cbCellChanged:
self.cbCellChanged(row,column,txt)
def selectionChanged(self,selected, deselected):
super().selectionChanged(selected,deselected)
indexes=selected.indexes()
dindexes=deselected.indexes()
rows=set([item.row() for item in indexes])
drows=set([item.row() for item in dindexes])
if rows:
row0=min(rows)
row1=max(rows)
if self.cbSelectionChanged:
self.cbSelectionChanged(row0,row1)
def setData(self):
horHeaders = []
for n, key in enumerate(self.headers):
horHeaders.append(key)
for m, item in enumerate(self.data[key]):
newitem = QTableWidgetItem(str(item))
self.setItem(m, n, newitem)
self.setHorizontalHeaderLabels(horHeaders)
class ValueSelectDelegate(QItemDelegate):
def __init__(self, parent=None,app=None):
QItemDelegate.__init__(self, parent)
self.app=app
def createEditor(self, parent, option, index):
model_value = index.model().data(index, Qt.DisplayRole)
editor = QComboBox(parent)
lines=[ x for x in model_value.split("\n") if x]
editor.addItems(lines)
return editor
def setEditorData(self, editor, index):
model_value = index.model().data(index, Qt.EditRole)
# must get value from actual data and select value matching in list
current_index = editor.findText(model_value)
if current_index > 0:
editor.setCurrentIndex(current_index)
def setModelData(self, editor, model, index):
editor_value = editor.currentText()
parts=editor_value.split("=")
if len(parts)>1:
value=parts[0].strip()
self.app.realTableCellChanged(index.row(),index.column(),value)
#print("setModelData",index.row(),index.column(),editor_value,model)
# model.setData(index, editor_value, QtCore.Qt.EditRole)
class SimplifiedPlotItemMovement(pg.PlotItem):
pointmoved = pyqtSignal([int,float,float],name="pointMoved")
def __init__(self):
pg.PlotItem.__init__(self)
# Initialize the class instance variables.
self.dragPoint = None
self.dragIndex = -1
self.dragOffset = 0
self.plot_item_control = None
def mouseDragEvent(self, ev):
# Check to make sure the button is the left mouse button. If not, ignore it.
# Would have to change this if porting to Mac I assume.
if ev.button() != Qt.MouseButton.LeftButton:
ev.ignore()
return
if ev.isStart():
# We are already one step into the drag.
# Find the point(s) at the mouse cursor when the button was first
# pressed:
# pos = ev.buttonDownPos()
pos = ev.buttonDownScenePos()
# Switch position into local coords using viewbox
local_pos = self.vb.mapSceneToView(pos)
for item in self.dataItems:
if hasattr(item,"scatter"):
new_pts = item.scatter.pointsAt(local_pos)
if len(new_pts) == 1:
# Store the drag point and the index of the point for future reference.
self.dragPoint = new_pts[0]
self.dragIndex = item.scatter.points().tolist().index(new_pts[0])
# Find the initial offset of the drag operation from the current point.
# This value should allow for the initial mismatch from cursor to point to be accounted for.
self.dragOffset = new_pts[0].pos() - local_pos
# If we get here, accept the event to prevent the screen from panning during the drag.
ev.accept()
elif ev.isFinish():
# The drag is finished. Reset the drag point and index.
self.dragPoint = None
self.dragIndex = -1
return
else:
# If we get here, this isn't the start or end. Somewhere in the middle.
if self.dragPoint is None:
# We aren't dragging a point so ignore the event and allow the panning to continue
ev.ignore()
return
else:
# We are dragging a point. Find the local position of the event.
local_pos = self.vb.mapSceneToView(ev.scenePos())
# Update the point in the PlotDataItem using get/set data.
# If we had more than one plotdataitem we would need to search/store which item
# is has a point being moved. For this example we know it is the plot_item_control object.
x,y = self.plot_item_control.getData()
# Be sure to add in the initial drag offset to each coordinate to account for the initial mismatch.
x[self.dragIndex] = local_pos.x() + self.dragOffset.x()
y[self.dragIndex] = local_pos.y() + self.dragOffset.y()
# Update the PlotDataItem (this will automatically update the graphics when a change is detected)
#self.plot_item_control.setData(x, y)
pos=local_pos.x(),local_pos.y()
self.pointmoved.emit(self.dragIndex,*pos)
class MainWindow(QMainWindow):
def __init__(self,ctx,argv):
super(MainWindow, self).__init__()
self.indicatorHeight=32
self.resultHeight=36
self.buttonHeight=43
self.cursorMoved=0
self.selectedRow=0
self.fontsize=12
self.infoColumns=2
self.clockHeight=70 # 100
self.progressCompact=1
self.buttonRows=1
self.withActionLabel=0
self.running=0
self.starttime=time.time()
self.lasttime=self.starttime
self.errorMessage=""
self.tfontsize=10
self.tfont = QFont('Courier', self.tfontsize) #, QFont.Bold)
self.mintemp=20.0
self.e={}
home=os.environ["HOME"]
if not home: raise Exeption("HOME environment variable not set!")
self.home=home
user=os.environ["USER"]
if not home: raise Exeption("USER environment variable not set!")
self.user=user
self.loadSettings()
self.build()
self.pc410controller=PC410Controller(QCycleThread,self.cmdlog,self.infocallback,self.statecallback,self.recordcallback)
self.pc410controller.run()
def loadSettings(self):
path=os.path.join(os.environ["HOME"],".pc410Settings")
self.settings={}
try:
self.settings=json.loads(open(path).read())
except:
print("exception reading and decoding",path)
fieldrows=self.settings.get("fieldrows",[])
if not fieldrows:
fieldrows=[["Sn63Pb37", 1,85,70, 1,150,35, 1,185,50, -0.01],
["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01],
]
self.settings["fieldrows"]=fieldrows
def saveSettings(self):
rowPosition = self.table.rowCount()
fieldrows=[]
for i in range(0,rowPosition):
name,program=self.getProgramParameters(i)
values=list(itertools.chain(*program))
fieldrows+=[[name]+values]
self.settings["fieldrows"]=fieldrows
path=os.path.join(os.environ["HOME"],".pc410Settings")
try:
open(path,"w").write(json.dumps(self.settings))
except:
print("exception writing settings",path)
raise
# PTN is which program
# STEP is which step of program...
def addButton(self,layout,name,fn,key=None,colour="white"):
button = QPushButton(self)
button.setText(name)
hmargin=(self.buttonHeight-self.fontsize)//2-5
button.setSizePolicy(
QSizePolicy.Preferred,
QSizePolicy.Expanding)
button.setContentsMargins(20,hmargin,20,hmargin)
#button.setStyleSheet("background-color: %s;"%colour)
#button.setStyleSheet("padding: 20px; background-color: %s;"%colour)
button.setStyleSheet("padding: %dpx; background-color: %s;"%(hmargin,colour))
#button.setGeometry(200, 150, 100, 40)
#button.setFont(QFont('Times', 15))
if not key: key=name
self.e[key]=(name,key,fn,button)
layout.addWidget(button)
button.clicked.connect(fn)
def saveAction(self):
self.saveSettings()
def stopAction(self):
self.pc410controller.cmdStop()
self.running=0
def runAction(self):
self.pc410controller.cmdRun()
self.running=1
self.clearRunRecord()
self.starttime=time.time()
def runningToStopped(self):
self.running=0
def stoppedToRunning(self):
self.running=1
self.clearRunRecord()
self.starttime=time.time()
def readAction(self):
program=self.pc410controller.readProgram()
print(program)
if program:
# update current row in table
smodel=self.table.selectionModel()
sindexes=smodel.selectedIndexes()
if sindexes:
rowPosition=max(set([s.row() for s in sindexes]))
else:
rowPosition = self.table.rowCount()
self.table.insertRow(rowPosition)
print(rowPosition)
name="read"
self.setProgramParameters(name,program,rowPosition)
def writeAction(self):
smodel=self.table.selectionModel()
sindexes=smodel.selectedIndexes()
if sindexes:
rowSet=set([s.row() for s in sindexes])
if len(rowSet)!=1:
# fail
return
rowPosition=min(rowSet)
name,program=self.getProgramParameters(rowPosition)
if program:
result=self.pc410controller.writeProgram(program)
if result!=0:
print("### failed to write program!!!")
def clearRunRecord(self):
self.runrecord=([],[])
def addRowAction(self):
self.addRow(-1)
def addRow(self,rowPosition=-1):
if rowPosition==-1:
smodel=self.table.selectionModel()
sindexes=smodel.selectedIndexes()
if sindexes:
rowPosition=max(set([s.row() for s in sindexes]))+1
else:
rowPosition = self.table.rowCount()
self.table.insertRow(rowPosition)
def removeRow(self,rowPosition):
if self.table.rowCount()<=1:
self.addRow(-1)
self.table.removeRow(rowPosition)
#@pyqtSlot(int,float,float)
def tableContextMenu(self,point):
index = self.table.indexAt(point)
if index.isValid() :
# show context menu
self.contextMenu = QMenu(self)
taddrowabove = self.contextMenu.addAction("Insert row above")
taddrowbelow = self.contextMenu.addAction("Insert row below")
self.contextMenu.addSeparator()
tdeleterow = self.contextMenu.addAction("Delete row")
# I want to perform actions only for a single column(E.g: Context menu only for column 4 of my table
# Need help here....???
action = self.contextMenu.exec_(self.table.mapToGlobal(point))
row0=index.row()
if action == taddrowabove:
self.addRow(row0)
elif action == taddrowbelow:
self.addRow(row0+1)
elif action == tdeleterow:
self.removeRow(row0)
def getTimeOfRun(self):
dt=self.lasttime-self.starttime
def showTime(self):
if self.running:
self.lasttime=time.time()
dt=self.lasttime-self.starttime
h=int((dt/3600)%100)
m=int((dt/60)%60)
s=int(dt%60)
label_time="%02d:%02d:%02d"%(h,m,s)
#current_time = QTime.currentTime()
#label_time = current_time.toString('hh:mm:ss')
self.clocklabel.setText(label_time)
def build(self):
if 1:
font = QFont('Arial', self.fontsize) #, QFont.Bold)
self.setFont(font)
toplayout = QHBoxLayout()
layout = QVBoxLayout()
toplayout.setContentsMargins(0,0,0,0)
layout.setContentsMargins(0,0,0,0)
toplayout.addLayout(layout)
layoutside = QVBoxLayout()
toplayout.addLayout(layoutside)
self.addButton(layoutside,"Save",self.saveAction,"action-save",colour="green")
self.addButton(layoutside,"Add row",self.addRowAction,"action-addrow",colour="cyan")
self.addButton(layoutside,"Run",self.runAction,"action-run",colour="yellow")
self.addButton(layoutside,"Stop",self.stopAction,"action-stop",colour="red")
self.addButton(layoutside,"Read program",self.readAction,"action-readprogram",colour="blue")
self.addButton(layoutside,"Write program",self.writeAction,"action-writeprogram",colour="green")
l=QLabel("Temperature over time")
layout.addWidget(l)
self.plot_graph = pg.GraphicsLayoutWidget()
#self.plot_graph = pg.PlotWidget()
self.plot_graph.setBackground("w")
self.lineplot=SimplifiedPlotItemMovement()
self.plot_graph.addItem(self.lineplot)
self.lineplot.pointMoved.connect(self.graphPointMoved)
self.plottexts=[]
if 1:
self.scatter = pg.ScatterPlotItem(size=2, brush=pg.mkBrush(255, 0, 0, 0))
#plot_item = self.plot_graph.addPlot()
#scatter_item = pg.ScatterPlotItem()
self.lineplot.addItem(self.scatter)
#self.plot_graph.addItem(self.scatter)
else:
self.recordplot = pg.PlotCurveItem(size=2, pen=pg.mkPen(color='#ff0000', width=1))
self.plot_graph.addItem(self.recordplot)
self.clearRunRecord()
self.lineplot.setTitle('Temperature vs Time')
self.lineplot.setLabel("left", "Temperature (°C)")
self.lineplot.setLabel("bottom", "Time (seconds)")
self.timer = QTimer(self)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
hsize=self.clockHeight-5*2
font = QFont('Arial', hsize, QFont.Bold)
self.clocklabel = QLabel()
self.clocklabel.setAlignment(Qt.AlignCenter)
self.clocklabel.setFont(font)
self.clocklabel.setContentsMargins(5,5,5,5)
layoutside.addWidget(self.clocklabel)
info=QGridLayout()
infoslots={}
def addInfoSlot(infoslots,name,label=None):
if not label: label=name
row=len(infoslots)
l=QLabel(label)
e=QLabel()
infoslots[name]=(name,l,e)
info.addWidget(l,row,0)
info.addWidget(e,row,1)
def addInfoSlot2D(infoslots,name,label=None):
if not label: label=name
count=len(infoslots)
row=count//2
col=count%2
l=QLabel(label)
e=QLabel()
infoslots[name]=(name,l,e)
info.addWidget(l,row,col*2+0)
info.addWidget(e,row,col*2+1)
faddinfoslot=addInfoSlot
if self.infoColumns==2: faddinfoslot=addInfoSlot2D
for param in ["PV","OP","SP","SL","HA","LA","DA","XP","TI","TD","HB","LB","CH","CC","RG","HS","LS","BP","HO","SR","Lc","SW","XS","OS"]:
# "HB",
faddinfoslot(infoslots,param)
layoutside.addLayout(info)
self.infoslots=infoslots
layout.addWidget(self.plot_graph)
if 1:
l=QLabel("Settings")
layout.addWidget(l)
if 0:
hbuttons = QHBoxLayout()
self.addButton(hbuttons,"Add row",self.addRow,"action-addrow",colour="cyan")
layout.addLayout(hbuttons)
"""
Lead Sn63Pb37
step r L d
1 1 85 70
2 1 150 35
3 1 185 50
4 END Hb=230
Lead-Free Sn965.5Ag3Cu0.5
step r L d
1 1 85 60
2 1 140 45
3 1 170 25
3 1 220 50
5 END hb=230
"""
fieldrows=[["Sn63Pb37", 1,85,70, 1,150,35, 1,185,50, -0.01],
["Sn965.5Ag3Cu0.5", 1,85,60, 1,140,45, 1,170,25, 1,220,50, -0.01],
]
fieldrows=self.settings.get("fieldrows",fieldrows)
headers=["Name"]
for i in range(1,9):
headers+=["r%d"%i,"l%d"%i,"t%d"%i]
data={}
for i in range(0,len(headers)):
data[headers[i]]=[]
for row in fieldrows:
fieldrow={}
for ki in range(0,len(headers)):
k=headers[ki]
if ki>=len(row): v=""
else: v=row[ki]
data[k]+=[v]
#data[self.decoded]+=[0]
table = TableView(headers,data,len(data[headers[0]]),len(headers))
self.table=table
table.setContentsMargins(0,0,0,0)
table.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
table.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
table.setSelectionBehavior(QAbstractItemView.SelectRows);
table.cbSelectionChanged=self.tableSelectionChanged
table.cbCellChanged=self.tableSelectionChanged #CellChanged
def tableClicked(*args):
item=args[0]
row=item.row()
table.clicked.connect(tableClicked)
# setting context menu policy on my table, "self.table"
self.table.setContextMenuPolicy(Qt.CustomContextMenu)
# setting context menu request by calling a function,"self.tableContextMenu"
self.table.customContextMenuRequested.connect(self.tableContextMenu)
table.show()
table.resize(250, 150)
layout.addWidget(self.table)
if layout:
widget = QWidget()
widget.setLayout(toplayout)
self.setCentralWidget(widget)
@pyqtSlot(int,float,float)
def graphPointMoved(self,index,x,y):
if index!=0:
row0=self.selectedRow
time,temp=self.buildCoordinates(row0)
"""
moving time of point 1,3,5 etc means changing rate and adding diff to dwell time
moving time of point 2,4,6 etc means changing dwell time, and changing rate of the one after
"""
step=(index-1)//2
stepi=1+step*3
rgi0=stepi+0
lgi0=stepi+1
tgi0=stepi+2
if (index%2)==0:
# 0,2,4 etc
# change rate of next to compensate for time change of this
# should cap but that time is constructed from rate and delta so max x would then be
# time[index]+(temp[index+1]-temp[index])/rate
# cap to minx and mintemp
rate0=float(self.table.item(row0,rgi0).text())
dy0=(y-temp[index-2])
minx=time[index-2]
if rate0: minx+=abs(dy0)/rate0
x=max(minx,x)
y=max(self.mintemp,y)
if index+1