|
| 1 | +#!/usr/bin/env python3 |
| 2 | + |
| 3 | +import os |
| 4 | +import sys |
| 5 | +import time |
| 6 | + |
| 7 | +import json |
| 8 | +import signal |
| 9 | + |
| 10 | +import hal |
| 11 | +from qtvcp.qt_halobjects import Qhal |
| 12 | +from common.iniinfo import _IStat as IStatParent |
| 13 | +from common import logger |
| 14 | + |
| 15 | +# LOG is for running code logging |
| 16 | +LOG = logger.initBaseLogger('HAL bridge', log_file=None, |
| 17 | + log_level=logger.WARNING, logToFile=False) |
| 18 | + |
| 19 | +# Force the log level for this module |
| 20 | +LOG.setLevel(logger.DEBUG) # One of DEBUG, INFO, WARNING, ERROR, CRITICAL |
| 21 | + |
| 22 | +try: |
| 23 | + import zmq |
| 24 | + ZMQ = True |
| 25 | +except: |
| 26 | + LOG.critical('ZMQ python library problem - Is python3-zmq installed?') |
| 27 | + ZMQ = False |
| 28 | + |
| 29 | +class Info(IStatParent): |
| 30 | + _instance = None |
| 31 | + _instanceNum = 0 |
| 32 | + |
| 33 | + def __new__(cls, *args, **kwargs): |
| 34 | + if not cls._instance: |
| 35 | + cls._instance = IStatParent.__new__(cls, *args, **kwargs) |
| 36 | + return cls._instance |
| 37 | + |
| 38 | +# Instantiate the library with global reference |
| 39 | + |
| 40 | + |
| 41 | +class Bridge(object): |
| 42 | + def __init__(self, readAddress = "tcp://127.0.0.1:5690", |
| 43 | + writeAddress = "tcp://127.0.0.1:5691"): |
| 44 | + super(Bridge, self).__init__() |
| 45 | + self.INFO = Info() |
| 46 | + |
| 47 | + self.currentSelectedAxis = 'None' |
| 48 | + self.axesSelected = {'X':0,'Y':0,'Z':0,'A':0,'B':0,'C':0, |
| 49 | + 'U':0,'V':0,'W':0} |
| 50 | + self.readAddress = readAddress |
| 51 | + self.writeAddress = writeAddress |
| 52 | + LOG.debug('read port: {}'.format(readAddress)) |
| 53 | + LOG.debug('write port: {}'.format(writeAddress)) |
| 54 | + |
| 55 | + self.readTopic = "" |
| 56 | + self.writeTopic = "STATUSREQUEST" |
| 57 | + |
| 58 | + # catch control c and terminate signals |
| 59 | + signal.signal(signal.SIGTERM, self.shutdown) |
| 60 | + signal.signal(signal.SIGINT, self.shutdown) |
| 61 | + |
| 62 | + self.init_hal() |
| 63 | + if ZMQ: |
| 64 | + self.init_read() |
| 65 | + self.init_write() |
| 66 | + |
| 67 | + def update(self, *arg): |
| 68 | + print(self, arg) |
| 69 | + raw=arg[0]; row=arg[1];column=arg[2];state=arg[3] |
| 70 | + LOG.debug('raw {}, row {}, col {}, state {}'.format(raw,row,column,state)) |
| 71 | + print ('raw',raw,'row:',row,'column:',column,'state:',state) |
| 72 | + self.writeMsg('set_selected_axis','Y') |
| 73 | + self.activeJoint.set(10) |
| 74 | + |
| 75 | + def init_hal(self): |
| 76 | + self.comp = h = hal.component("bridge") |
| 77 | + QHAL = Qhal(comp=self.comp, hal=hal) |
| 78 | + |
| 79 | + self.jogRate = QHAL.newpin("jog-rate", hal.HAL_FLOAT, hal.HAL_OUT) |
| 80 | + self.jogRateIn = QHAL.newpin("jog-rate-in", hal.HAL_FLOAT, hal.HAL_IN) |
| 81 | + self.jogRateIn.pinValueChanged.connect(self.pinChanged) |
| 82 | + |
| 83 | + self.jogRateAngular = QHAL.newpin("jog-rate-angular", hal.HAL_FLOAT, hal.HAL_OUT) |
| 84 | + self.jogRateAngularIn = QHAL.newpin("jog-rate-angular-in", hal.HAL_FLOAT, hal.HAL_OUT) |
| 85 | + self.jogRateAngularIn.pinValueChanged.connect(self.pinChanged) |
| 86 | + |
| 87 | + self.jogIncrement = QHAL.newpin("jog-increment", hal.HAL_FLOAT, hal.HAL_OUT) |
| 88 | + self.jogIncrementAngular = QHAL.newpin("jog-increment-angular", hal.HAL_FLOAT, hal.HAL_OUT) |
| 89 | + self.activeJoint = QHAL.newpin('joint-selected', hal.HAL_S32, hal.HAL_OUT) |
| 90 | + |
| 91 | + for i in (self.INFO.AVAILABLE_AXES): |
| 92 | + let = i.lower() |
| 93 | + # input |
| 94 | + self['axis{}Select'.format(let)] = QHAL.newpin('axis-%s-select'%let, hal.HAL_BIT, hal.HAL_IN) |
| 95 | + self['axis{}Select'.format(let)].pinValueChanged.connect(self.pinChanged) |
| 96 | + # output |
| 97 | + self['Axis{}IsSelected'.format(let)] = QHAL.newpin('axis-%s-is-selected'%let, hal.HAL_BIT, hal.HAL_OUT) |
| 98 | + |
| 99 | + self.cycle_start = QHAL.newpin('cycle-start-in',QHAL.HAL_BIT, QHAL.HAL_IN) |
| 100 | + self.cycle_start.pinValueChanged.connect(self.pinChanged) |
| 101 | + self.cycle_pause = QHAL.newpin('cycle-pause-in',QHAL.HAL_BIT, QHAL.HAL_IN) |
| 102 | + self.cycle_pause.pinValueChanged.connect(self.pinChanged) |
| 103 | + |
| 104 | + for i in self.INFO.MDI_COMMAND_DICT: |
| 105 | + LOG.debug('{} {}'.format(i,self.INFO.MDI_COMMAND_DICT.get(i))) |
| 106 | + self[i] = QHAL.newpin('ini-mdi-cmd-{}'.format(i),QHAL.HAL_BIT, QHAL.HAL_IN) |
| 107 | + self[i].pinValueChanged.connect(self.runMacroChanged) |
| 108 | + |
| 109 | + for i in self.INFO.INI_MACROS: |
| 110 | + name = i.split()[0] |
| 111 | + LOG.debug('{} {}'.format(name,i)) |
| 112 | + self[name] = QHAL.newpin('ini-macro-cmd-{}'.format(name),QHAL.HAL_BIT, QHAL.HAL_IN) |
| 113 | + self[name].pinValueChanged.connect(self.runMacroChanged) |
| 114 | + |
| 115 | + QHAL.setUpdateRate(100) |
| 116 | + h.ready() |
| 117 | + |
| 118 | + def init_write(self): |
| 119 | + context = zmq.Context() |
| 120 | + self.writeSocket = context.socket(zmq.PUB) |
| 121 | + self.writeSocket.bind(self.writeAddress) |
| 122 | + |
| 123 | + def init_read(self): |
| 124 | + # ZeroMQ Context |
| 125 | + self.readContext = zmq.Context() |
| 126 | + |
| 127 | + # Define the socket using the "Context" |
| 128 | + self.readSocket = self.readContext.socket(zmq.SUB) |
| 129 | + |
| 130 | + # Define subscription and messages with topic to accept. |
| 131 | + self.readSocket.setsockopt_string(zmq.SUBSCRIBE, self.readTopic) |
| 132 | + self.readSocket.connect(self.readAddress) |
| 133 | + |
| 134 | + # callback from ZMQ read socket |
| 135 | + def readMsg(self, msg): |
| 136 | + if self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: |
| 137 | + while self.readSocket.getsockopt(zmq.EVENTS) & zmq.POLLIN: |
| 138 | + # get raw message |
| 139 | + topic, data = self.readSocket.recv_multipart() |
| 140 | + # convert from json object to python object |
| 141 | + y = json.loads(data) |
| 142 | + self. action(y.get('MESSAGE'),y.get('ARGS')) |
| 143 | + |
| 144 | + # set our output HAL pins from messages from hal_glib |
| 145 | + def action(self, msg, data): |
| 146 | + LOG.debug('{} {}'.format(msg, data)) |
| 147 | + if msg == 'jograte-changed': |
| 148 | + self.jogRate.set(float(data[0])) |
| 149 | + elif msg == 'jograte-angular-changed': |
| 150 | + self.jogRateAngular.set(float(data[0])) |
| 151 | + elif msg == 'jogincrements-changed': |
| 152 | + self.jogIncrement.set(float(data[0][0])) |
| 153 | + elif msg == 'jogincrement-angular-changed': |
| 154 | + self.jogIncremtAngular.set(float(data[0][0])) |
| 155 | + elif msg == 'joint-selection-changed': |
| 156 | + self.activeJoint.set(int(data[0])) |
| 157 | + elif msg == 'axis-selection-changed': |
| 158 | + flag = 1 |
| 159 | + for i in(self.INFO.AVAILABLE_AXES): |
| 160 | + if data[0] == i: |
| 161 | + state = True |
| 162 | + self.currentSelectedAxis = data[0] |
| 163 | + flag = 0 |
| 164 | + else: |
| 165 | + state = False |
| 166 | + self['Axis{}IsSelected'.format(i.lower())].set(state) |
| 167 | + self.axesSelected[i] = int(state) |
| 168 | + if flag: |
| 169 | + self.currentSelectedAxis = 'None' |
| 170 | + #print ('axis state', self.axesSelected) |
| 171 | + |
| 172 | + # send msg to hal_glib |
| 173 | + def writeMsg(self, msg, data): |
| 174 | + print('Write Msg called') |
| 175 | + if ZMQ: |
| 176 | + topic = self.writeTopic |
| 177 | + message = json.dumps({'FUNCTION':msg,'ARGS':data}) |
| 178 | + LOG.debug('Sending ZMQ Message:{} {}'.format(topic, message)) |
| 179 | + self.writeSocket.send_multipart( |
| 180 | + [bytes(topic.encode('utf-8')), |
| 181 | + bytes((message).encode('utf-8'))]) |
| 182 | + |
| 183 | + # callback from HAL input pins |
| 184 | + def pinChanged(self, pinObject, value): |
| 185 | + LOG.debug('Pin name:{} changed value to {}'.format(pinObject.text(), value)) |
| 186 | + #print(type(value)) |
| 187 | + # Axis selction change request |
| 188 | + if 'select' in pinObject.text(): |
| 189 | + if bool(value) == False: |
| 190 | + pass |
| 191 | + #print('Not true state') |
| 192 | + return |
| 193 | + for i in (self.INFO.AVAILABLE_AXES): |
| 194 | + if '-{}-'.format(i.lower()) in pinObject.text(): |
| 195 | + self.writeMsg('set_selected_axis', i) |
| 196 | + break |
| 197 | + else: |
| 198 | + if 'None' in pinObject.text(): |
| 199 | + self.writeMsg('set_selected_axis', '') |
| 200 | + |
| 201 | + # cycle start |
| 202 | + elif self.cycle_start == pinObject: |
| 203 | + if value: |
| 204 | + self.writeMsg('request_cycle_start', value) |
| 205 | + |
| 206 | + # cycle pause |
| 207 | + elif self.cycle_pause == pinObject: |
| 208 | + #if value: |
| 209 | + self.writeMsg('request_cycle_pause', value) |
| 210 | + |
| 211 | + # linear jog rate |
| 212 | + elif self.jogRateIn == pinObject: |
| 213 | + self.writeMsg('set_jograte', value) |
| 214 | + |
| 215 | + # angular jog rate |
| 216 | + elif self.jogRateAngularIn == pinObject: |
| 217 | + self.writeMsg('set_jograte_angular', value) |
| 218 | + |
| 219 | + # catch all default |
| 220 | + else: |
| 221 | + self.writeMsg(pinObject.text(),value) |
| 222 | + |
| 223 | + # callback; request to run a specific macro |
| 224 | + def runMacroChanged(self, pinObject, value): |
| 225 | + LOG.debug('Macro Pin name:{} changed value to {}'.format(pinObject.text(), value)) |
| 226 | + #LOG.debug(type(value)) |
| 227 | + name = pinObject.text().strip('macro-cmd-') |
| 228 | + if value: |
| 229 | + self.writeMsg('request_macro_call', name) |
| 230 | + |
| 231 | + def shutdown(self,signum=None,stack_frame=None): |
| 232 | + LOG.debug('shutdown') |
| 233 | + global app |
| 234 | + app.quit() |
| 235 | + |
| 236 | + def getJogRate(self): |
| 237 | + return self.jogRate.get() |
| 238 | + def setJogRate(self, value): |
| 239 | + self.writeMsg('set_jograte', value) |
| 240 | + |
| 241 | + def getJogRateAngular(self): |
| 242 | + return self.jogRateAngular.get() |
| 243 | + def setJogRateAngular(self, value): |
| 244 | + self.writeMsg('set_jograte_angular', value) |
| 245 | + |
| 246 | + def getSelectedAxis(self): |
| 247 | + name = self.currentSelectedAxis |
| 248 | + if name == 'None': |
| 249 | + index = -1 |
| 250 | + else: |
| 251 | + index = 'XYZABCUVW'.index(name) |
| 252 | + return index |
| 253 | + def setSelectedAxis(self, value): |
| 254 | + if value < 0: |
| 255 | + letter = 'None' |
| 256 | + else: |
| 257 | + letter ='XYZABCUVW'[value] |
| 258 | + self.writeMsg('set_selected_axis', letter) |
| 259 | + |
| 260 | + def isAxisSelected(self, index): |
| 261 | + letter = 'XYZABCUVW'[index] |
| 262 | + return int(self.axesSelected[letter]) |
| 263 | + |
| 264 | + def __getitem__(self, item): |
| 265 | + return getattr(self, item) |
| 266 | + def __setitem__(self, item, value): |
| 267 | + return setattr(self, item, value) |
| 268 | + |
| 269 | +if __name__ == "__main__": |
| 270 | + import sys |
| 271 | + import getopt |
| 272 | + from PyQt5.QtWidgets import QApplication |
| 273 | + |
| 274 | + letters = 'dh' # the : means an argument needs to be passed after the letter |
| 275 | + keywords = ['readport=', 'writeport=' ] # the = means that a value is expected after |
| 276 | + # the keyword |
| 277 | + |
| 278 | + opts, extraparam = getopt.getopt(sys.argv[1:],letters,keywords) |
| 279 | + # starts at the second element of argv since the first one is the script name |
| 280 | + # extraparms are extra arguments passed after all option/keywords are assigned |
| 281 | + # opts is a list containing the pair "option"/"value" |
| 282 | + |
| 283 | + readport = "tcp://127.0.0.1:5690" |
| 284 | + writeport = "tcp://127.0.0.1:5691" |
| 285 | + |
| 286 | + for o,p in opts: |
| 287 | + if o in ['-d']: |
| 288 | + LOG.setLevel(logger.DEBUG) |
| 289 | + elif o in ['--readport']: |
| 290 | + readport = p |
| 291 | + elif o in ['--writeport']: |
| 292 | + writeport = p |
| 293 | + elif o in ['-h','--help']: |
| 294 | + print('HAL bridge: GUI to HAL interface using ZMQ') |
| 295 | + print('option "-d" = debug print mode') |
| 296 | + print('option "--readport=" read socket address') |
| 297 | + print('option "--writeport=" write socket address') |
| 298 | + print('example: hal_bridge -d --readport=tcp://127.0.0.1:5692') |
| 299 | + |
| 300 | + app = QApplication(sys.argv) |
| 301 | + test = Bridge(readport, writeport) |
| 302 | + sys.exit(app.exec_()) |
0 commit comments