565 lines
21 KiB
Python
565 lines
21 KiB
Python
#!/usr/bin/env python3
|
|
# Qtvcp widget
|
|
#
|
|
# Copyright (c) 2017 Chris Morley <chrisinnanaimo@hotmail.com>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
#################################################################################
|
|
|
|
from qtpy.QtCore import Property
|
|
import hal
|
|
from qtvcp.widgets.led_widget import LED
|
|
from qtvcp.core import Status
|
|
from qtvcp import logger
|
|
|
|
# Instantiate the libraries with global reference
|
|
# STATUS gives us status messages from linuxcnc
|
|
# LOG is for running code logging
|
|
STATUS = Status()
|
|
LOG = logger.getLogger(__name__)
|
|
|
|
# Force the log level for this module
|
|
# LOG.setLevel(logger.INFO) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL
|
|
|
|
|
|
class StateLED(LED):
|
|
'''
|
|
|
|
Widget to display a colored LED based on a linuxcnc state.
|
|
'''
|
|
|
|
def __init__(self, parent=None):
|
|
super(StateLED, self).__init__(parent)
|
|
self.setState(False)
|
|
|
|
self.is_estopped = False
|
|
self.is_on = False
|
|
self.is_homed = False
|
|
self.is_idle = False
|
|
self.is_paused = False
|
|
self.invert_state = False
|
|
self.is_flood = False
|
|
self.is_mist = False
|
|
self.is_block_delete = False
|
|
self.is_optional_stop = False
|
|
self.is_joint_homed = False
|
|
self.is_limits_overridden = False
|
|
self.is_manual = False
|
|
self.is_mdi = False
|
|
self.is_auto = False
|
|
self.is_spindle_stopped = False
|
|
self.is_spindle_fwd = False
|
|
self.is_spindle_rev = False
|
|
self.is_spindle_at_speed = False
|
|
self.is_neg_limit_tripped = False
|
|
self.is_pos_limit_tripped = False
|
|
self.is_limits_tripped = False
|
|
self._halpin_option = True
|
|
self._follow_halpin = False
|
|
|
|
self.joint_number = 0
|
|
self._halpin_name = ''
|
|
|
|
self._override = 1
|
|
self._at_speed_percent = .1
|
|
|
|
def _hal_init(self):
|
|
def only_false(data):
|
|
if data:
|
|
return
|
|
self._flip_state(False)
|
|
# optional output HAL pin reflecting state
|
|
if self._halpin_option:
|
|
if self._halpin_name == '':
|
|
pname = self.HAL_NAME_
|
|
else:
|
|
pname = self._halpin_name
|
|
self.hal_pin = self.HAL_GCOMP_.newpin(pname, hal.HAL_BIT, hal.HAL_OUT)
|
|
if self.is_estopped:
|
|
STATUS.connect('state-estop', lambda w: self._flip_state(True))
|
|
STATUS.connect('state-estop-reset', lambda w: self._flip_state(False))
|
|
elif self.is_on:
|
|
STATUS.connect('state-on', lambda w: self._flip_state(True))
|
|
STATUS.connect('state-off', lambda w: self._flip_state(False))
|
|
elif self.is_homed:
|
|
STATUS.connect('all-homed', lambda w: self._flip_state(True))
|
|
STATUS.connect('not-all-homed', lambda w, axis: self._flip_state(False))
|
|
elif self.is_idle:
|
|
STATUS.connect('interp-idle', lambda w: self._flip_state(True))
|
|
STATUS.connect('interp-run', lambda w: self._flip_state(False))
|
|
elif self.is_paused:
|
|
STATUS.connect('program-pause-changed', lambda w, data: self._flip_state(data))
|
|
elif self.is_flood:
|
|
STATUS.connect('flood-changed', lambda w, data: self._flip_state(data))
|
|
elif self.is_mist:
|
|
STATUS.connect('mist-changed', lambda w, data: self._flip_state(data))
|
|
elif self.is_block_delete:
|
|
STATUS.connect('block-delete-changed', lambda w, data: self._flip_state(data))
|
|
elif self.is_optional_stop:
|
|
STATUS.connect('optional-stop-changed', lambda w, data: self._flip_state(data))
|
|
elif self.is_joint_homed:
|
|
STATUS.connect('homed', lambda w, data: self.joint_homed(data))
|
|
STATUS.connect('not-all-homed', lambda w, data: self.joints_unhomed(data))
|
|
elif self.is_limits_overridden:
|
|
STATUS.connect('override-limits-changed', self.check_override_limits)
|
|
STATUS.connect('hard-limits-tripped', lambda w, data, group: only_false(data))
|
|
elif self.is_manual or self.is_mdi or self.is_auto:
|
|
STATUS.connect('mode-manual', lambda w: self.mode_changed(0))
|
|
STATUS.connect('mode-mdi', lambda w: self.mode_changed(1))
|
|
STATUS.connect('mode-auto', lambda w: self.mode_changed(2))
|
|
elif self.is_spindle_stopped or self.is_spindle_fwd or self.is_spindle_rev:
|
|
STATUS.connect('spindle-control-changed', lambda w, num, state, speed, upto: self.spindle_changed(speed))
|
|
elif self.is_spindle_at_speed:
|
|
STATUS.connect('requested-spindle-speed-changed', lambda w, speed: self.spindle_requested_changed(speed))
|
|
STATUS.connect('spindle-override-changed', lambda w, rate: self.spindle_override_changed(rate))
|
|
STATUS.connect('actual-spindle-speed-changed',lambda w, speed: self.spindle_actual_changed(speed))
|
|
elif self.is_neg_limit_tripped or \
|
|
self.is_pos_limit_tripped or \
|
|
self.is_limits_tripped:
|
|
STATUS.connect('hard-limits-tripped', lambda w, data, group: self.switch_on_hard_limits(data, group))
|
|
elif self._follow_halpin:
|
|
STATUS.connect('periodic', lambda w: self._set_halpin_text())
|
|
|
|
def _flip_state(self, data):
|
|
if self.invert_state:
|
|
data = not data
|
|
self.change_state(data)
|
|
|
|
def change_state(self, state):
|
|
super(StateLED, self).change_state(state)
|
|
if self._halpin_option:
|
|
self.hal_pin.set(state)
|
|
|
|
def joint_homed(self, joint):
|
|
if int(joint) == self.joint_number:
|
|
self._flip_state(True)
|
|
|
|
def joints_unhomed(self, jlist):
|
|
if str(self.joint_number) in jlist:
|
|
self._flip_state(False)
|
|
|
|
def check_override_limits(self, w, state, data):
|
|
for i in data:
|
|
if i == 1:
|
|
self._flip_state(True)
|
|
return
|
|
self._flip_state(False)
|
|
|
|
def mode_changed(self, mode):
|
|
if self.is_manual and mode == 0:
|
|
self._flip_state(True)
|
|
elif self.is_mdi and mode == 1:
|
|
self._flip_state(True)
|
|
elif self.is_auto and mode == 2:
|
|
self._flip_state(True)
|
|
else:
|
|
self._flip_state(False)
|
|
|
|
def spindle_changed(self, state):
|
|
if self.is_spindle_stopped and state == 0:
|
|
self._flip_state(True)
|
|
elif self.is_spindle_rev and state < 0:
|
|
self._flip_state(True)
|
|
elif self.is_spindle_fwd and state > 0:
|
|
self._flip_state(True)
|
|
else:
|
|
self._flip_state(False)
|
|
|
|
def spindle_off(self, state):
|
|
if state == 0:
|
|
if self.invert_state:
|
|
self.change_state(True)
|
|
else:
|
|
self.change_state(False)
|
|
|
|
def spindle_requested_changed(self, speed):
|
|
self._requested = speed
|
|
state = STATUS.is_spindle_on()
|
|
self.setState(state)
|
|
|
|
def spindle_override_changed(self, rate):
|
|
self._override = rate/100.0
|
|
|
|
def spindle_actual_changed(self, speed):
|
|
self._actual = speed
|
|
if not STATUS.is_spindle_on():
|
|
if self._halpin_option:
|
|
self.hal_pin.set(False)
|
|
self.setFlashing(False)
|
|
return
|
|
flash = self.spindle_near_check()
|
|
self.setFlashing(flash)
|
|
if self._halpin_option:
|
|
self.hal_pin.set(not flash)
|
|
|
|
def spindle_near_check(self):
|
|
req = self._requested * self._override
|
|
upper = abs(req * (1+self._at_speed_percent))
|
|
lower = abs(req * (1-self._at_speed_percent))
|
|
value = abs(self._actual)
|
|
#print(req,lower,value,upper,lower <= value <= upper)
|
|
if lower <= value <= upper:
|
|
return False
|
|
return True
|
|
|
|
def switch_on_hard_limits(self, data, group):
|
|
'''
|
|
Switch LED state based on hard limits state
|
|
of selected joint.
|
|
|
|
Args:
|
|
data (bool): combined limits state.
|
|
group (list): list of all joints negative
|
|
and positive hard limits state.
|
|
'''
|
|
|
|
pair = group[self.joint_number]
|
|
if self.is_neg_limit_tripped: data = pair[0]
|
|
elif self.is_pos_limit_tripped: data = pair[1]
|
|
else:
|
|
data = pair[0]+pair[1]
|
|
self._flip_state(data)
|
|
|
|
def _set_halpin_text(self):
|
|
try:
|
|
state = bool(self.HAL_GCOMP_.hal.get_value(self._halpin_name))
|
|
except Exception as e:
|
|
return
|
|
self._flip_state(state)
|
|
|
|
#########################################################################
|
|
# This is how designer can interact with our widget properties.
|
|
# designer will show the Property properties in the editor
|
|
# it will use the get set and reset calls to do those actions
|
|
#
|
|
# _toggle_properties makes it so we can only select one option
|
|
########################################################################
|
|
|
|
def _toggle_properties(self, picked):
|
|
data = ('is_paused', 'is_estopped', 'is_on', 'is_idle', 'is_homed',
|
|
'is_flood', 'is_mist', 'is_block_delete', 'is_optional_stop',
|
|
'is_joint_homed', 'is_limits_overridden','is_manual',
|
|
'is_mdi', 'is_auto', 'is_spindle_stopped', 'is_spindle_fwd',
|
|
'is_spindle_rev','is_spindle_at_speed','neg_hard_limit_state',
|
|
'pos_hard_limit_state','hard_limits_state','followhalpin')
|
|
|
|
for i in data:
|
|
if not i == picked:
|
|
if i in('neg_hard_limit_state',
|
|
'pos_hard_limit_state','hard_limits_state'):
|
|
self[i] = False
|
|
else:
|
|
self[i+'_status'] = False
|
|
|
|
# property getter/setters
|
|
|
|
# invert status
|
|
def set_invert_state(self, data):
|
|
self.invert_state = data
|
|
def get_invert_state(self):
|
|
return self.invert_state
|
|
def reset_invert_state(self):
|
|
self.invert_state = False
|
|
|
|
# machine is paused status
|
|
def set_is_paused(self, data):
|
|
self.is_paused = data
|
|
if data:
|
|
self._toggle_properties('is_paused')
|
|
def get_is_paused(self):
|
|
return self.is_paused
|
|
def reset_is_paused(self):
|
|
self.is_paused = False
|
|
|
|
# machine is estopped status
|
|
def set_is_estopped(self, data):
|
|
self.is_estopped = data
|
|
if data:
|
|
self._toggle_properties('is_estopped')
|
|
def get_is_estopped(self):
|
|
return self.is_estopped
|
|
def reset_is_estopped(self):
|
|
self.is_estopped = False
|
|
|
|
# machine is on status
|
|
def set_is_on(self, data):
|
|
self.is_on = data
|
|
if data:
|
|
self._toggle_properties('is_on')
|
|
def get_is_on(self):
|
|
return self.is_on
|
|
def reset_is_on(self):
|
|
self.is_on = False
|
|
|
|
# machine is idle status
|
|
def set_is_idle(self, data):
|
|
self.is_idle = data
|
|
if data:
|
|
self._toggle_properties('is_idle')
|
|
def get_is_idle(self):
|
|
return self.is_idle
|
|
def reset_is_idle(self):
|
|
self.is_idle = False
|
|
|
|
# machine_is_homed status
|
|
def set_is_homed(self, data):
|
|
self.is_homed = data
|
|
if data:
|
|
self._toggle_properties('is_homed')
|
|
def get_is_homed(self):
|
|
return self.is_homed
|
|
def reset_is_homed(self):
|
|
self.is_homed = False
|
|
|
|
# machine is_flood status
|
|
def set_is_flood(self, data):
|
|
self.is_flood = data
|
|
if data:
|
|
self._toggle_properties('is_flood')
|
|
def get_is_flood(self):
|
|
return self.is_flood
|
|
def reset_is_flood(self):
|
|
self.is_flood = False
|
|
|
|
# machine is_mist status
|
|
def set_is_mist(self, data):
|
|
self.is_mist = data
|
|
if data:
|
|
self._toggle_properties('is_mist')
|
|
def get_is_mist(self):
|
|
return self.is_mist
|
|
def reset_is_mist(self):
|
|
self.is_mist = False
|
|
|
|
# machine_is_block_delete status
|
|
def set_is_block_delete(self, data):
|
|
self.is_block_delete = data
|
|
if data:
|
|
self._toggle_properties('is_block_delete')
|
|
def get_is_block_delete(self):
|
|
return self.is_block_delete
|
|
def reset_is_block_delete(self):
|
|
self.is_block_delete = False
|
|
|
|
# machine_is_optional_stop status
|
|
def set_is_optional_stop(self, data):
|
|
self.is_optional_stop = data
|
|
if data:
|
|
self._toggle_properties('is_optional_stop')
|
|
def get_is_optional_stop(self):
|
|
return self.is_optional_stop
|
|
def reset_is_optional_stop(self):
|
|
self.is_optional_stop = False
|
|
|
|
# machine_is_joint_homed status
|
|
def set_is_joint_homed(self, data):
|
|
self.is_joint_homed = data
|
|
if data:
|
|
self._toggle_properties('is_joint_homed')
|
|
def get_is_joint_homed(self):
|
|
return self.is_joint_homed
|
|
def reset_is_joint_homed(self):
|
|
self.is_joint_homed = False
|
|
|
|
# machine_is_limits_overridden status
|
|
def set_is_limits_overridden(self, data):
|
|
self.is_limits_overridden = data
|
|
if data:
|
|
self._toggle_properties('is_limits_overridden')
|
|
def get_is_limits_overridden(self):
|
|
return self.is_limits_overridden
|
|
def reset_is_limits_overridden(self):
|
|
self.is_limits_overridden = False
|
|
|
|
# machine is manual status
|
|
def set_is_manual(self, data):
|
|
self.is_manual = data
|
|
if data:
|
|
self._toggle_properties('is_manual')
|
|
def get_is_manual(self):
|
|
return self.is_manual
|
|
def reset_is_manual(self):
|
|
self.is_manual = False
|
|
|
|
# machine is mdi status
|
|
def set_is_mdi(self, data):
|
|
self.is_mdi = data
|
|
if data:
|
|
self._toggle_properties('is_mdi')
|
|
def get_is_mdi(self):
|
|
return self.is_mdi
|
|
def reset_is_mdi(self):
|
|
self.is_mdi = False
|
|
|
|
# machine is auto status
|
|
def set_is_auto(self, data):
|
|
self.is_auto = data
|
|
if data:
|
|
self._toggle_properties('is_auto')
|
|
def get_is_auto(self):
|
|
return self.is_auto
|
|
def reset_is_auto(self):
|
|
self.is_auto = False
|
|
|
|
# machine is spindle_stopped status
|
|
def set_is_spindle_stopped(self, data):
|
|
self.is_spindle_stopped = data
|
|
if data:
|
|
self._toggle_properties('is_spindle_stopped')
|
|
def get_is_spindle_stopped(self):
|
|
return self.is_spindle_stopped
|
|
def reset_is_spindle_stopped(self):
|
|
self.is_spindle_stopped = False
|
|
|
|
# machine is spindle_fwd status
|
|
def set_is_spindle_fwd(self, data):
|
|
self.is_spindle_fwd = data
|
|
if data:
|
|
self._toggle_properties('is_spindle_fwd')
|
|
def get_is_spindle_fwd(self):
|
|
return self.is_spindle_fwd
|
|
def reset_is_spindle_fwd(self):
|
|
self.is_spindle_fwd = False
|
|
|
|
# machine is spindle_rev status
|
|
def set_is_spindle_rev(self, data):
|
|
self.is_spindle_rev = data
|
|
if data:
|
|
self._toggle_properties('is_spindle_rev')
|
|
def get_is_spindle_rev(self):
|
|
return self.is_spindle_rev
|
|
def reset_is_spindle_rev(self):
|
|
self.is_spindle_rev = False
|
|
|
|
# machine is spindle_at_speed status
|
|
def set_is_spindle_at_speed(self, data):
|
|
self.is_spindle_at_speed = data
|
|
if data:
|
|
self._toggle_properties('is_spindle_at_speed')
|
|
def get_is_spindle_at_speed(self):
|
|
return self.is_spindle_at_speed
|
|
def reset_is_spindle_at_speed(self):
|
|
self.is_spindle_at_speed = False
|
|
|
|
# machine hard limits_tripped status
|
|
def set_neg_limit_tripped(self, data):
|
|
self.is_neg_limit_tripped = data
|
|
if data:
|
|
self._toggle_properties('neg_hard_limit_state')
|
|
def get_neg_limit_tripped(self):
|
|
return self.is_neg_limit_tripped
|
|
def reset_neg_limit_tripped(self):
|
|
self.is_neg_limit_tripped = False
|
|
|
|
# machine hard limits_tripped status
|
|
def set_pos_limit_tripped(self, data):
|
|
self.is_pos_limit_tripped = data
|
|
if data:
|
|
self._toggle_properties('pos_hard_limit_state')
|
|
def get_pos_limit_tripped(self):
|
|
return self.is_pos_limit_tripped
|
|
def reset_pos_limit_tripped(self):
|
|
self.is_pos_limit_tripped = False
|
|
|
|
# machine hard limits_tripped status
|
|
def set_limits_tripped(self, data):
|
|
self.is_limits_tripped = data
|
|
if data:
|
|
self._toggle_properties('hard_limits_state')
|
|
def get_limits_tripped(self):
|
|
return self.is_limits_tripped
|
|
def reset_limits_tripped(self):
|
|
self.is_limits_tripped = False
|
|
|
|
def set_follow_pin(self, data):
|
|
self._follow_halpin = data
|
|
if data:
|
|
self._toggle_properties('follow_halpin')
|
|
def get_follow_pin(self):
|
|
return self._follow_halpin
|
|
def reset_follow_pin(self):
|
|
self._follow_halpin = False
|
|
|
|
# Non bool
|
|
|
|
# machine_joint_number status
|
|
def set_joint_number(self, data):
|
|
self.joint_number = data
|
|
def get_joint_number(self):
|
|
return self.joint_number
|
|
def reset_joint_number(self):
|
|
self.joint_number = 0
|
|
|
|
def set_spindle_near_percent(self, data):
|
|
if data < 0: data = 0
|
|
if data> 100: data = 100
|
|
self._at_speed_percent = data/100.0
|
|
def get_spindle_near_percent(self):
|
|
return int(self._at_speed_percent * 100)
|
|
def reset_spindle_near_percent(self):
|
|
self._at_speed_percent = 0.10
|
|
|
|
def set_halpin_name(self, data):
|
|
self._halpin_name = data
|
|
def get_halpin_name(self):
|
|
return self._halpin_name
|
|
def reset_halpin_name(self):
|
|
self._halpin_name = ''
|
|
|
|
# designer will show these properties in this order:
|
|
# BOOL
|
|
invert_state_status = Property(bool, get_invert_state, set_invert_state, reset_invert_state)
|
|
is_paused_status = Property(bool, get_is_paused, set_is_paused, reset_is_paused)
|
|
is_estopped_status = Property(bool, get_is_estopped, set_is_estopped, reset_is_estopped)
|
|
is_on_status = Property(bool, get_is_on, set_is_on, reset_is_on)
|
|
is_idle_status = Property(bool, get_is_idle, set_is_idle, reset_is_idle)
|
|
is_homed_status = Property(bool, get_is_homed, set_is_homed, reset_is_homed)
|
|
is_flood_status = Property(bool, get_is_flood, set_is_flood, reset_is_flood)
|
|
is_mist_status = Property(bool, get_is_mist, set_is_mist, reset_is_mist)
|
|
is_block_delete_status = Property(bool, get_is_block_delete, set_is_block_delete, reset_is_block_delete)
|
|
is_optional_stop_status = Property(bool, get_is_optional_stop, set_is_optional_stop, reset_is_optional_stop)
|
|
is_joint_homed_status = Property(bool, get_is_joint_homed, set_is_joint_homed, reset_is_joint_homed)
|
|
is_limits_overridden_status = Property(bool, get_is_limits_overridden, set_is_limits_overridden,
|
|
reset_is_limits_overridden)
|
|
is_manual_status = Property(bool, get_is_manual, set_is_manual, reset_is_manual)
|
|
is_mdi_status = Property(bool, get_is_mdi, set_is_mdi, reset_is_mdi)
|
|
is_auto_status = Property(bool, get_is_auto, set_is_auto, reset_is_auto)
|
|
is_spindle_stopped_status = Property(bool, get_is_spindle_stopped, set_is_spindle_stopped, reset_is_spindle_stopped)
|
|
is_spindle_fwd_status = Property(bool, get_is_spindle_fwd, set_is_spindle_fwd, reset_is_spindle_fwd)
|
|
is_spindle_rev_status = Property(bool, get_is_spindle_rev, set_is_spindle_rev, reset_is_spindle_rev)
|
|
is_spindle_at_speed_status = Property(bool, get_is_spindle_at_speed, set_is_spindle_at_speed, reset_is_spindle_at_speed)
|
|
neg_hard_limit_state = Property(bool, get_neg_limit_tripped, set_neg_limit_tripped, reset_neg_limit_tripped)
|
|
pos_hard_limit_state = Property(bool, get_pos_limit_tripped, set_pos_limit_tripped, reset_pos_limit_tripped)
|
|
hard_limits_state = Property(bool, get_limits_tripped, set_limits_tripped, reset_limits_tripped)
|
|
follow_halpin_state = Property(bool, get_follow_pin, set_follow_pin, reset_follow_pin)
|
|
|
|
# NON BOOL
|
|
joint_number_status = Property(int, get_joint_number, set_joint_number, reset_joint_number)
|
|
spindle_near_percent_status = Property(int, get_spindle_near_percent, set_spindle_near_percent, reset_spindle_near_percent)
|
|
halpin_name = Property(str, get_halpin_name, set_halpin_name, reset_halpin_name)
|
|
|
|
# boilder code
|
|
def __getitem__(self, item):
|
|
return getattr(self, item)
|
|
def __setitem__(self, item, value):
|
|
return setattr(self, item, value)
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import sys
|
|
from qtpy.QtGui import QApplication
|
|
app = QApplication(sys.argv)
|
|
led = StateLED()
|
|
led.show()
|
|
#led.startFlashing()
|
|
sys.exit(app.exec())
|