Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
40cfcd2
Use QThread
M-Chan Jun 19, 2026
59cfaf2
using 'worker' instead of 'commsThread'
M-Chan Jun 22, 2026
6cc7d7e
ruff formatting
M-Chan Jun 23, 2026
e19d952
fixes disable/enable polling
M-Chan Jun 23, 2026
e4ffe1c
removed moved code
M-Chan Jun 23, 2026
01b390e
fixed sendSeries
M-Chan Jun 23, 2026
5c690a5
use 'worker'
M-Chan Jun 23, 2026
1341921
fix function naming for ruff
M-Chan Jun 23, 2026
86604f3
attempt to safely delete threads and stop timers
M-Chan Jun 25, 2026
314dde6
using 'worker'
M-Chan Jun 25, 2026
553e679
use pyqt slots and signals to manage threads and timers
M-Chan Jun 29, 2026
3ef5cdc
fix threading and remove obsolete code + comments
M-Chan Jun 30, 2026
f72946b
add dataclasses for polling statuses
M-Chan Jul 8, 2026
5fddac0
uses dataclasses for polling status
M-Chan Jul 8, 2026
20a4cea
uses dataclasses for polling status
M-Chan Jul 9, 2026
d3a8faa
uses dataclasses for polling status
M-Chan Jul 9, 2026
90a68d4
clean up imports
M-Chan Jul 9, 2026
09ca129
uses dataclasses for polling status
M-Chan Jul 9, 2026
51f95d3
uses dataclasses for polling status
M-Chan Jul 9, 2026
e7cdd51
add the CoordinateSystemStatus
M-Chan Jul 9, 2026
c960c23
pass the gathered status
M-Chan Jul 13, 2026
e35c1af
receive the status
M-Chan Jul 13, 2026
0d0da76
use ip of geobrick 3 in lab18
M-Chan Jul 13, 2026
969296c
add individual motor_status
M-Chan Jul 13, 2026
dc7edd4
use status dataclass values to update screen
M-Chan Jul 13, 2026
90e109b
comment out unused code
M-Chan Jul 13, 2026
1492d5e
add back watch window and queue
M-Chan Jul 14, 2026
0f558fe
delete unused code
M-Chan Jul 14, 2026
ae22bce
add commented-out old update_func function for reference
M-Chan Jul 22, 2026
28d9e01
Merge branch 'new-main' into dataclasses
M-Chan Aug 5, 2026
d02fc4e
tidy up comments and namings
M-Chan Aug 5, 2026
8d942d8
Delete repeated code
M-Chan Aug 6, 2026
b25430b
add test geobrick ip to launch.json
M-Chan Aug 11, 2026
5b672a6
restore default ip
M-Chan Aug 11, 2026
7c020e1
strip $ for ppmacs and make global_status of type str
M-Chan Aug 11, 2026
01aff5a
revert back to commsThread
M-Chan Aug 14, 2026
cf95da5
plural dataclasses and add both amplifier_statuses to account for ppmacs
M-Chan Aug 14, 2026
ef378d7
do velocity calculation beforehand and call setText once
M-Chan Aug 14, 2026
8a4253f
change motor_row to motor and call i2t_fault_status instead of amplif…
M-Chan Aug 14, 2026
bd85742
separate out sendCommand
M-Chan Aug 14, 2026
46e51a1
add back the pollrate calculation
M-Chan Aug 14, 2026
dee4846
fix typo and hence function
M-Chan Aug 19, 2026
3578028
move i65 to 'global' ControllerStatus
M-Chan Aug 19, 2026
332222d
do the success validation earlier at the sendCommand stage
M-Chan Aug 19, 2026
4fc20aa
keep velocity as a numerical value before it is set as a text
M-Chan Aug 19, 2026
e77fdd3
remove print statements
M-Chan Aug 25, 2026
65a05a3
only update if success; else print error
M-Chan Sep 1, 2026
a8274dd
no longer return ControllerStatus
M-Chan Sep 2, 2026
8676498
clean up dataclasses
M-Chan Sep 4, 2026
9b055c1
add pmac variables undervolt, overvolt and overtemp
M-Chan Sep 4, 2026
1626a3b
don't fix it at 6 variables for motors response just in case
M-Chan Sep 4, 2026
40c16d6
don't forget overcurrent value
M-Chan Sep 4, 2026
274268a
fix overcurrent value
M-Chan Sep 4, 2026
462c533
only powerpmacs give overcurrent values
M-Chan Sep 4, 2026
bc35b89
keep it as a bool
M-Chan Sep 4, 2026
1e89460
account for the undervolt, overvolt and overtemp for ppmacs
M-Chan Sep 4, 2026
ff41d6a
move undervolt, overvolt and overtemp to be global values
M-Chan Sep 4, 2026
ea12a72
there'll always be an overcurrent value given
M-Chan Sep 4, 2026
f2df445
unpackage the status for the geobrick whilst parsing the polled response
M-Chan Sep 11, 2026
dc7e01b
add typehinting for motors and use enumerate for motor_no
M-Chan Sep 15, 2026
f6d9b1a
use bus_under_voltage, bus_over_voltage and over_temp direct from sta…
M-Chan Sep 15, 2026
dc12c87
use overcurrent and i2t_fault_status direct from motors dataclass
M-Chan Sep 15, 2026
af11586
remove commented out code
M-Chan Sep 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@
"name": "DLS PMAC Control",
"type": "debugpy",
"request": "launch",
"module": "dls_pmac_control"
"module": "dls_pmac_control",
"args": [
"-o",
"tcpip",
"-s",
"172.23.171.103",
"-p",
"1025"
],
},
{
"name": "Debug Unit Test",
Expand Down
282 changes: 102 additions & 180 deletions src/dls_pmac_control/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from dls_pmac_control.login import Loginform
from dls_pmac_control.ppmacgather import PpmacGatherform
from dls_pmac_control.status import PpmacStatusform, Statusform
from dls_pmac_control.status_dataclasses import ControllerStatus
from dls_pmac_control.ui_form_control import UiControlForm
from dls_pmac_control.watches import Watchesform

Expand Down Expand Up @@ -137,6 +138,8 @@ def __init__(self, options, parent=None):
)

self.comms_thread.started.connect(self.comms_worker.start)
self.comms_worker.update_received.connect(self.start_updating_motors)
self.comms_worker.watches_ready.connect(self.update_watches)

self.comms_worker.finished.connect(self.comms_thread.quit)
self.comms_thread.finished.connect(self.comms_worker.deleteLater)
Expand Down Expand Up @@ -669,195 +672,117 @@ def add_to_txt_shell(self, command, ret_str=None, chk_show_all=True):
ret_str.rstrip("\x06").lstrip("\x07").replace("\r", " ")
)

# Called when an event comes out of the polling thread
# and the jog ribbon.
def update_motors(self):
under_voltage = False
over_voltage = False
over_temperature = False
def start_updating_motors(self, status: ControllerStatus):

self.comms_worker.resultQueue.qsize()
for _que_item in range(0, self.comms_worker.resultQueue.qsize()):
try:
value = self.comms_worker.resultQueue.get(False)
except Empty:
return
try:
self.update_identity(status.identifier_i65)
self.PpmacGlobalStatusScreen.update_status(
Comment thread
M-Chan marked this conversation as resolved.
int(status.global_status.strip("$"), 16)
)
self.PpmacCSStatusScreen.update_status(
int(status.coordinate_systems.cs_status.strip("$"), 16)
)
self.PpmacCSStatusScreen.update_feed(
int(round(float(status.coordinate_systems.feedrate)))
)

try:
motor_row = value[6]
# check for special cases
if isinstance(motor_row, str):
if isinstance(self.pmac, PPmacSshInterface):
if motor_row == "G":
self.PpmacGlobalStatusScreen.update_status(
int(value[0].strip("$"), 16)
)
continue
if motor_row.startswith("CS"):
self.PpmacCSStatusScreen.update_status(
int(value[0].strip("$"), 16)
)
continue
if motor_row.startswith("FEED"):
self.PpmacCSStatusScreen.update_feed(
int(round(float(value[0])))
)
continue
if motor_row == "IDENT":
self.update_identity(int(value[0]))
continue
if motor_row == "UVOL":
if int(value[0]) != 0:
under_voltage = True
continue
if motor_row == "OVOL":
if int(value[0]) != 0:
over_voltage = True
continue
if motor_row == "OTEMP":
if int(value[0]) != 0:
over_temperature = True
continue
else:
if motor_row == "G":
self.global_status_screen.update_status(int(value[0], 16))
continue
if motor_row.startswith("CS"):
self.cs_status_screen.update_status(int(value[0], 16))
continue
if motor_row.startswith("FEED"):
self.cs_status_screen.update_feed(
int(round(float(value[0])))
)
continue
if motor_row == "IDENT":
self.update_identity(int(value[0]))
continue
for motor in status.motors:
if isinstance(self.pmac, PPmacSshInterface):
velocity = motor.velocity
else:
velocity = round(float(motor.velocity) * self.servoCycleTime, 1)

self.__item(motor.number - 1, 0).setText(str(motor.position))
self.__item(motor.number - 1, 1).setText(str(velocity))
self.__item(motor.number - 1, 2).setText(str(motor.following_error))

status_word = int(motor.motor_status.strip("$"), 16)

# define high and low limits for power pmac
if isinstance(self.pmac, PPmacSshInterface):
lo_lim = bool(status_word & 0x2000000000000000) # MinusLimit
hi_lim = bool(status_word & 0x1000000000000000) # PlusLimit
lo_lim_soft = bool(
status_word & 0x0080000000000000
) # SoftMinusLimit
hi_lim_soft = bool(
status_word & 0x0040000000000000
) # SoftPlusLimit

# define high and low limits for pmac
else:
lo_lim = bool(
status_word & 0x400000000000
) # negative end limit set
hi_lim = bool(
status_word & 0x200000000000
) # positive end limit set
lo_lim_soft = False
hi_lim_soft = False

# set limit indicators in polling table
if hi_lim:
self.__item(motor.number - 1, 3).setIcon(QIcon(self.redLedOn))
elif hi_lim_soft:
self.__item(motor.number - 1, 3).setIcon(QIcon(self.amberLedOn))
else:
self.__item(motor.number - 1, 3).setIcon(QIcon(self.redLedOff))
if lo_lim:
self.__item(motor.number - 1, 4).setIcon(QIcon(self.redLedOn))
elif lo_lim_soft:
self.__item(motor.number - 1, 4).setIcon(QIcon(self.amberLedOn))
else:
self.__item(motor.number - 1, 4).setIcon(QIcon(self.redLedOff))

# set amplifier status indicators in polling table
if motor.i2t_fault_status:
self.__item(motor.number - 1, 5).setIcon(QIcon(self.redLedOn))
else:
position = str(round(float(value[1]), 1))
if isinstance(self.pmac, PPmacSshInterface):
velocity = str(round(float(value[2]), 1))
else:
# On Turbo PMAC velocity is returned in counts per servo cycle
# so you have to use the servo cycle time to convert it to cts/msec
velocity = str(round(float(value[2]) * self.servoCycleTime, 1))
folerr = str(round(float(value[3]), 1))

i2t_fault = False
over_current = False

if motor_row < 8:
if isinstance(self.pmac, PPmacSshInterface):
if int(value[4]) > 0:
i2t_fault = True
if int(value[5]) > 0:
over_current = True
elif isinstance(self.pmac, PmacEthernetInterface):
amp_status = (int(value[4]) & 448) >> 6
if amp_status == 5:
i2t_fault = True
elif amp_status == 6:
over_current = True
if motor_row < 4:
if amp_status == 2:
under_voltage = True
elif amp_status == 3:
over_temperature = True
elif amp_status == 4:
over_voltage = True

self.__item(motor_row, 0).setText(position)
self.__item(motor_row, 1).setText(velocity)
self.__item(motor_row, 2).setText(folerr)

status_word = int(value[0].strip("$"), 16)

# define high and low limits for power pmac
if isinstance(self.pmac, PPmacSshInterface):
lo_lim = bool(status_word & 0x2000000000000000) # MinusLimit
hi_lim = bool(status_word & 0x1000000000000000) # PlusLimit
lo_lim_soft = bool(
status_word & 0x0080000000000000
) # SoftMinusLimit
hi_lim_soft = bool(
status_word & 0x0040000000000000
) # SoftPlusLimit

# define high and low limits for pmac
else:
lo_lim = bool(
status_word & 0x400000000000
) # negative end limit set
hi_lim = bool(
status_word & 0x200000000000
) # positive end limit set
lo_lim_soft = False
hi_lim_soft = False

# set limit indicators in polling table
self.__item(motor.number - 1, 5).setIcon(QIcon(self.redLedOff))
if motor.overcurrent:
self.__item(motor.number - 1, 6).setIcon(QIcon(self.redLedOn))
else:
self.__item(motor.number - 1, 6).setIcon(QIcon(self.redLedOff))

# Update also the jog ribbon
if motor.number == self.currentMotor:
self.lblPosition.setText(str(motor.position))
self.lblVelo.setText(str(motor.velocity))
self.lblFolErr.setText(str(motor.following_error))
if hi_lim:
self.__item(motor_row, 3).setIcon(QIcon(self.redLedOn))
self.pixHiLim.setPixmap(self.redLedOn)
elif hi_lim_soft:
self.__item(motor_row, 3).setIcon(QIcon(self.amberLedOn))
self.pixHiLim.setPixmap(self.amberLedOn)
else:
self.__item(motor_row, 3).setIcon(QIcon(self.redLedOff))
self.pixHiLim.setPixmap(self.redLedOff)
if lo_lim:
self.__item(motor_row, 4).setIcon(QIcon(self.redLedOn))
self.pixLoLim.setPixmap(self.redLedOn)
elif lo_lim_soft:
self.__item(motor_row, 4).setIcon(QIcon(self.amberLedOn))
self.pixLoLim.setPixmap(self.amberLedOn)
else:
self.__item(motor_row, 4).setIcon(QIcon(self.redLedOff))
self.pixLoLim.setPixmap(self.redLedOff)
self.status_screen.update_status(status_word)
self.ppmacstatusScreen.update_status(status_word)

# set amplifier status indicators in polling table
if i2t_fault:
self.__item(motor_row, 5).setIcon(QIcon(self.redLedOn))
else:
self.__item(motor_row, 5).setIcon(QIcon(self.redLedOff))
if over_current:
self.__item(motor_row, 6).setIcon(QIcon(self.redLedOn))
else:
self.__item(motor_row, 6).setIcon(QIcon(self.redLedOff))

# Update also the jog ribbon
if motor_row + 1 == self.currentMotor:
self.lblPosition.setText(position)
self.lblVelo.setText(velocity)
self.lblFolErr.setText(folerr)
if hi_lim:
self.pixHiLim.setPixmap(self.redLedOn)
elif hi_lim_soft:
self.pixHiLim.setPixmap(self.amberLedOn)
else:
self.pixHiLim.setPixmap(self.redLedOff)
if lo_lim:
self.pixLoLim.setPixmap(self.redLedOn)
elif lo_lim_soft:
self.pixLoLim.setPixmap(self.amberLedOn)
else:
self.pixLoLim.setPixmap(self.redLedOff)
self.status_screen.update_status(status_word)
self.ppmacstatusScreen.update_status(status_word)

# set controller status indicators on main window
if under_voltage:
self.pixUnderVoltage.setPixmap(self.redLedOn)
else:
self.pixUnderVoltage.setPixmap(self.redLedOff)
if over_voltage:
self.pixOverVoltage.setPixmap(self.redLedOn)
else:
self.pixOverVoltage.setPixmap(self.redLedOff)
if over_temperature:
self.pixOverTemperature.setPixmap(self.redLedOn)
else:
self.pixOverTemperature.setPixmap(self.redLedOff)
# set controller status indicators on main window
if status.bus_under_voltage:
self.pixUnderVoltage.setPixmap(self.redLedOn)
else:
self.pixUnderVoltage.setPixmap(self.redLedOff)
if status.bus_over_voltage:
self.pixOverVoltage.setPixmap(self.redLedOn)
else:
self.pixOverVoltage.setPixmap(self.redLedOff)
if status.over_temp:
self.pixOverTemperature.setPixmap(self.redLedOn)
else:
self.pixOverTemperature.setPixmap(self.redLedOff)

except (ValueError, IndexError):
# Catch the exception and continue, since there may be other
# updates waiting in the queue.
if self.verboseMode:
print("Update request received invalid response: ", value)
except (ValueError, IndexError):
# Catch the exception and continue, since there may be other
# updates waiting in the queue.
if self.verboseMode:
print(f"Update request received invalid response: {status}")

domain_names = [
"BL",
Expand Down Expand Up @@ -938,9 +863,6 @@ def customEvent(self, E):
elif E.type() == self.downloadDoneEventType:
self.progressDialog.setValue(self.progressDialog.maximum())
self.txtShell.append(str(E.data()))
elif E.type() == self.updatesReadyEventType:
self.update_motors()
self.update_watches()

def signal_handler(self, signum, frame):
if signum == 2: # SIGINT
Expand Down
Loading
Loading