diff --git a/README.md b/README.md index 61eee2e..3964a28 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,9 @@ install as a special kernel when you are the user. found [here](https://janakiev.com/til/jupyter-virtual-envs/). ## Change Log +* 0.5.1 (Mar. 9, 2023) + * Include `spidev` package in requirements. + * More details reported when unable to "find trough". * 0.5.0 (Mar. 4, 2023) First version with working GUI * 0.1.0 First pypi compatible package version. @@ -134,9 +137,8 @@ install as a special kernel when you are the user. 2. Update any `.md` files included in `_init_.py`. * Generally URLs should be absolute, not relative. 3. At the root level run pdoc `pdoc --logo-link https://gutow.github. - io/Langmuir_Trough/ --footer-text "Langmuir_Trough vX.X.X" --math -html -o - docs Trough_GUI Trough_Control` - where `X.X.X` is the version number. +io/Langmuir_Trough/ --footer-text "Langmuir_Trough vX.X.X" --math -html -o +docs Trough_GUI Trough_Control` where `X.X.X` is the version number. 4. Because of the way the document building process works the background tasks will be started. **You will have to stop the document build after the documentation is done building (watch the `doc` folder) with a `^C` to diff --git a/Trough_Control/trough_util.py b/Trough_Control/trough_util.py index bb5dd31..925873a 100644 --- a/Trough_Control/trough_util.py +++ b/Trough_Control/trough_util.py @@ -99,6 +99,8 @@ def init_trough(): from piplates import DAQC2plate del DAQC2plate except Exception as e: + print("An issue was encountered while accessing the DAQC2plate:" + + str(e)) trough_exists = False if trough_exists: TROUGH = Process(target=troughctl, args=(cmdrcv, datasend)) diff --git a/docs/Trough_Control.html b/docs/Trough_Control.html index 520ed99..8494cd4 100644 --- a/docs/Trough_Control.html +++ b/docs/Trough_Control.html @@ -63,7 +63,7 @@
126def troughctl(CTLPipe,DATAPipe): -127 """ -128 Will run as separate process taking in commands through a pipe and -129 returning data on demand through a second pipe. -130 Iteration 1, collects data into a fifo and watches barrier position. -131 :param Pipe CTLPipe: pipe commands come in on and messages go out on. -132 :param Pipe DATAPipe: pipe data is sent out on -133 """ -134 import time -135 import numpy as np -136 from collections import deque -137 from multiprocessing import Pipe -138 from piplates import DAQC2plate as DAQC2 -139 from sys import exit -140 -141 def bundle_to_send(que_lst): -142 """ -143 -144 Parameters -145 ---------- -146 que_lst: list -147 list of deques. -148 -149 Returns -150 ------- -151 list -152 list of lists. One list for each deque. -153 """ -154 -155 pkg_lst = [] -156 for k in que_lst: -157 # print ('que_lst element: '+ str(k)) -158 tmp_lst = [] -159 for j in k: -160 tmp_lst.append(j) -161 pkg_lst.append(tmp_lst) -162 return pkg_lst -163 -164 def barrier_at_limit_check(openlimit, closelimit): -165 """ -166 Checks if barrier is at or beyond limit and stops barrier if it is moving -167 in a direction that would make it worse. -168 :param float openlimit: lowest voltage allowed for opening -169 :param float closelimit: highest voltage allowed for closing -170 -171 Returns -172 ======= -173 True or False. If True also shuts down power to barrier -174 """ -175 -176 direction = 0 # -1 closing, 0 stopped, 1 openning. -177 if (etol_call(DAQC2.getDAC,(0, 0)) >= 2.5): -178 direction = -1 -179 else: -180 direction = 1 -181 position = etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)) -182 if (position >= closelimit) and (direction == -1): -183 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -184 return True -185 if (position <= openlimit) and (direction == 1): -186 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -187 return True -188 return False -189 -190 def take_over_barrier_monitoring(): -191 """ -192 Call this to run something in a tight loop that needs to take over watching -193 the barriers. Check if a trough controller is registered at /tmp/troughctl.pid. -194 If one is store it's pid and replace with own. Revert on exit to avoid -195 crashing. -196 -197 Returns -198 ------- -199 int: -200 pid for process that was watching the barriers. -201 """ -202 import os -203 pidpath = '/tmp/troughctl.pid' -204 ctlpid = None -205 if os.path.exists(pidpath): -206 file = open(pidpath, 'r') -207 ctlpid = int(file.readline()) -208 file.close() -209 pid = os.getpid() -210 # print(str(pid)) -211 file = open(pidpath, 'w') -212 file.write(str(pid) + '\n') -213 file.close() -214 # Do not proceed until this file exists on the file system -215 while not os.path.exists(pidpath): -216 pass # just waiting for file system to catch up. -217 # Read it to make sure -218 file = open(pidpath, 'r') -219 checkpid = file.readline() -220 file.close() -221 if int(checkpid) != pid: -222 raise FileNotFoundError('Checkpid = ' + checkpid + ', but should = ' + str(pid)) -223 return ctlpid -224 -225 def return_barrier_monitoring_to_prev_process(ctlpid): -226 """ -227 -228 Parameters -229 ---------- -230 ctlpid : int -231 Process id of the process that was watching the barriers before. -232 -233 Returns -234 ------- -235 -236 """ -237 import os -238 pidpath = '/tmp/troughctl.pid' -239 if ctlpid is not None: -240 file = open(pidpath, 'w') -241 file.write(str(ctlpid) + '\n') -242 file.close() -243 elif os.path.exists(pidpath): -244 os.remove(pidpath) -245 elif os.path.exists(pidpath): # double check +diff --git a/docs/Trough_GUI.html b/docs/Trough_GUI.html index 0c6b8f4..745a502 100644 --- a/docs/Trough_GUI.html +++ b/docs/Trough_GUI.html @@ -66,7 +66,7 @@128def troughctl(CTLPipe,DATAPipe): +129 """ +130 Will run as separate process taking in commands through a pipe and +131 returning data on demand through a second pipe. +132 Iteration 1, collects data into a fifo and watches barrier position. +133 :param Pipe CTLPipe: pipe commands come in on and messages go out on. +134 :param Pipe DATAPipe: pipe data is sent out on +135 """ +136 import time +137 import numpy as np +138 from collections import deque +139 from multiprocessing import Pipe +140 from piplates import DAQC2plate as DAQC2 +141 from sys import exit +142 +143 def bundle_to_send(que_lst): +144 """ +145 +146 Parameters +147 ---------- +148 que_lst: list +149 list of deques. +150 +151 Returns +152 ------- +153 list +154 list of lists. One list for each deque. +155 """ +156 +157 pkg_lst = [] +158 for k in que_lst: +159 # print ('que_lst element: '+ str(k)) +160 tmp_lst = [] +161 for j in k: +162 tmp_lst.append(j) +163 pkg_lst.append(tmp_lst) +164 return pkg_lst +165 +166 def barrier_at_limit_check(openlimit, closelimit): +167 """ +168 Checks if barrier is at or beyond limit and stops barrier if it is moving +169 in a direction that would make it worse. +170 :param float openlimit: lowest voltage allowed for opening +171 :param float closelimit: highest voltage allowed for closing +172 +173 Returns +174 ======= +175 True or False. If True also shuts down power to barrier +176 """ +177 +178 direction = 0 # -1 closing, 0 stopped, 1 openning. +179 if (etol_call(DAQC2.getDAC,(0, 0)) >= 2.5): +180 direction = -1 +181 else: +182 direction = 1 +183 position = etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)) +184 if (position >= closelimit) and (direction == -1): +185 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +186 return True +187 if (position <= openlimit) and (direction == 1): +188 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +189 return True +190 return False +191 +192 def take_over_barrier_monitoring(): +193 """ +194 Call this to run something in a tight loop that needs to take over watching +195 the barriers. Check if a trough controller is registered at /tmp/troughctl.pid. +196 If one is store it's pid and replace with own. Revert on exit to avoid +197 crashing. +198 +199 Returns +200 ------- +201 int: +202 pid for process that was watching the barriers. +203 """ +204 import os +205 pidpath = '/tmp/troughctl.pid' +206 ctlpid = None +207 if os.path.exists(pidpath): +208 file = open(pidpath, 'r') +209 ctlpid = int(file.readline()) +210 file.close() +211 pid = os.getpid() +212 # print(str(pid)) +213 file = open(pidpath, 'w') +214 file.write(str(pid) + '\n') +215 file.close() +216 # Do not proceed until this file exists on the file system +217 while not os.path.exists(pidpath): +218 pass # just waiting for file system to catch up. +219 # Read it to make sure +220 file = open(pidpath, 'r') +221 checkpid = file.readline() +222 file.close() +223 if int(checkpid) != pid: +224 raise FileNotFoundError('Checkpid = ' + checkpid + ', but should = ' + str(pid)) +225 return ctlpid +226 +227 def return_barrier_monitoring_to_prev_process(ctlpid): +228 """ +229 +230 Parameters +231 ---------- +232 ctlpid : int +233 Process id of the process that was watching the barriers before. +234 +235 Returns +236 ------- +237 +238 """ +239 import os +240 pidpath = '/tmp/troughctl.pid' +241 if ctlpid is not None: +242 file = open(pidpath, 'w') +243 file.write(str(ctlpid) + '\n') +244 file.close() +245 elif os.path.exists(pidpath): 246 os.remove(pidpath) -247 pass -248 -249 def get_power_supply_volts(): -250 """ -251 Returns the negative and positive voltages from the power supply corrected -252 for the inline voltage divider allowing measurement up to a bit more -253 than +/- 15 V. -254 -255 :returns float V_neg: the negative voltage. -256 :returns float V_pos: the positive voltage. -257 """ -258 V_neg = 1.3875*etol_call(DAQC2.getADC, (0,6)) -259 V_pos = 1.3729*etol_call(DAQC2.getADC, (0,7)) -260 -261 return V_neg, V_pos +247 elif os.path.exists(pidpath): # double check +248 os.remove(pidpath) +249 pass +250 +251 def get_power_supply_volts(): +252 """ +253 Returns the negative and positive voltages from the power supply corrected +254 for the inline voltage divider allowing measurement up to a bit more +255 than +/- 15 V. +256 +257 :returns float V_neg: the negative voltage. +258 :returns float V_pos: the positive voltage. +259 """ +260 V_neg = 1.3875*etol_call(DAQC2.getADC, (0,6)) +261 V_pos = 1.3729*etol_call(DAQC2.getADC, (0,7)) 262 -263 def start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, -264 minopenV): -265 """ -266 Will start the barriers including giving a little boost if running -267 voltage is below the starting voltage. -268 """ -269 DAC_V = calcDAC_V(speed, direction, maxcloseV, mincloseV, maxopenV, -270 minopenV) -271 # print("speed:"+str(speed)+", direction:"+str(direction)+", DAC_V:"+str(DAC_V)) -272 if (DAC_V > startopenV) and (DAC_V < startcloseV) and (direction != 0): -273 # need a boost to start moving -274 if speed == 1: -275 DAQC2.setDAC(0, 0, startcloseV) -276 else: -277 DAQC2.setDAC(0, 0, startopenV) -278 DAQC2.setDOUTbit(0, 0) -279 time.sleep(0.25) -280 DAQC2.setDAC(0, 0, DAC_V) -281 DAQC2.setDOUTbit(0, 0) -282 pass -283 -284 def write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen): -285 """Write the motorcal to a file in ~/.Trough/calibrations.""" -286 from AdvancedHTMLParser import AdvancedTag as Domel -287 from datetime import datetime -288 from time import time -289 from pathlib import Path -290 calib_div = Domel('div') -291 calib_title = Domel('h3') -292 calib_title.setAttribute('id','title') -293 calib_title.appendInnerHTML('Calibration of Motor') -294 calib_div.appendChild(calib_title) -295 -296 time_table = Domel('table') -297 time_table.setAttribute('class','time_table') -298 time_table.setAttribute('id', 'time_table') -299 time_table.setAttribute('border', '1') -300 tr = Domel('tr') -301 tr.appendInnerHTML('<th>ISO time</th><th>Timestamp</th>') -302 time_table.append(tr) -303 tr = Domel('tr') -304 timestamp = time() -305 isotime = (datetime.fromtimestamp(timestamp)).isoformat() -306 tr.appendInnerHTML('<td>' + str(isotime) + '</td>' -307 '<td id="timestamp">' + str(timestamp) + '</td>') -308 time_table.append(tr) -309 calib_div.append(time_table) -310 -311 calib_info = Domel('table') -312 calib_info.setAttribute('class', 'calib_info') -313 calib_info.setAttribute('id', 'calib_info') -314 calib_info.setAttribute('border', '1') -315 tr = Domel('tr') -316 tr.appendInnerHTML('<th>Calibration of</th><th>Max Value</th>' -317 '<th>Min Value</th><th>Start Value</th>') -318 calib_info.appendChild(tr) -319 tr = Domel('tr') -320 tr.appendInnerHTML('<th id = "name"> Opening </th>' -321 '<td id = "maxopen">' + str(maxopen) + '</td>' -322 '<td id = "minopen">' + str(minopen) + '</td>' -323 '<td id = "startopen">' + str(startopen) + '</td>') -324 calib_info.appendChild(tr) -325 tr = Domel('tr') -326 tr.appendInnerHTML('<th id = "name"> Closing </th>' -327 '<td id = "maxclose">' + str(maxclose) + '</td>' -328 '<td id = "minclose">' + str(minclose) + '</td>' -329 '<td id = "startclose">' + str(startclose) + '</td>') -330 calib_info.appendChild(tr) -331 calib_div.appendChild(calib_info) -332 -333 -334 fileext = '.trh.cal.html' -335 filename = 'motorcal'+fileext -336 dirpath = Path('~/.Trough/calibrations').expanduser() -337 fullpath = Path(dirpath, filename) -338 svhtml = '<!DOCTYPE html><html><body>' + calib_div.asHTML() + \ -339 '</body></html>' -340 f = open(fullpath, 'w') -341 f.write(svhtml) -342 f.close() -343 pass -344 -345 def read_motorcal(): -346 """Reads the motor calibration file in ~/.Trough/calibrations -347 if it exists and returns the calibration parameters.""" -348 from AdvancedHTMLParser import AdvancedHTMLParser as Parser -349 from pathlib import Path -350 from os.path import exists -351 -352 maxopen = 0 -353 minopen = 0 -354 startopen = 0 -355 maxclose = 0 -356 minclose = 0 -357 startclose = 0 -358 timestamp = 0 -359 -360 filepath = Path('~/.Trough/calibrations/motorcal.trh.cal.html').expanduser() -361 html = "" -362 if exists(filepath): -363 f = open(filepath, 'r') -364 html = f.read() -365 f.close() -366 document = Parser() -367 document.parseStr(html) -368 if document.getElementById('title').text.strip() != 'Calibration of Motor': -369 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp -370 maxclose = float(document.getElementById('maxclose').text) -371 minclose = float(document.getElementById('minclose').text) -372 startclose = float(document.getElementById('startclose').text) -373 maxopen = float(document.getElementById('maxopen').text) -374 minopen = float(document.getElementById('minopen').text) -375 startopen = float(document.getElementById('startopen').text) -376 timestamp = float(document.getElementById('timestamp').text) -377 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp -378 -379 def motorcal(barriermin, barriermax): -380 ''' -381 :param float barriermin: minimum voltage for barrier (do not open more). -382 :param float barriermax: maximum voltage for barrier (do not close more). -383 :returns float maxclose: DAC setting for maximum close speed. -384 :returns float minclose: DAC setting for minimum close speed. -385 :returns float startclose: DAC setting providing minimum voltage to start closing. -386 :returns float maxopen: DAC setting for maximum close speed. -387 :returns float minopen: DAC setting for minimum close speed. -388 :returns float startopen: DAC setting providing minimum voltage to start opening. -389 ''' -390 -391 import os, time -392 # Since this runs in a tight loop needs to take over watching barriers. -393 # Check if a trough controller is registered at /tmp/troughctl.pid. -394 # If one is store it's pid and replace with own. Will revert on exit without -395 # crashing. -396 ctlpid = take_over_barrier_monitoring() -397 -398 # Set base values -399 maxclose = 4 -400 minclose = 2.6 -401 startclose = 2.6 -402 maxopen = 0 -403 minopen = 2.4 -404 startopen = 2.4 -405 # Calibrate the barrier. This assumes a DAQC2 pi-plate is the controlling interface. -406 # First move to fully open. -407 # print('Moving to fully open...') -408 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -409 if position > barriermin: -410 # need to open some -411 atlimit = False -412 DAQC2.setDAC(0, 0, maxopen) # set fast open -413 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -414 time.sleep(1) -415 while not atlimit: -416 atlimit = barrier_at_limit_check(barriermin, barriermax) -417 # Get rid of any hysteresis in the close direction -418 # print('Removing close hysteresis...') -419 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -420 stoppos = position + 0.05 -421 if stoppos > barriermax: -422 stoppos = barriermax -423 DAQC2.setDAC(0, 0, maxclose) # set fast close -424 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -425 time.sleep(1) -426 atlimit = False -427 while not atlimit: -428 atlimit = barrier_at_limit_check(barriermin, stoppos) -429 # Find closing stall voltage -430 # print('Finding closing stall voltage...') -431 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -432 DAQC2.setDAC(0, 0, maxclose) # set fast close -433 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -434 testclose = maxclose -435 time.sleep(2) -436 atlimit = False -437 while not atlimit: -438 atlimit = barrier_at_limit_check(barriermin, barriermax) -439 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -440 # if position > 6.5: -441 # print ('old: '+str(oldposition)+' position: '+str(position)) -442 if (position - oldposition) < 0.01: -443 # because there can be noise take a new reading and check again -444 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -445 if (position - oldposition) < 0.01: -446 minclose = testclose -447 atlimit = True -448 DAQC2.clrDOUTbit(0, 0) -449 oldposition = position -450 testclose = testclose - 0.05 -451 DAQC2.setDAC(0, 0, testclose) -452 time.sleep(2) -453 -454 # Find minimum closing start voltage -455 # print('Finding minimum closing start voltage...') -456 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -457 startclose = minclose -458 atlimit = False -459 while not atlimit: -460 atlimit = barrier_at_limit_check(barriermin, barriermax) -461 DAQC2.setDAC(0, 0, startclose) -462 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -463 time.sleep(2) -464 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -465 if (position - oldposition) < 0.01: -466 # because there can be noise take a new reading and check again -467 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -468 if (position - oldposition) < 0.01: -469 startclose = startclose + 0.05 -470 else: -471 atlimit = True -472 DAQC2.clrDOUTbit(0, 0) -473 startclose = startclose + 0.05 # To provide a margin. -474 # Move to fully closed -475 # print('Moving to fully closed...') -476 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -477 if position < barriermax: -478 # need to close some -479 atlimit = False -480 DAQC2.setDAC(0, 0, maxclose) # set fast close -481 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -482 time.sleep(1) -483 while not atlimit: -484 atlimit = barrier_at_limit_check(barriermin, barriermax) -485 -486 # Get rid of any hysteresis in the open direction -487 # print('Removing open hysteresis...') -488 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -489 stoppos = position - 0.05 -490 if stoppos < barriermin: -491 stoppos = barriermin -492 DAQC2.setDAC(0, 0, maxopen) # set fast close -493 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -494 time.sleep(1) -495 atlimit = False -496 while not atlimit: -497 atlimit = barrier_at_limit_check(stoppos, barriermax) -498 -499 # Find openning stall voltage -500 # print('Finding openning stall voltage...') -501 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -502 DAQC2.setDAC(0, 0, maxopen) # set fast close -503 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -504 testopen = maxopen -505 time.sleep(2) -506 atlimit = False -507 while not atlimit: -508 atlimit = barrier_at_limit_check(barriermin, barriermax) -509 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -510 # if position > 6.5: -511 # print ('old: '+str(oldposition)+' position: '+str(position)) -512 if (oldposition - position) < 0.01: -513 # because there can be noise take a new reading and check again -514 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -515 if (oldposition - position) < 0.01: -516 minopen = testopen -517 atlimit = True -518 DAQC2.clrDOUTbit(0, 0) -519 oldposition = position -520 testopen = testopen + 0.05 -521 DAQC2.setDAC(0, 0, testopen) -522 time.sleep(2) -523 -524 # Find minimum opening start voltage -525 # print('Finding minimum openning start voltage...') -526 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -527 startopen = minopen -528 atlimit = False -529 while not atlimit: -530 atlimit = barrier_at_limit_check(barriermin, barriermax) -531 DAQC2.setDAC(0, 0, startopen) -532 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -533 time.sleep(2) -534 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -535 if (oldposition - position) < 0.01: -536 # because there can be noise take a new reading and check again -537 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -538 if (oldposition - position) < 0.01: -539 startopen = startopen - 0.05 -540 else: -541 atlimit = True -542 DAQC2.clrDOUTbit(0, 0) -543 startopen = startopen - 0.05 # To provide a margin. -544 -545 write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen) +263 return V_neg, V_pos +264 +265 def start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, +266 minopenV): +267 """ +268 Will start the barriers including giving a little boost if running +269 voltage is below the starting voltage. +270 """ +271 DAC_V = calcDAC_V(speed, direction, maxcloseV, mincloseV, maxopenV, +272 minopenV) +273 # print("speed:"+str(speed)+", direction:"+str(direction)+", DAC_V:"+str(DAC_V)) +274 if (DAC_V > startopenV) and (DAC_V < startcloseV) and (direction != 0): +275 # need a boost to start moving +276 if speed == 1: +277 DAQC2.setDAC(0, 0, startcloseV) +278 else: +279 DAQC2.setDAC(0, 0, startopenV) +280 DAQC2.setDOUTbit(0, 0) +281 time.sleep(0.25) +282 DAQC2.setDAC(0, 0, DAC_V) +283 DAQC2.setDOUTbit(0, 0) +284 pass +285 +286 def write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen): +287 """Write the motorcal to a file in ~/.Trough/calibrations.""" +288 from AdvancedHTMLParser import AdvancedTag as Domel +289 from datetime import datetime +290 from time import time +291 from pathlib import Path +292 calib_div = Domel('div') +293 calib_title = Domel('h3') +294 calib_title.setAttribute('id','title') +295 calib_title.appendInnerHTML('Calibration of Motor') +296 calib_div.appendChild(calib_title) +297 +298 time_table = Domel('table') +299 time_table.setAttribute('class','time_table') +300 time_table.setAttribute('id', 'time_table') +301 time_table.setAttribute('border', '1') +302 tr = Domel('tr') +303 tr.appendInnerHTML('<th>ISO time</th><th>Timestamp</th>') +304 time_table.append(tr) +305 tr = Domel('tr') +306 timestamp = time() +307 isotime = (datetime.fromtimestamp(timestamp)).isoformat() +308 tr.appendInnerHTML('<td>' + str(isotime) + '</td>' +309 '<td id="timestamp">' + str(timestamp) + '</td>') +310 time_table.append(tr) +311 calib_div.append(time_table) +312 +313 calib_info = Domel('table') +314 calib_info.setAttribute('class', 'calib_info') +315 calib_info.setAttribute('id', 'calib_info') +316 calib_info.setAttribute('border', '1') +317 tr = Domel('tr') +318 tr.appendInnerHTML('<th>Calibration of</th><th>Max Value</th>' +319 '<th>Min Value</th><th>Start Value</th>') +320 calib_info.appendChild(tr) +321 tr = Domel('tr') +322 tr.appendInnerHTML('<th id = "name"> Opening </th>' +323 '<td id = "maxopen">' + str(maxopen) + '</td>' +324 '<td id = "minopen">' + str(minopen) + '</td>' +325 '<td id = "startopen">' + str(startopen) + '</td>') +326 calib_info.appendChild(tr) +327 tr = Domel('tr') +328 tr.appendInnerHTML('<th id = "name"> Closing </th>' +329 '<td id = "maxclose">' + str(maxclose) + '</td>' +330 '<td id = "minclose">' + str(minclose) + '</td>' +331 '<td id = "startclose">' + str(startclose) + '</td>') +332 calib_info.appendChild(tr) +333 calib_div.appendChild(calib_info) +334 +335 +336 fileext = '.trh.cal.html' +337 filename = 'motorcal'+fileext +338 dirpath = Path('~/.Trough/calibrations').expanduser() +339 fullpath = Path(dirpath, filename) +340 svhtml = '<!DOCTYPE html><html><body>' + calib_div.asHTML() + \ +341 '</body></html>' +342 f = open(fullpath, 'w') +343 f.write(svhtml) +344 f.close() +345 pass +346 +347 def read_motorcal(): +348 """Reads the motor calibration file in ~/.Trough/calibrations +349 if it exists and returns the calibration parameters.""" +350 from AdvancedHTMLParser import AdvancedHTMLParser as Parser +351 from pathlib import Path +352 from os.path import exists +353 +354 maxopen = 0 +355 minopen = 0 +356 startopen = 0 +357 maxclose = 0 +358 minclose = 0 +359 startclose = 0 +360 timestamp = 0 +361 +362 filepath = Path('~/.Trough/calibrations/motorcal.trh.cal.html').expanduser() +363 html = "" +364 if exists(filepath): +365 f = open(filepath, 'r') +366 html = f.read() +367 f.close() +368 document = Parser() +369 document.parseStr(html) +370 if document.getElementById('title').text.strip() != 'Calibration of Motor': +371 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp +372 maxclose = float(document.getElementById('maxclose').text) +373 minclose = float(document.getElementById('minclose').text) +374 startclose = float(document.getElementById('startclose').text) +375 maxopen = float(document.getElementById('maxopen').text) +376 minopen = float(document.getElementById('minopen').text) +377 startopen = float(document.getElementById('startopen').text) +378 timestamp = float(document.getElementById('timestamp').text) +379 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp +380 +381 def motorcal(barriermin, barriermax): +382 ''' +383 :param float barriermin: minimum voltage for barrier (do not open more). +384 :param float barriermax: maximum voltage for barrier (do not close more). +385 :returns float maxclose: DAC setting for maximum close speed. +386 :returns float minclose: DAC setting for minimum close speed. +387 :returns float startclose: DAC setting providing minimum voltage to start closing. +388 :returns float maxopen: DAC setting for maximum close speed. +389 :returns float minopen: DAC setting for minimum close speed. +390 :returns float startopen: DAC setting providing minimum voltage to start opening. +391 ''' +392 +393 import os, time +394 # Since this runs in a tight loop needs to take over watching barriers. +395 # Check if a trough controller is registered at /tmp/troughctl.pid. +396 # If one is store it's pid and replace with own. Will revert on exit without +397 # crashing. +398 ctlpid = take_over_barrier_monitoring() +399 +400 # Set base values +401 maxclose = 4 +402 minclose = 2.6 +403 startclose = 2.6 +404 maxopen = 0 +405 minopen = 2.4 +406 startopen = 2.4 +407 # Calibrate the barrier. This assumes a DAQC2 pi-plate is the controlling interface. +408 # First move to fully open. +409 # print('Moving to fully open...') +410 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +411 if position > barriermin: +412 # need to open some +413 atlimit = False +414 DAQC2.setDAC(0, 0, maxopen) # set fast open +415 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +416 time.sleep(1) +417 while not atlimit: +418 atlimit = barrier_at_limit_check(barriermin, barriermax) +419 # Get rid of any hysteresis in the close direction +420 # print('Removing close hysteresis...') +421 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +422 stoppos = position + 0.05 +423 if stoppos > barriermax: +424 stoppos = barriermax +425 DAQC2.setDAC(0, 0, maxclose) # set fast close +426 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +427 time.sleep(1) +428 atlimit = False +429 while not atlimit: +430 atlimit = barrier_at_limit_check(barriermin, stoppos) +431 # Find closing stall voltage +432 # print('Finding closing stall voltage...') +433 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +434 DAQC2.setDAC(0, 0, maxclose) # set fast close +435 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +436 testclose = maxclose +437 time.sleep(2) +438 atlimit = False +439 while not atlimit: +440 atlimit = barrier_at_limit_check(barriermin, barriermax) +441 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +442 # if position > 6.5: +443 # print ('old: '+str(oldposition)+' position: '+str(position)) +444 if (position - oldposition) < 0.01: +445 # because there can be noise take a new reading and check again +446 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +447 if (position - oldposition) < 0.01: +448 minclose = testclose +449 atlimit = True +450 DAQC2.clrDOUTbit(0, 0) +451 oldposition = position +452 testclose = testclose - 0.05 +453 DAQC2.setDAC(0, 0, testclose) +454 time.sleep(2) +455 +456 # Find minimum closing start voltage +457 # print('Finding minimum closing start voltage...') +458 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +459 startclose = minclose +460 atlimit = False +461 while not atlimit: +462 atlimit = barrier_at_limit_check(barriermin, barriermax) +463 DAQC2.setDAC(0, 0, startclose) +464 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +465 time.sleep(2) +466 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +467 if (position - oldposition) < 0.01: +468 # because there can be noise take a new reading and check again +469 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +470 if (position - oldposition) < 0.01: +471 startclose = startclose + 0.05 +472 else: +473 atlimit = True +474 DAQC2.clrDOUTbit(0, 0) +475 startclose = startclose + 0.05 # To provide a margin. +476 # Move to fully closed +477 # print('Moving to fully closed...') +478 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +479 if position < barriermax: +480 # need to close some +481 atlimit = False +482 DAQC2.setDAC(0, 0, maxclose) # set fast close +483 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +484 time.sleep(1) +485 while not atlimit: +486 atlimit = barrier_at_limit_check(barriermin, barriermax) +487 +488 # Get rid of any hysteresis in the open direction +489 # print('Removing open hysteresis...') +490 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +491 stoppos = position - 0.05 +492 if stoppos < barriermin: +493 stoppos = barriermin +494 DAQC2.setDAC(0, 0, maxopen) # set fast close +495 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +496 time.sleep(1) +497 atlimit = False +498 while not atlimit: +499 atlimit = barrier_at_limit_check(stoppos, barriermax) +500 +501 # Find openning stall voltage +502 # print('Finding openning stall voltage...') +503 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +504 DAQC2.setDAC(0, 0, maxopen) # set fast close +505 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +506 testopen = maxopen +507 time.sleep(2) +508 atlimit = False +509 while not atlimit: +510 atlimit = barrier_at_limit_check(barriermin, barriermax) +511 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +512 # if position > 6.5: +513 # print ('old: '+str(oldposition)+' position: '+str(position)) +514 if (oldposition - position) < 0.01: +515 # because there can be noise take a new reading and check again +516 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +517 if (oldposition - position) < 0.01: +518 minopen = testopen +519 atlimit = True +520 DAQC2.clrDOUTbit(0, 0) +521 oldposition = position +522 testopen = testopen + 0.05 +523 DAQC2.setDAC(0, 0, testopen) +524 time.sleep(2) +525 +526 # Find minimum opening start voltage +527 # print('Finding minimum openning start voltage...') +528 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +529 startopen = minopen +530 atlimit = False +531 while not atlimit: +532 atlimit = barrier_at_limit_check(barriermin, barriermax) +533 DAQC2.setDAC(0, 0, startopen) +534 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +535 time.sleep(2) +536 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +537 if (oldposition - position) < 0.01: +538 # because there can be noise take a new reading and check again +539 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +540 if (oldposition - position) < 0.01: +541 startopen = startopen - 0.05 +542 else: +543 atlimit = True +544 DAQC2.clrDOUTbit(0, 0) +545 startopen = startopen - 0.05 # To provide a margin. 546 -547 # Return control to previous trough controller -548 return_barrier_monitoring_to_prev_process(ctlpid) -549 -550 # Return the results -551 return (maxclose, minclose, startclose, maxopen, minopen, startopen) -552 -553 def calcDAC_V(speed, direction, maxclose, minclose, maxopen, minopen): -554 ''' -555 :param float speed: fraction of maximum speed -556 :param int direction: -1 close, 0 don't move, 1 open -557 :param float maxclose: DAC V for maximum close speed -558 :param float minclose: DAC V for minimum close speed -559 :param float maxopen: DAC V for maximum open speed -560 :param float minopen: DAC V for minimum open speed -561 -562 :return float DAC_V: -563 ''' -564 DAC_V = (minclose+minopen)/2 # default to not moving -565 if direction == -1: -566 DAC_V = (maxclose-minclose)*speed + minclose -567 if direction == 1: -568 DAC_V = (maxopen - minopen)*speed + minopen -569 return DAC_V -570 -571 # Take over the barrier monitoring because we are in a tight loop. -572 # Unless some other process tries to access the A-to-D this should -573 # make most of the fault tolerance unnecessary. -574 ctlpid = take_over_barrier_monitoring() -575 # TODO Should this all be wrapped in a try... so that if -576 # anything stops this it gives up barrier monitoring? -577 pos_F = deque(maxlen=20) -578 pos_F_std = deque(maxlen=20) -579 pos_V = deque(maxlen=20) -580 pos_V_std = deque(maxlen=20) -581 bal_V = deque(maxlen=20) -582 bal_std = deque(maxlen=20) -583 therm_V = deque(maxlen=20) -584 therm_std = deque(maxlen=20) -585 time_stamp = deque(maxlen=20) -586 cmd_deque = deque() -587 messages = deque() -588 que_lst = [time_stamp, pos_F, pos_F_std, bal_V, bal_std, therm_V, therm_std, messages] -589 que_lst_labels = ["time stamp", "position fraction", "position standard deviation", -590 "balance voltage", "balance standard deviation", "thermistor voltage", -591 "thermistor standard deviation", "messages"] -592 timedelta = 0.500 # seconds -593 openmin = 0.02 # minimum voltage allowed when opening. -594 openlimit = openmin -595 closemax = 7.40 # maximum voltage allowed when closing. -596 closelimit = closemax -597 # Check that power supply is on. If not we cannot do anything. -598 PS_minus, PS_plus = get_power_supply_volts() -599 if PS_minus > -10 or PS_plus < 10: -600 messages.append("ERROR") -601 messages.append("Power supply appears to be off (or malfunctioning). Try again...") -602 DATAPipe.send(bundle_to_send(que_lst)) -603 # make sure no power to barriers -604 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -605 # Close connections -606 DATAPipe.close() -607 CTLPipe.close() -608 # give up process id -609 return_barrier_monitoring_to_prev_process(ctlpid) -610 run = False -611 exit() -612 messages.append("Checking Motor Calibration. Please wait...") -613 DATAPipe.send(bundle_to_send(que_lst)) -614 messages.clear() -615 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV, lasttime = read_motorcal() -616 # check that the calibration is 12 hours or less old -617 if lasttime < time.time() - 43200: -618 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal(openlimit, closelimit) -619 messages.append("Trough ready") -620 DATAPipe.send(bundle_to_send(que_lst)) -621 messages.clear() -622 speed = 0 # 0 to 1 fraction of maximum speed. -623 direction = 0 # -1 close, 0 don't move, 1 open -624 run = True -625 loopcount = 0 -626 while run: -627 poshigh = [] -628 poslow = [] -629 balhigh = [] -630 ballow = [] -631 thermhigh = [] -632 thermlow = [] -633 -634 starttime = time.time() -635 stopat = starttime + timedelta - 0.200 # leave 200 ms for communications and control -636 #loopcount +=1 -637 #print('Starting a data record: '+str(loopcount)) -638 #itcount = 0 -639 while (time.time() < stopat): -640 tempposlow = None -641 tempposhigh= None -642 tempballow = None -643 tempbalhigh = None -644 tempthermlow = None -645 tempthermhigh = None -646 #itcount +=1 -647 #print(str(itcount)+",",end = "") -648 tempposlow = etol_call(DAQC2.getADC,(0, 1)) -649 temposhigh = etol_call(DAQC2.getADC,(0, 0)) -650 tempballow = etol_call(DAQC2.getADC,(0, 3)) -651 tempbalhigh = etol_call(DAQC2.getADC,(0, 2)) -652 tempthermlow = etol_call(DAQC2.getADC,(0, 4)) -653 tempthermhigh = etol_call(DAQC2.getADC,(0, 5)) -654 datagood = True -655 if isnumber(tempposlow): -656 poslow.append(tempposlow) -657 else: -658 datagood = False -659 if isnumber(temposhigh): -660 poshigh.append(temposhigh) -661 else: -662 datagood = False -663 if isnumber(tempballow): -664 ballow.append(tempballow) -665 else: -666 datagood = False -667 if isnumber(tempbalhigh): -668 balhigh.append(tempbalhigh) -669 else: -670 datagood = False -671 if isnumber(tempthermlow): -672 thermlow.append(tempthermlow) -673 else: -674 datagood = False -675 if isnumber(tempthermhigh): -676 thermhigh.append(tempthermhigh) -677 else: -678 datagood = False -679 if not datagood: -680 return_barrier_monitoring_to_prev_process(ctlpid) -681 raise ValueError('Not getting numeric values from A-to-D!') -682 -683 time_stamp.append((starttime + stopat) / 2) -684 # position -685 #print("poshigh: "+str(poshigh)) -686 ndata = len(poshigh) -687 if ndata >= 1: -688 high = np.array(poshigh, dtype=np.float64) -689 #print("poslow: "+str(poslow)) -690 low = np.array(poslow, dtype=np.float64) -691 vals = high - low -692 avg = (np.sum(vals)) / ndata -693 stdev = np.std(vals, ddof=1, dtype=np.float64) -694 stdev_avg = stdev / np.sqrt(float(ndata)) -695 pos_frac = 1.00 - (avg - openmin)/(closemax - openmin) -696 pos_frac_std = stdev_avg/(closemax - openmin) -697 pos_V.append(avg) -698 pos_V_std.append(stdev_avg) -699 pos_F.append(pos_frac) -700 pos_F_std.append(pos_frac_std) -701 # balance -702 ndata = len(balhigh) -703 high = np.array(balhigh, dtype=np.float64) -704 low = np.array(ballow, dtype=np.float64) -705 vals = high - low -706 avg = (np.sum(vals)) / ndata -707 stdev = np.std(vals, ddof=1, dtype=np.float64) -708 stdev_avg = stdev / np.sqrt(float(ndata)) -709 bal_V.append(avg) -710 bal_std.append(stdev_avg) -711 # thermistor -712 ndata = len(thermhigh) -713 high = np.array(thermhigh, dtype=np.float64) -714 low = np.array(thermlow, dtype=np.float64) -715 vals = high - low -716 avg = (np.sum(vals)) / ndata -717 stdev = np.std(vals, ddof=1, dtype=np.float64) -718 stdev_avg = stdev / np.sqrt(float(ndata)) -719 therm_V.append(avg) -720 therm_std.append(stdev_avg) -721 -722 # Check barrier positions -723 if openlimit < openmin: -724 openlimit = openmin -725 if closelimit > closemax: -726 closelimit = closemax -727 barrier_at_limit = barrier_at_limit_check(openlimit,closelimit) -728 # TODO: Send warnings and error messages -729 # Check command pipe and update command queue -730 #print('Checking commands...') -731 while CTLPipe.poll(): -732 cmd_deque.append(CTLPipe.recv()) -733 # Each command is a python list. -734 # element 1: cmd name (string) -735 # element 2: single command parameter (number or string) -736 # Execute commands in queue -737 #print('Executing commands...') -738 while len(cmd_deque) > 0: -739 cmd = cmd_deque.popleft() -740 # execute the command -741 #print(str(cmd)) -742 if cmd[0] == 'Stop': -743 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -744 elif cmd[0] == 'Send': -745 #print('Got a "Send" command.') -746 # send current contents of the data deques -747 DATAPipe.send(bundle_to_send(que_lst)) -748 # purge the sent content -749 time_stamp.clear() -750 pos_V.clear() -751 pos_V_std.clear() -752 pos_F.clear() -753 pos_F_std.clear() -754 bal_V.clear() -755 bal_std.clear() -756 therm_V.clear() -757 therm_std.clear() -758 messages.clear() -759 elif cmd[0] == 'Start': -760 # start barriers moving using current direction and speed -761 if speed > 1: -762 speed = 1 -763 if speed < 0: -764 speed = 0 -765 if (direction != -1) and (direction != 0) and (direction != 1): -766 direction = 0 -767 closelimit = closemax -768 openlimit = openmin -769 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, -770 minopenV) -771 elif cmd[0] == 'Direction': -772 # set the direction -773 direction = cmd[1] -774 if (direction != -1) and (direction != 0) and (direction != 1): -775 direction = 0 -776 elif cmd[0] == 'Speed': -777 # set the speed -778 speed = cmd[1] -779 if speed > 1: -780 speed = 1 -781 if speed < 0: -782 speed = 0 -783 pass -784 elif cmd[0] == 'MoveTo': -785 # Move to fraction of open 0 .. 1. -786 # set the stop position -787 to_pos = (1.0-float(cmd[1]))*closemax + float(cmd[1])*openmin -788 # adjust direction if necessary -789 # get current position -790 position = etol_call(DAQC2.getADC,(0, 0)) - \ -791 etol_call(DAQC2.getADC,(0, 1)) -792 if position > to_pos: -793 direction = 1 -794 openlimit = to_pos -795 else: -796 direction = -1 -797 closelimit = to_pos -798 # start the barriers -799 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, -800 minopenV) -801 pass -802 elif cmd[0] == 'MotorCal': -803 # calibrate the voltages for starting motor and speeds -804 messages.append("Checking Motor Calibration. Please wait...") -805 DATAPipe.send(bundle_to_send(que_lst)) -806 messages.clear() -807 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal( -808 openlimit, closelimit) -809 messages.append("Trough ready") -810 DATAPipe.send(bundle_to_send(que_lst)) -811 messages.clear() -812 pass -813 elif cmd[0] == 'ConstPi': -814 # maintain a constant pressure -815 # not yet implemented -816 messages.append('Constant pressure mode not yet implemented.') -817 DATAPipe.send(bundle_to_send(que_lst)) -818 messages.clear() -819 pass -820 elif cmd[0] == 'DataLabels': -821 # send data labels as a message -822 messages.append(que_lst_labels) -823 # send current contents of the data deques -824 DATAPipe.send(bundle_to_send(que_lst)) -825 # purge the sent content -826 time_stamp.clear() -827 pos_V.clear() -828 pos_V_std.clear() -829 bal_V.clear() -830 bal_std.clear() -831 therm_V.clear() -832 therm_std.clear() -833 messages.clear() -834 elif cmd[0] == 'ShutDown': -835 # shutdown trough -836 # make sure no power to barriers -837 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -838 # Close connections -839 DATAPipe.close() -840 CTLPipe.close() -841 # give up process id -842 return_barrier_monitoring_to_prev_process(ctlpid) -843 run = False -844 exit() -845 # Delay if have not used up all 200 ms -846 used = time.time() - starttime -847 if used < 0.495: -848 time.sleep(0.495 - used) -849 # shutdown automatically if no communication from controller? +547 write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen) +548 +549 # Return control to previous trough controller +550 return_barrier_monitoring_to_prev_process(ctlpid) +551 +552 # Return the results +553 return (maxclose, minclose, startclose, maxopen, minopen, startopen) +554 +555 def calcDAC_V(speed, direction, maxclose, minclose, maxopen, minopen): +556 ''' +557 :param float speed: fraction of maximum speed +558 :param int direction: -1 close, 0 don't move, 1 open +559 :param float maxclose: DAC V for maximum close speed +560 :param float minclose: DAC V for minimum close speed +561 :param float maxopen: DAC V for maximum open speed +562 :param float minopen: DAC V for minimum open speed +563 +564 :return float DAC_V: +565 ''' +566 DAC_V = (minclose+minopen)/2 # default to not moving +567 if direction == -1: +568 DAC_V = (maxclose-minclose)*speed + minclose +569 if direction == 1: +570 DAC_V = (maxopen - minopen)*speed + minopen +571 return DAC_V +572 +573 # Take over the barrier monitoring because we are in a tight loop. +574 # Unless some other process tries to access the A-to-D this should +575 # make most of the fault tolerance unnecessary. +576 ctlpid = take_over_barrier_monitoring() +577 # TODO Should this all be wrapped in a try... so that if +578 # anything stops this it gives up barrier monitoring? +579 pos_F = deque(maxlen=20) +580 pos_F_std = deque(maxlen=20) +581 pos_V = deque(maxlen=20) +582 pos_V_std = deque(maxlen=20) +583 bal_V = deque(maxlen=20) +584 bal_std = deque(maxlen=20) +585 therm_V = deque(maxlen=20) +586 therm_std = deque(maxlen=20) +587 time_stamp = deque(maxlen=20) +588 cmd_deque = deque() +589 messages = deque() +590 que_lst = [time_stamp, pos_F, pos_F_std, bal_V, bal_std, therm_V, therm_std, messages] +591 que_lst_labels = ["time stamp", "position fraction", "position standard deviation", +592 "balance voltage", "balance standard deviation", "thermistor voltage", +593 "thermistor standard deviation", "messages"] +594 timedelta = 0.500 # seconds +595 openmin = 0.02 # minimum voltage allowed when opening. +596 openlimit = openmin +597 closemax = 7.40 # maximum voltage allowed when closing. +598 closelimit = closemax +599 # Check that power supply is on. If not we cannot do anything. +600 PS_minus, PS_plus = get_power_supply_volts() +601 if PS_minus > -10 or PS_plus < 10: +602 messages.append("ERROR") +603 messages.append("Power supply appears to be off (or malfunctioning). Try again...") +604 DATAPipe.send(bundle_to_send(que_lst)) +605 # make sure no power to barriers +606 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +607 # Close connections +608 DATAPipe.close() +609 CTLPipe.close() +610 # give up process id +611 return_barrier_monitoring_to_prev_process(ctlpid) +612 run = False +613 exit() +614 messages.append("Checking Motor Calibration. Please wait...") +615 DATAPipe.send(bundle_to_send(que_lst)) +616 messages.clear() +617 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV, lasttime = read_motorcal() +618 # check that the calibration is 12 hours or less old +619 if lasttime < time.time() - 43200: +620 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal(openlimit, closelimit) +621 messages.append("Trough ready") +622 DATAPipe.send(bundle_to_send(que_lst)) +623 messages.clear() +624 speed = 0 # 0 to 1 fraction of maximum speed. +625 direction = 0 # -1 close, 0 don't move, 1 open +626 run = True +627 loopcount = 0 +628 while run: +629 poshigh = [] +630 poslow = [] +631 balhigh = [] +632 ballow = [] +633 thermhigh = [] +634 thermlow = [] +635 +636 starttime = time.time() +637 stopat = starttime + timedelta - 0.200 # leave 200 ms for communications and control +638 #loopcount +=1 +639 #print('Starting a data record: '+str(loopcount)) +640 #itcount = 0 +641 while (time.time() < stopat): +642 tempposlow = None +643 tempposhigh= None +644 tempballow = None +645 tempbalhigh = None +646 tempthermlow = None +647 tempthermhigh = None +648 #itcount +=1 +649 #print(str(itcount)+",",end = "") +650 tempposlow = etol_call(DAQC2.getADC,(0, 1)) +651 temposhigh = etol_call(DAQC2.getADC,(0, 0)) +652 tempballow = etol_call(DAQC2.getADC,(0, 3)) +653 tempbalhigh = etol_call(DAQC2.getADC,(0, 2)) +654 tempthermlow = etol_call(DAQC2.getADC,(0, 4)) +655 tempthermhigh = etol_call(DAQC2.getADC,(0, 5)) +656 datagood = True +657 if isnumber(tempposlow): +658 poslow.append(tempposlow) +659 else: +660 datagood = False +661 if isnumber(temposhigh): +662 poshigh.append(temposhigh) +663 else: +664 datagood = False +665 if isnumber(tempballow): +666 ballow.append(tempballow) +667 else: +668 datagood = False +669 if isnumber(tempbalhigh): +670 balhigh.append(tempbalhigh) +671 else: +672 datagood = False +673 if isnumber(tempthermlow): +674 thermlow.append(tempthermlow) +675 else: +676 datagood = False +677 if isnumber(tempthermhigh): +678 thermhigh.append(tempthermhigh) +679 else: +680 datagood = False +681 if not datagood: +682 return_barrier_monitoring_to_prev_process(ctlpid) +683 raise ValueError('Not getting numeric values from A-to-D!') +684 +685 time_stamp.append((starttime + stopat) / 2) +686 # position +687 #print("poshigh: "+str(poshigh)) +688 ndata = len(poshigh) +689 if ndata >= 1: +690 high = np.array(poshigh, dtype=np.float64) +691 #print("poslow: "+str(poslow)) +692 low = np.array(poslow, dtype=np.float64) +693 vals = high - low +694 avg = (np.sum(vals)) / ndata +695 stdev = np.std(vals, ddof=1, dtype=np.float64) +696 stdev_avg = stdev / np.sqrt(float(ndata)) +697 pos_frac = 1.00 - (avg - openmin)/(closemax - openmin) +698 pos_frac_std = stdev_avg/(closemax - openmin) +699 pos_V.append(avg) +700 pos_V_std.append(stdev_avg) +701 pos_F.append(pos_frac) +702 pos_F_std.append(pos_frac_std) +703 # balance +704 ndata = len(balhigh) +705 high = np.array(balhigh, dtype=np.float64) +706 low = np.array(ballow, dtype=np.float64) +707 vals = high - low +708 avg = (np.sum(vals)) / ndata +709 stdev = np.std(vals, ddof=1, dtype=np.float64) +710 stdev_avg = stdev / np.sqrt(float(ndata)) +711 bal_V.append(avg) +712 bal_std.append(stdev_avg) +713 # thermistor +714 ndata = len(thermhigh) +715 high = np.array(thermhigh, dtype=np.float64) +716 low = np.array(thermlow, dtype=np.float64) +717 vals = high - low +718 avg = (np.sum(vals)) / ndata +719 stdev = np.std(vals, ddof=1, dtype=np.float64) +720 stdev_avg = stdev / np.sqrt(float(ndata)) +721 therm_V.append(avg) +722 therm_std.append(stdev_avg) +723 +724 # Check barrier positions +725 if openlimit < openmin: +726 openlimit = openmin +727 if closelimit > closemax: +728 closelimit = closemax +729 barrier_at_limit = barrier_at_limit_check(openlimit,closelimit) +730 # TODO: Send warnings and error messages +731 # Check command pipe and update command queue +732 #print('Checking commands...') +733 while CTLPipe.poll(): +734 cmd_deque.append(CTLPipe.recv()) +735 # Each command is a python list. +736 # element 1: cmd name (string) +737 # element 2: single command parameter (number or string) +738 # Execute commands in queue +739 #print('Executing commands...') +740 while len(cmd_deque) > 0: +741 cmd = cmd_deque.popleft() +742 # execute the command +743 #print(str(cmd)) +744 if cmd[0] == 'Stop': +745 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +746 elif cmd[0] == 'Send': +747 #print('Got a "Send" command.') +748 # send current contents of the data deques +749 DATAPipe.send(bundle_to_send(que_lst)) +750 # purge the sent content +751 time_stamp.clear() +752 pos_V.clear() +753 pos_V_std.clear() +754 pos_F.clear() +755 pos_F_std.clear() +756 bal_V.clear() +757 bal_std.clear() +758 therm_V.clear() +759 therm_std.clear() +760 messages.clear() +761 elif cmd[0] == 'Start': +762 # start barriers moving using current direction and speed +763 if speed > 1: +764 speed = 1 +765 if speed < 0: +766 speed = 0 +767 if (direction != -1) and (direction != 0) and (direction != 1): +768 direction = 0 +769 closelimit = closemax +770 openlimit = openmin +771 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, +772 minopenV) +773 elif cmd[0] == 'Direction': +774 # set the direction +775 direction = cmd[1] +776 if (direction != -1) and (direction != 0) and (direction != 1): +777 direction = 0 +778 elif cmd[0] == 'Speed': +779 # set the speed +780 speed = cmd[1] +781 if speed > 1: +782 speed = 1 +783 if speed < 0: +784 speed = 0 +785 pass +786 elif cmd[0] == 'MoveTo': +787 # Move to fraction of open 0 .. 1. +788 # set the stop position +789 to_pos = (1.0-float(cmd[1]))*closemax + float(cmd[1])*openmin +790 # adjust direction if necessary +791 # get current position +792 position = etol_call(DAQC2.getADC,(0, 0)) - \ +793 etol_call(DAQC2.getADC,(0, 1)) +794 if position > to_pos: +795 direction = 1 +796 openlimit = to_pos +797 else: +798 direction = -1 +799 closelimit = to_pos +800 # start the barriers +801 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, +802 minopenV) +803 pass +804 elif cmd[0] == 'MotorCal': +805 # calibrate the voltages for starting motor and speeds +806 messages.append("Checking Motor Calibration. Please wait...") +807 DATAPipe.send(bundle_to_send(que_lst)) +808 messages.clear() +809 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal( +810 openlimit, closelimit) +811 messages.append("Trough ready") +812 DATAPipe.send(bundle_to_send(que_lst)) +813 messages.clear() +814 pass +815 elif cmd[0] == 'ConstPi': +816 # maintain a constant pressure +817 # not yet implemented +818 messages.append('Constant pressure mode not yet implemented.') +819 DATAPipe.send(bundle_to_send(que_lst)) +820 messages.clear() +821 pass +822 elif cmd[0] == 'DataLabels': +823 # send data labels as a message +824 messages.append(que_lst_labels) +825 # send current contents of the data deques +826 DATAPipe.send(bundle_to_send(que_lst)) +827 # purge the sent content +828 time_stamp.clear() +829 pos_V.clear() +830 pos_V_std.clear() +831 bal_V.clear() +832 bal_std.clear() +833 therm_V.clear() +834 therm_std.clear() +835 messages.clear() +836 elif cmd[0] == 'ShutDown': +837 # shutdown trough +838 # make sure no power to barriers +839 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +840 # Close connections +841 DATAPipe.close() +842 CTLPipe.close() +843 # give up process id +844 return_barrier_monitoring_to_prev_process(ctlpid) +845 run = False +846 exit() +847 # Delay if have not used up all 200 ms +848 used = time.time() - starttime +849 if used < 0.495: +850 time.sleep(0.495 - used) +851 # shutdown automatically if no communication from controller?API Documentation
- + built with pdocAPI Documentation - + built with pdoc 69 self.livefig.update_xaxes(title="$Area\,(cm^2)$") 70 if self.units == 'Angstrom^2/molec': 71 x_data = self.df['Area per molecule (A^2)'] - 72 self.livefig.update_xaxes(title="$Area per molecule ({\overset{\circ}{A}}^2)$") - 73 else: - 74 x_data = self.df['time_stamp']-self.df['time_stamp'][0] - 75 self.livefig.add_scatter(y=self.df['Surface Pressure (mN/m)'], x=x_data) - 76 - 77 @classmethod - 78 def from_html(self, html): - 79 """Create a run from an html representation - 80 Parameters - 81 ---------- - 82 html: str - 83 The html to be parsed to create the run object - 84 - 85 Returns - 86 ------- - 87 trough_run: trough_run - 88 A trough_run object - 89 """ - 90 from AdvancedHTMLParser import AdvancedHTMLParser as Parser - 91 from pandas import read_html - 92 # parse the document - 93 document = Parser() - 94 document.parseStr(html) - 95 id = int(document.getElementById('id').text) - 96 filename = document.getElementById('filename').text - 97 title = document.getElementById('title').text - 98 units = document.getElementById('units').text - 99 target = float(document.getElementById('target').text) -100 speed = float(document.getElementById('speed').text) -101 moles = float(document.getElementById('moles').text) -102 plate_circ = float(document.getElementById('plate_circ').text) -103 dataframe = read_html(html, index_col=0, attrs = {'id': 'run_data'})[0] -104 timestamp = float(document.getElementById('timestamp').text) -105 # return as run object -106 return trough_run(id, filename, title, units, target, speed, moles, -107 plate_circ, dataframe, timestamp) -108 -109 def run_caption(self): -110 """Returns an html table with info about the run to use as a caption""" -111 caption = '' -112 caption += '<table><tr><th>Run ID</th><th>Title</th>' -113 caption += '<th>Storage File Name</th>' -114 caption += '<th>Target (' + str(self.units) + ')</th>' -115 caption += '<th>Speed (' + str(self.units) + '/min)</th>' -116 caption += '<th>Moles</th><th>Plate Circumference (mm)</th></tr>' -117 caption += '<tr><td>' + str(self.id) + '</td>' -118 caption += '<td>' + str(self.title) + '</td>' -119 caption += '<td>' + str(self.filename) + '</td>' -120 caption += '<td>' + str(self.target) + '</td>' -121 caption += '<td>' + str(self.speed) + '</td>' -122 caption += '<td>' + str(self.moles) + '</td>' -123 caption += '<td>' + str(self.plate_circ) + '</td></tr>' -124 caption += '</table>' -125 return caption -126 -127 def _end_of_run(self): -128 from IPython import get_ipython -129 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -130 # This hides the control stuff -131 self.close_collect_control() -132 # Store data -133 self.write_run('') -134 # start background updating -135 if not Trough_GUI.updater_running.value: -136 Trough_GUI.run_updater.value = True -137 Trough_GUI.start_status_updater() -138 return -139 -140 def init_collect_control(self): -141 """This initializes the collection control widgets and VBox that -142 contains them. The VBox may be accessed as `self.collect_control`""" -143 from ipywidgets import Button, HBox, VBox -144 from IPython import get_ipython -145 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -146 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep -147 Bar_Area = Trough_GUI.status_widgets.Bar_Area -148 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec -149 degC = Trough_GUI.status_widgets.degC -150 surf_press = Trough_GUI.status_widgets.surf_press -151 Barr_Units = Trough_GUI.command_widgets.Barr_Units -152 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed -153 Barr_Target = Trough_GUI.command_widgets.Barr_Target -154 -155 run_start_stop = Button(description="Run", -156 button_style="success") -157 -158 def _on_run_start_stop(change): -159 from threading import Thread -160 from numpy import sign -161 from IPython import get_ipython -162 from Trough_GUI.conversions import sqcm_to_cm, angpermolec_to_sqcm -163 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -164 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep -165 Bar_Area = Trough_GUI.status_widgets.Bar_Area -166 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec -167 moles_molec = Trough_GUI.status_widgets.moles_molec -168 Barr_Units = Trough_GUI.command_widgets.Barr_Units -169 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed -170 Barr_Target = Trough_GUI.command_widgets.Barr_Target -171 # Check if we are stopping a run -172 if run_start_stop.description == "Stop": -173 _on_stop_run() -174 return -175 # take over status updating -176 # First shutdown currently running updater -177 if Trough_GUI.updater_running.value: -178 Trough_GUI.run_updater.value = False -179 while Trough_GUI.updater_running.value: -180 # wait -181 pass -182 # Start run -183 # Convert "Start" to "Stop" button -184 Trough_GUI.run_updater.value = True -185 run_start_stop.description = "Stop" -186 run_start_stop.button_style = "danger" -187 # start barriers -188 trough_lock.acquire() -189 direction = 0 -190 tempspeed = 0 -191 temp_targ = 0 -192 speed = 0 -193 skimmer_correction = float( -194 Trough_GUI.calibrations.barriers_open.additional_data[ -195 "skimmer correction (cm^2)"]) -196 width = float(Trough_GUI.calibrations.barriers_open.additional_data[ -197 "trough width (cm)"]) -198 target_speed = float(Barr_Speed.value) -199 if Barr_Units.value == 'cm': -200 direction = int( -201 sign(float(Barr_Target.value) - float(Bar_Sep.value))) -202 tempspeed = target_speed -203 temp_targ = float(Barr_Target.value) -204 elif Barr_Units.value == "cm^2": -205 direction = int( -206 sign(float(Barr_Target.value) - float(Bar_Area.value))) -207 tempspeed = (target_speed - skimmer_correction) / width -208 temp_targ = \ -209 sqcm_to_cm(float(Barr_Target.value), 0, -210 Trough_GUI.calibrations)[0] -211 elif Barr_Units.value == "Angstrom^2/molec": -212 direction = int(sign( -213 float(Barr_Target.value) - float(Bar_Area_per_Molec.value))) -214 tempspeed = (target_speed - skimmer_correction) / width / 1e16 * float( -215 moles_molec.value) * 6.02214076e23 -216 temp_targ = sqcm_to_cm( -217 *angpermolec_to_sqcm(float(Barr_Target.value), 0, -218 float(moles_molec.value)), -219 Trough_GUI.calibrations)[0] -220 if direction < 0: -221 target = \ -222 Trough_GUI.calibrations.barriers_close.cal_inv( -223 float(temp_targ), 0)[ -224 0] -225 speed = \ -226 Trough_GUI.calibrations.speed_close.cal_inv(tempspeed, 0)[0] -227 else: -228 target = \ -229 Trough_GUI.calibrations.barriers_open.cal_inv( -230 float(temp_targ), 0)[ -231 0] -232 speed = \ -233 Trough_GUI.calibrations.speed_open.cal_inv(tempspeed, 0)[0] -234 cmdsend.send(['Speed', speed]) -235 cmdsend.send(['Direction', direction]) -236 cmdsend.send(['MoveTo', target]) -237 trough_lock.release() -238 # display data as collected and periodically update status widgets -239 # spawn updater thread that updates both status widgets and the figure. -240 updater = Thread(target=collect_data_updater, -241 args=(trough_lock, cmdsend, -242 datarcv, -243 Trough_GUI.calibrations, -244 Trough_GUI.lastdirection, -245 Trough_GUI.run_updater, -246 Trough_GUI.updater_running, -247 self)) -248 updater.start() -249 return -250 -251 def _on_stop_run(): -252 from IPython import get_ipython -253 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -254 run_start_stop.description = "Done" -255 run_start_stop.disabled = True -256 run_start_stop.button_style = "" -257 # Stop run -258 if Trough_GUI.updater_running.value: -259 Trough_GUI.run_updater.value = False -260 # when collection stops it will call _end_of_run. -261 return -262 -263 run_start_stop.on_click(_on_run_start_stop) -264 -265 run_start_stop.description = "Run" -266 run_start_stop.disabled = False -267 run_start_stop.button_style = "success" -268 collect_control1 = HBox([surf_press, run_start_stop]) -269 position = None -270 x_units = Barr_Units.value -271 if Barr_Units.value == 'cm': -272 position = Bar_Sep -273 elif Barr_Units.value == 'cm^2': -274 position = Bar_Area -275 x_units = "$cm^2$" -276 elif Barr_Units.value == 'Angstrom^2/molec': -277 position = Bar_Area_per_Molec -278 x_units = "$Area\,per\,molecule\,({\overset{\circ}{A}}^2)$" -279 collect_control2 = HBox([degC, position]) -280 self.collect_control = VBox([collect_control1, collect_control2]) -281 x_min = 0 -282 x_max = 1 -283 if float(Barr_Target.value) < float(position.value): -284 x_min = 0.98 * float(Barr_Target.value) -285 x_max = 1.02 * float(position.value) -286 else: -287 x_min = 0.98 * float(position.value) -288 x_max = 1.02 * float(Barr_Target.value) -289 if float(Barr_Speed.value) == 0: -290 x_units = 'Time (s)' -291 x_min = 0 -292 x_max = 600 # 10 minutes -293 self.livefig.update_yaxes(title="$\Pi\,(mN/m)$", -294 range=[0, 60]) -295 self.livefig.update_xaxes(title=x_units, -296 range=[x_min, x_max]) -297 self.livefig.add_scatter(x=[], y=[]) -298 return -299 -300 def close_collect_control(self): -301 """This closes `self.collect_control` which also minimizes -302 the objects maintained on the Python side.""" -303 self.collect_control.close() -304 self.collect_control = None -305 return -306 -307 def __repr__(self): -308 from IPython.display import HTML, display -309 display(self.livefig) -310 display(HTML(self.run_caption())) -311 return '' -312 -313 def to_html(self): -314 """Create an html string representing a run""" -315 from AdvancedHTMLParser import AdvancedTag as Domel -316 # create the html -317 run_div = Domel('div') -318 run_info = Domel('table') -319 run_info.setAttribute('class','run_info') -320 run_info.setAttribute('id','run_info') -321 run_info.setAttribute('border','1') -322 tr = Domel('tr') -323 tr.appendInnerHTML('<th>ID</th><th>Filename</th><th>Title</th><th>Units' -324 '</th><th>Target</th><th>Speed</th><th>Moles</th>' -325 '<th>Plate Circumference (mm)</th><th>Time</th><th>Time ' -326 'Stamp</th>') -327 run_info.appendChild(tr) -328 tr = Domel('tr') -329 tr.appendInnerHTML('<td id="id">'+str(self.id)+'</td>' -330 '<td id="filename">'+str(self.filename) + '</td>' -331 '<td id="title">'+str(self.title) + '</td>' -332 '<td id="units">' + str(self.units)+'</td>' -333 '<td id="target">'+str(self.target) +'</td>' -334 '<td id="speed">'+str(self.speed)+'</td>' -335 '<td id="moles">' + str(self.moles)+'</td>' -336 '<td id="plate_circ">'+str(self.plate_circ) + '</td>' -337 '<td id="datestr">'+str(self.datestr)+'</td>' -338 '<td id="timestamp">' + str(self.timestamp)+'</td>') -339 run_info.appendChild(tr) -340 run_div.appendChild(run_info) -341 dfstr = self.df.to_html(table_id="run_data") -342 run_div.appendInnerHTML('<!--placeholder to avoid bug that strips table tag-->\n'+dfstr+'\n<!--avoid tag loss-->\n') -343 return run_div.asHTML() -344 -345 def write_run(self, dirpath, **kwargs): -346 """ -347 Writes a run file with the base filename `run.filename` into the -348 directory specified. If a file with the current name exists -349 attempts to make the name unique by appending self.timestamp -350 to the filename. Currently only produces -351 an html file that is also human-readable. Other file formats may be -352 available in the future through the use of key word arguments. -353 -354 Parameters -355 ---------- -356 dirpath: -357 pathlike object or string. Empty string means the current working -358 directory. -359 -360 kwargs: -361 optional key word arguments for future adaptability -362 """ -363 from pathlib import Path -364 from warnings import warn -365 fileext = '.trh.run.html' -366 filename = str(self.filename) -367 fullpath = Path(dirpath, filename) -368 if fullpath.exists(): -369 basename = self.filename -370 for k in Path(self.filename).suffixes: -371 basename = basename.replace(k, '') -372 if basename.split("_")[-1].isnumeric(): -373 split = basename.split("_") -374 if int(split[-1]) == self.timestamp: -375 warn("Run file not written as it already exists.") -376 return -377 basename = '' -378 for k in range(0,len(split)-1): -379 basename += split[k] +"_" -380 filename = basename + str(int(self.timestamp)) + fileext -381 self.filename = filename -382 self.write_run(dirpath) -383 return -384 svhtml = '<!DOCTYPE html><html><body>' + self.to_html() + \ -385 '</body></html>' -386 f = open(fullpath, 'w') -387 f.write(svhtml) -388 f.close() -389 return -390 + 72 self.livefig.update_xaxes(title="$Area\,per\," + 73 "molecule\,({\overset{\circ}{A}}^2)$") + 74 else: + 75 x_data = self.df['time_stamp']-self.df['time_stamp'][0] + 76 self.livefig.add_scatter(y=self.df['Surface Pressure (mN/m)'], x=x_data) + 77 + 78 @classmethod + 79 def from_html(self, html): + 80 """Create a run from an html representation + 81 Parameters + 82 ---------- + 83 html: str + 84 The html to be parsed to create the run object + 85 + 86 Returns + 87 ------- + 88 trough_run: trough_run + 89 A trough_run object + 90 """ + 91 from AdvancedHTMLParser import AdvancedHTMLParser as Parser + 92 from pandas import read_html + 93 # parse the document + 94 document = Parser() + 95 document.parseStr(html) + 96 id = int(document.getElementById('id').text) + 97 filename = document.getElementById('filename').text + 98 title = document.getElementById('title').text + 99 units = document.getElementById('units').text +100 target = float(document.getElementById('target').text) +101 speed = float(document.getElementById('speed').text) +102 moles = float(document.getElementById('moles').text) +103 plate_circ = float(document.getElementById('plate_circ').text) +104 dataframe = read_html(html, index_col=0, attrs = {'id': 'run_data'})[0] +105 timestamp = float(document.getElementById('timestamp').text) +106 # return as run object +107 return trough_run(id, filename, title, units, target, speed, moles, +108 plate_circ, dataframe, timestamp) +109 +110 def run_caption(self): +111 """Returns an html table with info about the run to use as a caption""" +112 caption = '' +113 caption += '<table><tr><th>Run ID</th><th>Title</th>' +114 caption += '<th>Storage File Name</th>' +115 caption += '<th>Target (' + str(self.units) + ')</th>' +116 caption += '<th>Speed (' + str(self.units) + '/min)</th>' +117 caption += '<th>Moles</th><th>Plate Circumference (mm)</th></tr>' +118 caption += '<tr><td>' + str(self.id) + '</td>' +119 caption += '<td>' + str(self.title) + '</td>' +120 caption += '<td>' + str(self.filename) + '</td>' +121 caption += '<td>' + str(self.target) + '</td>' +122 caption += '<td>' + str(self.speed) + '</td>' +123 caption += '<td>' + str(self.moles) + '</td>' +124 caption += '<td>' + str(self.plate_circ) + '</td></tr>' +125 caption += '</table>' +126 return caption +127 +128 def _end_of_run(self): +129 from IPython import get_ipython +130 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +131 # This hides the control stuff +132 self.close_collect_control() +133 # Store data +134 self.write_run('') +135 # start background updating +136 if not Trough_GUI.updater_running.value: +137 Trough_GUI.run_updater.value = True +138 Trough_GUI.start_status_updater() +139 return +140 +141 def init_collect_control(self): +142 """This initializes the collection control widgets and VBox that +143 contains them. The VBox may be accessed as `self.collect_control`""" +144 from ipywidgets import Button, HBox, VBox +145 from IPython import get_ipython +146 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +147 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep +148 Bar_Area = Trough_GUI.status_widgets.Bar_Area +149 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec +150 degC = Trough_GUI.status_widgets.degC +151 surf_press = Trough_GUI.status_widgets.surf_press +152 Barr_Units = Trough_GUI.command_widgets.Barr_Units +153 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed +154 Barr_Target = Trough_GUI.command_widgets.Barr_Target +155 +156 run_start_stop = Button(description="Run", +157 button_style="success") +158 +159 def _on_run_start_stop(change): +160 from threading import Thread +161 from numpy import sign +162 from IPython import get_ipython +163 from Trough_GUI.conversions import sqcm_to_cm, angpermolec_to_sqcm +164 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +165 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep +166 Bar_Area = Trough_GUI.status_widgets.Bar_Area +167 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec +168 moles_molec = Trough_GUI.status_widgets.moles_molec +169 Barr_Units = Trough_GUI.command_widgets.Barr_Units +170 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed +171 Barr_Target = Trough_GUI.command_widgets.Barr_Target +172 # Check if we are stopping a run +173 if run_start_stop.description == "Stop": +174 _on_stop_run() +175 return +176 # take over status updating +177 # First shutdown currently running updater +178 if Trough_GUI.updater_running.value: +179 Trough_GUI.run_updater.value = False +180 while Trough_GUI.updater_running.value: +181 # wait +182 pass +183 # Start run +184 # Convert "Start" to "Stop" button +185 Trough_GUI.run_updater.value = True +186 run_start_stop.description = "Stop" +187 run_start_stop.button_style = "danger" +188 # start barriers +189 trough_lock.acquire() +190 direction = 0 +191 tempspeed = 0 +192 temp_targ = 0 +193 speed = 0 +194 skimmer_correction = float( +195 Trough_GUI.calibrations.barriers_open.additional_data[ +196 "skimmer correction (cm^2)"]) +197 width = float(Trough_GUI.calibrations.barriers_open.additional_data[ +198 "trough width (cm)"]) +199 target_speed = float(Barr_Speed.value) +200 if Barr_Units.value == 'cm': +201 direction = int( +202 sign(float(Barr_Target.value) - float(Bar_Sep.value))) +203 tempspeed = target_speed +204 temp_targ = float(Barr_Target.value) +205 elif Barr_Units.value == "cm^2": +206 direction = int( +207 sign(float(Barr_Target.value) - float(Bar_Area.value))) +208 tempspeed = (target_speed - skimmer_correction) / width +209 temp_targ = \ +210 sqcm_to_cm(float(Barr_Target.value), 0, +211 Trough_GUI.calibrations)[0] +212 elif Barr_Units.value == "Angstrom^2/molec": +213 direction = int(sign( +214 float(Barr_Target.value) - float(Bar_Area_per_Molec.value))) +215 tempspeed = (target_speed - skimmer_correction) / width / 1e16 * float( +216 moles_molec.value) * 6.02214076e23 +217 temp_targ = sqcm_to_cm( +218 *angpermolec_to_sqcm(float(Barr_Target.value), 0, +219 float(moles_molec.value)), +220 Trough_GUI.calibrations)[0] +221 if direction < 0: +222 target = \ +223 Trough_GUI.calibrations.barriers_close.cal_inv( +224 float(temp_targ), 0)[ +225 0] +226 speed = \ +227 Trough_GUI.calibrations.speed_close.cal_inv(tempspeed, 0)[0] +228 else: +229 target = \ +230 Trough_GUI.calibrations.barriers_open.cal_inv( +231 float(temp_targ), 0)[ +232 0] +233 speed = \ +234 Trough_GUI.calibrations.speed_open.cal_inv(tempspeed, 0)[0] +235 cmdsend.send(['Speed', speed]) +236 cmdsend.send(['Direction', direction]) +237 cmdsend.send(['MoveTo', target]) +238 trough_lock.release() +239 # display data as collected and periodically update status widgets +240 # spawn updater thread that updates both status widgets and the figure. +241 updater = Thread(target=collect_data_updater, +242 args=(trough_lock, cmdsend, +243 datarcv, +244 Trough_GUI.calibrations, +245 Trough_GUI.lastdirection, +246 Trough_GUI.run_updater, +247 Trough_GUI.updater_running, +248 self)) +249 updater.start() +250 return +251 +252 def _on_stop_run(): +253 from IPython import get_ipython +254 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +255 run_start_stop.description = "Done" +256 run_start_stop.disabled = True +257 run_start_stop.button_style = "" +258 # Stop run +259 if Trough_GUI.updater_running.value: +260 Trough_GUI.run_updater.value = False +261 # when collection stops it will call _end_of_run. +262 return +263 +264 run_start_stop.on_click(_on_run_start_stop) +265 +266 run_start_stop.description = "Run" +267 run_start_stop.disabled = False +268 run_start_stop.button_style = "success" +269 collect_control1 = HBox([surf_press, run_start_stop]) +270 position = None +271 x_units = Barr_Units.value +272 if Barr_Units.value == 'cm': +273 position = Bar_Sep +274 elif Barr_Units.value == 'cm^2': +275 position = Bar_Area +276 x_units = "$cm^2$" +277 elif Barr_Units.value == 'Angstrom^2/molec': +278 position = Bar_Area_per_Molec +279 x_units = "$Area\,per\,molecule\,({\overset{\circ}{A}}^2)$" +280 collect_control2 = HBox([degC, position]) +281 self.collect_control = VBox([collect_control1, collect_control2]) +282 x_min = 0 +283 x_max = 1 +284 if float(Barr_Target.value) < float(position.value): +285 x_min = 0.98 * float(Barr_Target.value) +286 x_max = 1.02 * float(position.value) +287 else: +288 x_min = 0.98 * float(position.value) +289 x_max = 1.02 * float(Barr_Target.value) +290 if float(Barr_Speed.value) == 0: +291 x_units = 'Time (s)' +292 x_min = 0 +293 x_max = 600 # 10 minutes +294 self.livefig.update_yaxes(title="$\Pi\,(mN/m)$", +295 range=[0, 60]) +296 self.livefig.update_xaxes(title=x_units, +297 range=[x_min, x_max]) +298 self.livefig.add_scatter(x=[], y=[]) +299 return +300 +301 def close_collect_control(self): +302 """This closes `self.collect_control` which also minimizes +303 the objects maintained on the Python side.""" +304 self.collect_control.close() +305 self.collect_control = None +306 return +307 +308 def __repr__(self): +309 from IPython.display import HTML, display +310 display(self.livefig) +311 display(HTML(self.run_caption())) +312 return '' +313 +314 def to_html(self): +315 """Create an html string representing a run""" +316 from AdvancedHTMLParser import AdvancedTag as Domel +317 # create the html +318 run_div = Domel('div') +319 run_info = Domel('table') +320 run_info.setAttribute('class','run_info') +321 run_info.setAttribute('id','run_info') +322 run_info.setAttribute('border','1') +323 tr = Domel('tr') +324 tr.appendInnerHTML('<th>ID</th><th>Filename</th><th>Title</th><th>Units' +325 '</th><th>Target</th><th>Speed</th><th>Moles</th>' +326 '<th>Plate Circumference (mm)</th><th>Time</th><th>Time ' +327 'Stamp</th>') +328 run_info.appendChild(tr) +329 tr = Domel('tr') +330 tr.appendInnerHTML('<td id="id">'+str(self.id)+'</td>' +331 '<td id="filename">'+str(self.filename) + '</td>' +332 '<td id="title">'+str(self.title) + '</td>' +333 '<td id="units">' + str(self.units)+'</td>' +334 '<td id="target">'+str(self.target) +'</td>' +335 '<td id="speed">'+str(self.speed)+'</td>' +336 '<td id="moles">' + str(self.moles)+'</td>' +337 '<td id="plate_circ">'+str(self.plate_circ) + '</td>' +338 '<td id="datestr">'+str(self.datestr)+'</td>' +339 '<td id="timestamp">' + str(self.timestamp)+'</td>') +340 run_info.appendChild(tr) +341 run_div.appendChild(run_info) +342 dfstr = self.df.to_html(table_id="run_data") +343 run_div.appendInnerHTML('<!--placeholder to avoid bug that strips table tag-->\n'+dfstr+'\n<!--avoid tag loss-->\n') +344 return run_div.asHTML() +345 +346 def write_run(self, dirpath, **kwargs): +347 """ +348 Writes a run file with the base filename `run.filename` into the +349 directory specified. If a file with the current name exists +350 attempts to make the name unique by appending self.timestamp +351 to the filename. Currently only produces +352 an html file that is also human-readable. Other file formats may be +353 available in the future through the use of key word arguments. +354 +355 Parameters +356 ---------- +357 dirpath: +358 pathlike object or string. Empty string means the current working +359 directory. +360 +361 kwargs: +362 optional key word arguments for future adaptability +363 """ +364 from pathlib import Path +365 from warnings import warn +366 fileext = '.trh.run.html' +367 filename = str(self.filename) +368 fullpath = Path(dirpath, filename) +369 if fullpath.exists(): +370 basename = self.filename +371 for k in Path(self.filename).suffixes: +372 basename = basename.replace(k, '') +373 if basename.split("_")[-1].isnumeric(): +374 split = basename.split("_") +375 if int(split[-1]) == self.timestamp: +376 warn("Run file not written as it already exists.") +377 return +378 basename = '' +379 for k in range(0,len(split)-1): +380 basename += split[k] +"_" +381 filename = basename + str(int(self.timestamp)) + fileext +382 self.filename = filename +383 self.write_run(dirpath) +384 return +385 svhtml = '<!DOCTYPE html><html><body>' + self.to_html() + \ +386 '</body></html>' +387 f = open(fullpath, 'w') +388 f.write(svhtml) +389 f.close() +390 return 391 392 -393def Run(run_name): -394 """ -395 This routine creates a GUI for initializing, starting, collecting and -396 completing a run. If the run has been completed it will simply reload it -397 from the local datafile. -398 -399 Parameters -400 ---------- -401 run_name: str or Path -402 This should generally be the name for the file the data will be stored in -403 without a file type extension. Recommend a naming scheme that produces -404 Unique filenames, such as `Trough_run_<username>_<timestamp>`. The file -405 name will be `run_name.trh.run.html`. -406 """ -407 from pathlib import Path -408 from ipywidgets import Text, Dropdown, HBox, VBox, Accordion, Label, Button -409 from IPython.display import display -410 from IPython import get_ipython -411 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -412 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep -413 Bar_Area = Trough_GUI.status_widgets.Bar_Area -414 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec -415 degC = Trough_GUI.status_widgets.degC -416 surf_press = Trough_GUI.status_widgets.surf_press -417 zero_press = Trough_GUI.status_widgets.zero_press -418 plate_circumference = Trough_GUI.status_widgets.plate_circumference -419 moles_molec = Trough_GUI.status_widgets.moles_molec -420 Barr_Units = Trough_GUI.command_widgets.Barr_Units -421 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed -422 Barr_Target = Trough_GUI.command_widgets.Barr_Target -423 name_to_run = {} -424 completed_runs = [] -425 for k in Trough_GUI.runs: -426 name_to_run[k.title]= k -427 completed_runs.append(k.title) -428 # Check if run completed, if so reload data, display and exit -429 # TODO figure out how to handle ID# if loaded into different index -430 datafilepath = Path.cwd()/Path(str(run_name) + '.trh.run.html') # or should it be gz -431 if datafilepath.exists(): -432 # display the data as a live plotly plot. -433 f = open(datafilepath,'r') -434 html = f.read() -435 f.close() -436 Trough_GUI.runs.append(Trough_GUI.Collect_data.trough_run.from_html(html)) -437 display(Trough_GUI.runs[-1]) -438 return -439 # Build run setup GUI -440 run_title = Text(description = "Run Title", -441 value = str(run_name), -442 disabled = False) -443 def changed_base_on(change): -444 # update settings to match the chosen model run -445 if base_on.value == 'None': -446 return -447 modelrun = name_to_run[str(base_on.value)] -448 plate_circumference.value = str(modelrun.plate_circ) -449 moles_molec.value = str(modelrun.moles) -450 Barr_Units.value = str(modelrun.units) -451 Barr_Speed.value = str(modelrun.speed) -452 Barr_Target.value = str(modelrun.target) -453 pass -454 -455 base_on = Dropdown(description = "Base on", -456 options=['None'] + completed_runs, -457 value='None') -458 base_on.observe(changed_base_on, names='value') -459 top_HBox = HBox([run_title, base_on]) -460 # Displayed status widgets -461 status_HBox1 = HBox([Bar_Sep, Bar_Area, Bar_Area_per_Molec]) -462 status_HBox2 = HBox([surf_press, degC]) -463 status_VBox = VBox([status_HBox1, status_HBox2]) -464 status_Acc = Accordion([status_VBox]) -465 status_Acc.set_title(0,"Status") -466 status_Acc.selected_index = 0 -467 # Displayed settings widgets -468 settings_HBox1 = HBox([zero_press, plate_circumference, moles_molec]) -469 settings_HBox2 = HBox([Barr_Units, Barr_Speed]) -470 store_settings = Button(description="Store Settings") -471 -472 def on_store_settings(change): -473 from IPython.display import HTML, clear_output -474 # create the run object -475 id = len(Trough_GUI.runs) -476 Trough_GUI.runs.append(trough_run(id,run_name+'.trh.run.html', run_title.value, -477 Barr_Units.value, -478 float(Barr_Target.value), -479 float(Barr_Speed.value), -480 float(moles_molec.value), -481 float(plate_circumference.value))) -482 # Create the collection display -483 Trough_GUI.runs[-1].init_collect_control() -484 clear_output() -485 display(Trough_GUI.runs[-1].livefig) -486 display(Trough_GUI.runs[-1].collect_control) -487 display(HTML(Trough_GUI.runs[-1].run_caption())) -488 return -489 -490 store_settings.on_click(on_store_settings) -491 settings_HBox3 = HBox([Label("Target", style=longdesc), Barr_Target, -492 store_settings]) -493 settings_VBox = VBox([settings_HBox1,settings_HBox2, settings_HBox3]) -494 settings_Acc = Accordion([settings_VBox]) -495 settings_Acc.set_title(0, "Settings") -496 status_Acc.selected_index = 0 -497 Barr_Target.disabled = False -498 display(top_HBox, status_Acc, settings_Acc) -499 -500 return -501 -502def collect_data_updater(trough_lock, cmdsend, datarcv, cals, lastdirection, -503 run_updater, updater_running, run): -504 """This is run in a separate thread and will update the figure and -505 all status widgets at an interval of 2 seconds or a little longer. While -506 this is running nothing else will be able to talk to the trough. -507 -508 Parameters -509 ---------- -510 trough_lock: threading.lock -511 When acquired this routine will talk to the trough. It is not -512 released until the routine exits to avoid any data loss. It does -513 call the status_widgets updater as often as it can while collecting -514 the data. -515 -516 cmdsend: Pipe -517 End of Pipe to send commands to the Trough. -518 -519 datarcv: Pipe -520 End of Pipe to receive data from the Trough. -521 -522 cals: Trough_GUI.calibrations -523 Used to convert the data to user units. -524 -525 lastdirection: multiprocessing.Value -526 Of type 'i' to indicate last direction the barriers moved. -527 -528 run_updater: multiprocessing.Value -529 Of type 'c_bool'. True if this updater should keep running. -530 -531 updater_running: multiprocessing.Value -532 Of type 'c_bool'. Set to True by this process when it starts -533 and set to False before exiting. -534 -535 run: trough_run -536 This object contains the live figure and the place to store the data. -537 """ -538 import time -539 from IPython import get_ipython -540 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -541 # Set the shared I'm running flag. -542 updater_running.value = True -543 trough_lock.acquire() -544 while run_updater.value: -545 min_next_time = time.time() + 2.0 -546 cmdsend.send(['Send','']) -547 waiting = True -548 while waiting: -549 if datarcv.poll(): -550 datapkg =datarcv.recv() -551 # update figure and then call update_status -552 if len(datapkg[1]) >= 1: -553 update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run) -554 update_dict = {'barr_raw':datapkg[1][-1], -555 'barr_dev':datapkg[2][-1], -556 'bal_raw':datapkg[3][-1], -557 'bal_dev':datapkg[4][-1], -558 'temp_raw':datapkg[5][-1], -559 'temp_dev':datapkg[6][-1], -560 'messages':datapkg[7]} -561 else: -562 # No updated data, so just pass messages -563 update_dict = {'messages':datapkg[7]} -564 Trough_GUI.status_widgets.update_status(update_dict, cals, -565 lastdirection) -566 waiting = False -567 if time.time()< min_next_time: -568 time.sleep(min_next_time - time.time()) -569 # Release lock and set the shared I'm running flag to False before exiting. -570 trough_lock.release() -571 updater_running.value = False -572 run._end_of_run() -573 return -574 -575def update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run): -576 """Updates the graph and the data storage""" -577 from pandas import DataFrame, concat -578 import numpy as np -579 from IPython import get_ipython -580 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -581 plate_circumference = Trough_GUI.status_widgets.plate_circumference -582 moles_molec = Trough_GUI.status_widgets.moles_molec -583 # do all the calculations on the new data -584 time_stamp = np.array(datapkg[0]) -585 pos_raw = np.array(datapkg[1]) -586 pos_raw_stdev = np.array(datapkg[2]) -587 bal_raw = np.array(datapkg[3]) -588 bal_raw_stdev = np.array(datapkg[4]) -589 temp_raw = np.array(datapkg[5]) -590 temp_raw_stdev = np.array(datapkg[6]) -591 sep_cm = None -592 sep_cm_stdev = None -593 if lastdirection.value < 0: -594 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, -595 pos_raw_stdev) -596 else: -597 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, -598 pos_raw_stdev) -599 sep_cm = np.array(sep_cm) -600 sep_cm_stdev = np.array(sep_cm_stdev) -601 area_sqcm = sep_cm*float(cals.barriers_open.additional_data[ \ -602 "trough width (cm)"]) -603 area_sqcm_stdev = sep_cm_stdev*float(cals.barriers_open.additional_data[ \ -604 "trough width (cm)"]) -605 +393 +394def Run(run_name): +395 """ +396 This routine creates a GUI for initializing, starting, collecting and +397 completing a run. If the run has been completed it will simply reload it +398 from the local datafile. +399 +400 Parameters +401 ---------- +402 run_name: str or Path +403 This should generally be the name for the file the data will be stored in +404 without a file type extension. Recommend a naming scheme that produces +405 Unique filenames, such as `Trough_run_<username>_<timestamp>`. The file +406 name will be `run_name.trh.run.html`. +407 """ +408 from pathlib import Path +409 from ipywidgets import Text, Dropdown, HBox, VBox, Accordion, Label, Button +410 from IPython.display import display +411 from IPython import get_ipython +412 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +413 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep +414 Bar_Area = Trough_GUI.status_widgets.Bar_Area +415 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec +416 degC = Trough_GUI.status_widgets.degC +417 surf_press = Trough_GUI.status_widgets.surf_press +418 zero_press = Trough_GUI.status_widgets.zero_press +419 plate_circumference = Trough_GUI.status_widgets.plate_circumference +420 moles_molec = Trough_GUI.status_widgets.moles_molec +421 Barr_Units = Trough_GUI.command_widgets.Barr_Units +422 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed +423 Barr_Target = Trough_GUI.command_widgets.Barr_Target +424 name_to_run = {} +425 completed_runs = [] +426 for k in Trough_GUI.runs: +427 name_to_run[k.title]= k +428 completed_runs.append(k.title) +429 # Check if run completed, if so reload data, display and exit +430 # TODO figure out how to handle ID# if loaded into different index +431 datafilepath = Path.cwd()/Path(str(run_name) + '.trh.run.html') # or should it be gz +432 if datafilepath.exists(): +433 # display the data as a live plotly plot. +434 f = open(datafilepath,'r') +435 html = f.read() +436 f.close() +437 Trough_GUI.runs.append(Trough_GUI.Collect_data.trough_run.from_html(html)) +438 display(Trough_GUI.runs[-1]) +439 return +440 # Build run setup GUI +441 run_title = Text(description = "Run Title", +442 value = str(run_name), +443 disabled = False) +444 def changed_base_on(change): +445 # update settings to match the chosen model run +446 if base_on.value == 'None': +447 return +448 modelrun = name_to_run[str(base_on.value)] +449 plate_circumference.value = str(modelrun.plate_circ) +450 moles_molec.value = str(modelrun.moles) +451 Barr_Units.value = str(modelrun.units) +452 Barr_Speed.value = str(modelrun.speed) +453 Barr_Target.value = str(modelrun.target) +454 pass +455 +456 base_on = Dropdown(description = "Base on", +457 options=['None'] + completed_runs, +458 value='None') +459 base_on.observe(changed_base_on, names='value') +460 top_HBox = HBox([run_title, base_on]) +461 # Displayed status widgets +462 status_HBox1 = HBox([Bar_Sep, Bar_Area, Bar_Area_per_Molec]) +463 status_HBox2 = HBox([surf_press, degC]) +464 status_VBox = VBox([status_HBox1, status_HBox2]) +465 status_Acc = Accordion([status_VBox]) +466 status_Acc.set_title(0,"Status") +467 status_Acc.selected_index = 0 +468 # Displayed settings widgets +469 settings_HBox1 = HBox([zero_press, plate_circumference, moles_molec]) +470 settings_HBox2 = HBox([Barr_Units, Barr_Speed]) +471 store_settings = Button(description="Store Settings") +472 +473 def on_store_settings(change): +474 from IPython.display import HTML, clear_output +475 # create the run object +476 id = len(Trough_GUI.runs) +477 Trough_GUI.runs.append(trough_run(id,run_name+'.trh.run.html', run_title.value, +478 Barr_Units.value, +479 float(Barr_Target.value), +480 float(Barr_Speed.value), +481 float(moles_molec.value), +482 float(plate_circumference.value))) +483 # Create the collection display +484 Trough_GUI.runs[-1].init_collect_control() +485 clear_output() +486 display(Trough_GUI.runs[-1].livefig) +487 display(Trough_GUI.runs[-1].collect_control) +488 display(HTML(Trough_GUI.runs[-1].run_caption())) +489 return +490 +491 store_settings.on_click(on_store_settings) +492 settings_HBox3 = HBox([Label("Target", style=longdesc), Barr_Target, +493 store_settings]) +494 settings_VBox = VBox([settings_HBox1,settings_HBox2, settings_HBox3]) +495 settings_Acc = Accordion([settings_VBox]) +496 settings_Acc.set_title(0, "Settings") +497 status_Acc.selected_index = 0 +498 Barr_Target.disabled = False +499 display(top_HBox, status_Acc, settings_Acc) +500 +501 return +502 +503def collect_data_updater(trough_lock, cmdsend, datarcv, cals, lastdirection, +504 run_updater, updater_running, run): +505 """This is run in a separate thread and will update the figure and +506 all status widgets at an interval of 2 seconds or a little longer. While +507 this is running nothing else will be able to talk to the trough. +508 +509 Parameters +510 ---------- +511 trough_lock: threading.lock +512 When acquired this routine will talk to the trough. It is not +513 released until the routine exits to avoid any data loss. It does +514 call the status_widgets updater as often as it can while collecting +515 the data. +516 +517 cmdsend: Pipe +518 End of Pipe to send commands to the Trough. +519 +520 datarcv: Pipe +521 End of Pipe to receive data from the Trough. +522 +523 cals: Trough_GUI.calibrations +524 Used to convert the data to user units. +525 +526 lastdirection: multiprocessing.Value +527 Of type 'i' to indicate last direction the barriers moved. +528 +529 run_updater: multiprocessing.Value +530 Of type 'c_bool'. True if this updater should keep running. +531 +532 updater_running: multiprocessing.Value +533 Of type 'c_bool'. Set to True by this process when it starts +534 and set to False before exiting. +535 +536 run: trough_run +537 This object contains the live figure and the place to store the data. +538 """ +539 import time +540 from IPython import get_ipython +541 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +542 # Set the shared I'm running flag. +543 updater_running.value = True +544 trough_lock.acquire() +545 while run_updater.value: +546 min_next_time = time.time() + 2.0 +547 cmdsend.send(['Send','']) +548 waiting = True +549 while waiting: +550 if datarcv.poll(): +551 datapkg =datarcv.recv() +552 # update figure and then call update_status +553 if len(datapkg[1]) >= 1: +554 update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run) +555 update_dict = {'barr_raw':datapkg[1][-1], +556 'barr_dev':datapkg[2][-1], +557 'bal_raw':datapkg[3][-1], +558 'bal_dev':datapkg[4][-1], +559 'temp_raw':datapkg[5][-1], +560 'temp_dev':datapkg[6][-1], +561 'messages':datapkg[7]} +562 else: +563 # No updated data, so just pass messages +564 update_dict = {'messages':datapkg[7]} +565 Trough_GUI.status_widgets.update_status(update_dict, cals, +566 lastdirection) +567 waiting = False +568 if time.time()< min_next_time: +569 time.sleep(min_next_time - time.time()) +570 # Release lock and set the shared I'm running flag to False before exiting. +571 trough_lock.release() +572 updater_running.value = False +573 run._end_of_run() +574 return +575 +576def update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run): +577 """Updates the graph and the data storage""" +578 from pandas import DataFrame, concat +579 import numpy as np +580 from IPython import get_ipython +581 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +582 plate_circumference = Trough_GUI.status_widgets.plate_circumference +583 moles_molec = Trough_GUI.status_widgets.moles_molec +584 # do all the calculations on the new data +585 time_stamp = np.array(datapkg[0]) +586 pos_raw = np.array(datapkg[1]) +587 pos_raw_stdev = np.array(datapkg[2]) +588 bal_raw = np.array(datapkg[3]) +589 bal_raw_stdev = np.array(datapkg[4]) +590 temp_raw = np.array(datapkg[5]) +591 temp_raw_stdev = np.array(datapkg[6]) +592 sep_cm = None +593 sep_cm_stdev = None +594 if lastdirection.value < 0: +595 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, +596 pos_raw_stdev) +597 else: +598 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, +599 pos_raw_stdev) +600 sep_cm = np.array(sep_cm) +601 sep_cm_stdev = np.array(sep_cm_stdev) +602 area_sqcm = sep_cm*float(cals.barriers_open.additional_data[ \ +603 "trough width (cm)"]) +604 area_sqcm_stdev = sep_cm_stdev*float(cals.barriers_open.additional_data[ \ +605 "trough width (cm)"]) 606 -607 area_per_molec_ang_sq_stdev = area_sqcm_stdev * 1e16 / moles_molec.value / 6.02214076e23 -608 area_per_molec_ang_sq = area_sqcm * 1e16 / moles_molec.value / 6.02214076e23 -609 mgrams, mgrams_err = cals.balance.cal_apply(bal_raw,bal_raw_stdev) -610 mgrams = np.array(mgrams) -611 mgrams_err = np.array(mgrams_err) -612 surf_press_err = mgrams_err * 9.80665 / plate_circumference.value -613 surf_press_data = (Trough_GUI.status_widgets.tare_pi-mgrams)*9.80665\ -614 /plate_circumference.value -615 tempC, tempC_stdev = cals.temperature.cal_apply(temp_raw, temp_raw_stdev) -616 -617 # add data to the dataframe -618 newdata = DataFrame({"time_stamp":time_stamp, -619 "position_raw": pos_raw, -620 "postion_raw_stdev":pos_raw_stdev, -621 "balance_raw":bal_raw, -622 "balance_raw_stdev":bal_raw_stdev, -623 "temperature_raw":temp_raw, -624 "temperature_raw_stdev":temp_raw_stdev, -625 "Separation (cm)":sep_cm, -626 "Separation stdev":sep_cm_stdev, -627 "Area (cm^2)":area_sqcm, -628 "Area stdev":area_sqcm_stdev, -629 "Area per molecule (A^2)":area_per_molec_ang_sq, -630 "Area per molec stdev": area_per_molec_ang_sq_stdev, -631 "mg":mgrams, -632 "mg stdev":mgrams_err, -633 "Surface Pressure (mN/m)":surf_press_data, -634 "Surface Pressure stdev":surf_press_err, -635 "Deg C":tempC, -636 "Deg C stdev":tempC_stdev}) -637 if not isinstance(run.df, DataFrame): -638 run.df = newdata.copy() -639 else: -640 run.df = concat([run.df,newdata], ignore_index=True) # This may be -641 # costly. If so make the data frame only at the end. -642 # TODO should I lock the dataframe or only make np.arrays until done. -643 # update the graph -644 x_data =[] -645 y_data = run.df["Surface Pressure (mN/m)"] -646 lastpos = None -647 initpos = None -648 if run.speed == 0: -649 x_data = run.df["time_stamp"]-run.df["time_stamp"][0] -650 else: -651 if run.units == "cm": -652 x_data = run.df["Separation (cm)"] -653 lastpos = run.df["Separation (cm)"][len(run.df["Separation (cm)"])-1] -654 initpos = run.df["Separation (cm)"][0] -655 if run.units == "cm^2": -656 x_data = run.df["Area (cm^2)"] -657 lastpos = run.df["Area (cm^2)"][len(run.df["Area (cm^2)"])-1] -658 initpos = run.df["Area (cm^2)"][0] -659 if run.units == "Angstrom^2/molec": -660 x_data = run.df["Area per molecule (A^2)"] -661 lastpos = run.df["Area per molecule (A^2)"][len(run.df["Area per molecule (A^2)"])-1] -662 initpos = run.df["Area per molecule (A^2)"][0] -663 run.livefig.data[0].x = x_data -664 run.livefig.data[0].y = y_data -665 if (lastpos < initpos) and (lastpos <= run.target): -666 # Stop collecting -667 run_updater.value = False -668 if (lastpos > initpos) and (lastpos >= run.target): -669 # Stop collecting -670 run_updater.value = False -671 return +607 +608 area_per_molec_ang_sq_stdev = area_sqcm_stdev * 1e16 / moles_molec.value / 6.02214076e23 +609 area_per_molec_ang_sq = area_sqcm * 1e16 / moles_molec.value / 6.02214076e23 +610 mgrams, mgrams_err = cals.balance.cal_apply(bal_raw,bal_raw_stdev) +611 mgrams = np.array(mgrams) +612 mgrams_err = np.array(mgrams_err) +613 surf_press_err = mgrams_err * 9.80665 / plate_circumference.value +614 surf_press_data = (Trough_GUI.status_widgets.tare_pi-mgrams)*9.80665\ +615 /plate_circumference.value +616 tempC, tempC_stdev = cals.temperature.cal_apply(temp_raw, temp_raw_stdev) +617 +618 # add data to the dataframe +619 newdata = DataFrame({"time_stamp":time_stamp, +620 "position_raw": pos_raw, +621 "postion_raw_stdev":pos_raw_stdev, +622 "balance_raw":bal_raw, +623 "balance_raw_stdev":bal_raw_stdev, +624 "temperature_raw":temp_raw, +625 "temperature_raw_stdev":temp_raw_stdev, +626 "Separation (cm)":sep_cm, +627 "Separation stdev":sep_cm_stdev, +628 "Area (cm^2)":area_sqcm, +629 "Area stdev":area_sqcm_stdev, +630 "Area per molecule (A^2)":area_per_molec_ang_sq, +631 "Area per molec stdev": area_per_molec_ang_sq_stdev, +632 "mg":mgrams, +633 "mg stdev":mgrams_err, +634 "Surface Pressure (mN/m)":surf_press_data, +635 "Surface Pressure stdev":surf_press_err, +636 "Deg C":tempC, +637 "Deg C stdev":tempC_stdev}) +638 if not isinstance(run.df, DataFrame): +639 run.df = newdata.copy() +640 else: +641 run.df = concat([run.df,newdata], ignore_index=True) # This may be +642 # costly. If so make the data frame only at the end. +643 # TODO should I lock the dataframe or only make np.arrays until done. +644 # update the graph +645 x_data =[] +646 y_data = run.df["Surface Pressure (mN/m)"] +647 lastpos = None +648 initpos = None +649 if run.speed == 0: +650 x_data = run.df["time_stamp"]-run.df["time_stamp"][0] +651 else: +652 if run.units == "cm": +653 x_data = run.df["Separation (cm)"] +654 lastpos = run.df["Separation (cm)"][len(run.df["Separation (cm)"])-1] +655 initpos = run.df["Separation (cm)"][0] +656 if run.units == "cm^2": +657 x_data = run.df["Area (cm^2)"] +658 lastpos = run.df["Area (cm^2)"][len(run.df["Area (cm^2)"])-1] +659 initpos = run.df["Area (cm^2)"][0] +660 if run.units == "Angstrom^2/molec": +661 x_data = run.df["Area per molecule (A^2)"] +662 lastpos = run.df["Area per molecule (A^2)"][len(run.df["Area per molecule (A^2)"])-1] +663 initpos = run.df["Area per molecule (A^2)"][0] +664 run.livefig.data[0].x = x_data +665 run.livefig.data[0].y = y_data +666 if (lastpos < initpos) and (lastpos <= run.target): +667 # Stop collecting +668 run_updater.value = False +669 if (lastpos > initpos) and (lastpos >= run.target): +670 # Stop collecting +671 run_updater.value = False +672 return
78 @classmethod - 79 def from_html(self, html): - 80 """Create a run from an html representation - 81 Parameters - 82 ---------- - 83 html: str - 84 The html to be parsed to create the run object - 85 - 86 Returns - 87 ------- - 88 trough_run: trough_run - 89 A trough_run object - 90 """ - 91 from AdvancedHTMLParser import AdvancedHTMLParser as Parser - 92 from pandas import read_html - 93 # parse the document - 94 document = Parser() - 95 document.parseStr(html) - 96 id = int(document.getElementById('id').text) - 97 filename = document.getElementById('filename').text - 98 title = document.getElementById('title').text - 99 units = document.getElementById('units').text -100 target = float(document.getElementById('target').text) -101 speed = float(document.getElementById('speed').text) -102 moles = float(document.getElementById('moles').text) -103 plate_circ = float(document.getElementById('plate_circ').text) -104 dataframe = read_html(html, index_col=0, attrs = {'id': 'run_data'})[0] -105 timestamp = float(document.getElementById('timestamp').text) -106 # return as run object -107 return trough_run(id, filename, title, units, target, speed, moles, -108 plate_circ, dataframe, timestamp) +@@ -1356,23 +1359,23 @@79 @classmethod + 80 def from_html(self, html): + 81 """Create a run from an html representation + 82 Parameters + 83 ---------- + 84 html: str + 85 The html to be parsed to create the run object + 86 + 87 Returns + 88 ------- + 89 trough_run: trough_run + 90 A trough_run object + 91 """ + 92 from AdvancedHTMLParser import AdvancedHTMLParser as Parser + 93 from pandas import read_html + 94 # parse the document + 95 document = Parser() + 96 document.parseStr(html) + 97 id = int(document.getElementById('id').text) + 98 filename = document.getElementById('filename').text + 99 title = document.getElementById('title').text +100 units = document.getElementById('units').text +101 target = float(document.getElementById('target').text) +102 speed = float(document.getElementById('speed').text) +103 moles = float(document.getElementById('moles').text) +104 plate_circ = float(document.getElementById('plate_circ').text) +105 dataframe = read_html(html, index_col=0, attrs = {'id': 'run_data'})[0] +106 timestamp = float(document.getElementById('timestamp').text) +107 # return as run object +108 return trough_run(id, filename, title, units, target, speed, moles, +109 plate_circ, dataframe, timestamp)Returns
141 def init_collect_control(self): -142 """This initializes the collection control widgets and VBox that -143 contains them. The VBox may be accessed as `self.collect_control`""" -144 from ipywidgets import Button, HBox, VBox -145 from IPython import get_ipython -146 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -147 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep -148 Bar_Area = Trough_GUI.status_widgets.Bar_Area -149 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec -150 degC = Trough_GUI.status_widgets.degC -151 surf_press = Trough_GUI.status_widgets.surf_press -152 Barr_Units = Trough_GUI.command_widgets.Barr_Units -153 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed -154 Barr_Target = Trough_GUI.command_widgets.Barr_Target -155 -156 run_start_stop = Button(description="Run", -157 button_style="success") -158 -159 def _on_run_start_stop(change): -160 from threading import Thread -161 from numpy import sign -162 from IPython import get_ipython -163 from Trough_GUI.conversions import sqcm_to_cm, angpermolec_to_sqcm -164 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -165 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep -166 Bar_Area = Trough_GUI.status_widgets.Bar_Area -167 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec -168 moles_molec = Trough_GUI.status_widgets.moles_molec -169 Barr_Units = Trough_GUI.command_widgets.Barr_Units -170 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed -171 Barr_Target = Trough_GUI.command_widgets.Barr_Target -172 # Check if we are stopping a run -173 if run_start_stop.description == "Stop": -174 _on_stop_run() -175 return -176 # take over status updating -177 # First shutdown currently running updater -178 if Trough_GUI.updater_running.value: -179 Trough_GUI.run_updater.value = False -180 while Trough_GUI.updater_running.value: -181 # wait -182 pass -183 # Start run -184 # Convert "Start" to "Stop" button -185 Trough_GUI.run_updater.value = True -186 run_start_stop.description = "Stop" -187 run_start_stop.button_style = "danger" -188 # start barriers -189 trough_lock.acquire() -190 direction = 0 -191 tempspeed = 0 -192 temp_targ = 0 -193 speed = 0 -194 skimmer_correction = float( -195 Trough_GUI.calibrations.barriers_open.additional_data[ -196 "skimmer correction (cm^2)"]) -197 width = float(Trough_GUI.calibrations.barriers_open.additional_data[ -198 "trough width (cm)"]) -199 target_speed = float(Barr_Speed.value) -200 if Barr_Units.value == 'cm': -201 direction = int( -202 sign(float(Barr_Target.value) - float(Bar_Sep.value))) -203 tempspeed = target_speed -204 temp_targ = float(Barr_Target.value) -205 elif Barr_Units.value == "cm^2": -206 direction = int( -207 sign(float(Barr_Target.value) - float(Bar_Area.value))) -208 tempspeed = (target_speed - skimmer_correction) / width -209 temp_targ = \ -210 sqcm_to_cm(float(Barr_Target.value), 0, -211 Trough_GUI.calibrations)[0] -212 elif Barr_Units.value == "Angstrom^2/molec": -213 direction = int(sign( -214 float(Barr_Target.value) - float(Bar_Area_per_Molec.value))) -215 tempspeed = (target_speed - skimmer_correction) / width / 1e16 * float( -216 moles_molec.value) * 6.02214076e23 -217 temp_targ = sqcm_to_cm( -218 *angpermolec_to_sqcm(float(Barr_Target.value), 0, -219 float(moles_molec.value)), -220 Trough_GUI.calibrations)[0] -221 if direction < 0: -222 target = \ -223 Trough_GUI.calibrations.barriers_close.cal_inv( -224 float(temp_targ), 0)[ -225 0] -226 speed = \ -227 Trough_GUI.calibrations.speed_close.cal_inv(tempspeed, 0)[0] -228 else: -229 target = \ -230 Trough_GUI.calibrations.barriers_open.cal_inv( -231 float(temp_targ), 0)[ -232 0] -233 speed = \ -234 Trough_GUI.calibrations.speed_open.cal_inv(tempspeed, 0)[0] -235 cmdsend.send(['Speed', speed]) -236 cmdsend.send(['Direction', direction]) -237 cmdsend.send(['MoveTo', target]) -238 trough_lock.release() -239 # display data as collected and periodically update status widgets -240 # spawn updater thread that updates both status widgets and the figure. -241 updater = Thread(target=collect_data_updater, -242 args=(trough_lock, cmdsend, -243 datarcv, -244 Trough_GUI.calibrations, -245 Trough_GUI.lastdirection, -246 Trough_GUI.run_updater, -247 Trough_GUI.updater_running, -248 self)) -249 updater.start() -250 return -251 -252 def _on_stop_run(): -253 from IPython import get_ipython -254 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -255 run_start_stop.description = "Done" -256 run_start_stop.disabled = True -257 run_start_stop.button_style = "" -258 # Stop run -259 if Trough_GUI.updater_running.value: -260 Trough_GUI.run_updater.value = False -261 # when collection stops it will call _end_of_run. -262 return -263 -264 run_start_stop.on_click(_on_run_start_stop) -265 -266 run_start_stop.description = "Run" -267 run_start_stop.disabled = False -268 run_start_stop.button_style = "success" -269 collect_control1 = HBox([surf_press, run_start_stop]) -270 position = None -271 x_units = Barr_Units.value -272 if Barr_Units.value == 'cm': -273 position = Bar_Sep -274 elif Barr_Units.value == 'cm^2': -275 position = Bar_Area -276 x_units = "$cm^2$" -277 elif Barr_Units.value == 'Angstrom^2/molec': -278 position = Bar_Area_per_Molec -279 x_units = "$Area\,per\,molecule\,({\overset{\circ}{A}}^2)$" -280 collect_control2 = HBox([degC, position]) -281 self.collect_control = VBox([collect_control1, collect_control2]) -282 x_min = 0 -283 x_max = 1 -284 if float(Barr_Target.value) < float(position.value): -285 x_min = 0.98 * float(Barr_Target.value) -286 x_max = 1.02 * float(position.value) -287 else: -288 x_min = 0.98 * float(position.value) -289 x_max = 1.02 * float(Barr_Target.value) -290 if float(Barr_Speed.value) == 0: -291 x_units = 'Time (s)' -292 x_min = 0 -293 x_max = 600 # 10 minutes -294 self.livefig.update_yaxes(title="$\Pi\,(mN/m)$", -295 range=[0, 60]) -296 self.livefig.update_xaxes(title=x_units, -297 range=[x_min, x_max]) -298 self.livefig.add_scatter(x=[], y=[]) -299 return +@@ -1571,12 +1574,12 @@142 def init_collect_control(self): +143 """This initializes the collection control widgets and VBox that +144 contains them. The VBox may be accessed as `self.collect_control`""" +145 from ipywidgets import Button, HBox, VBox +146 from IPython import get_ipython +147 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +148 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep +149 Bar_Area = Trough_GUI.status_widgets.Bar_Area +150 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec +151 degC = Trough_GUI.status_widgets.degC +152 surf_press = Trough_GUI.status_widgets.surf_press +153 Barr_Units = Trough_GUI.command_widgets.Barr_Units +154 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed +155 Barr_Target = Trough_GUI.command_widgets.Barr_Target +156 +157 run_start_stop = Button(description="Run", +158 button_style="success") +159 +160 def _on_run_start_stop(change): +161 from threading import Thread +162 from numpy import sign +163 from IPython import get_ipython +164 from Trough_GUI.conversions import sqcm_to_cm, angpermolec_to_sqcm +165 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +166 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep +167 Bar_Area = Trough_GUI.status_widgets.Bar_Area +168 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec +169 moles_molec = Trough_GUI.status_widgets.moles_molec +170 Barr_Units = Trough_GUI.command_widgets.Barr_Units +171 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed +172 Barr_Target = Trough_GUI.command_widgets.Barr_Target +173 # Check if we are stopping a run +174 if run_start_stop.description == "Stop": +175 _on_stop_run() +176 return +177 # take over status updating +178 # First shutdown currently running updater +179 if Trough_GUI.updater_running.value: +180 Trough_GUI.run_updater.value = False +181 while Trough_GUI.updater_running.value: +182 # wait +183 pass +184 # Start run +185 # Convert "Start" to "Stop" button +186 Trough_GUI.run_updater.value = True +187 run_start_stop.description = "Stop" +188 run_start_stop.button_style = "danger" +189 # start barriers +190 trough_lock.acquire() +191 direction = 0 +192 tempspeed = 0 +193 temp_targ = 0 +194 speed = 0 +195 skimmer_correction = float( +196 Trough_GUI.calibrations.barriers_open.additional_data[ +197 "skimmer correction (cm^2)"]) +198 width = float(Trough_GUI.calibrations.barriers_open.additional_data[ +199 "trough width (cm)"]) +200 target_speed = float(Barr_Speed.value) +201 if Barr_Units.value == 'cm': +202 direction = int( +203 sign(float(Barr_Target.value) - float(Bar_Sep.value))) +204 tempspeed = target_speed +205 temp_targ = float(Barr_Target.value) +206 elif Barr_Units.value == "cm^2": +207 direction = int( +208 sign(float(Barr_Target.value) - float(Bar_Area.value))) +209 tempspeed = (target_speed - skimmer_correction) / width +210 temp_targ = \ +211 sqcm_to_cm(float(Barr_Target.value), 0, +212 Trough_GUI.calibrations)[0] +213 elif Barr_Units.value == "Angstrom^2/molec": +214 direction = int(sign( +215 float(Barr_Target.value) - float(Bar_Area_per_Molec.value))) +216 tempspeed = (target_speed - skimmer_correction) / width / 1e16 * float( +217 moles_molec.value) * 6.02214076e23 +218 temp_targ = sqcm_to_cm( +219 *angpermolec_to_sqcm(float(Barr_Target.value), 0, +220 float(moles_molec.value)), +221 Trough_GUI.calibrations)[0] +222 if direction < 0: +223 target = \ +224 Trough_GUI.calibrations.barriers_close.cal_inv( +225 float(temp_targ), 0)[ +226 0] +227 speed = \ +228 Trough_GUI.calibrations.speed_close.cal_inv(tempspeed, 0)[0] +229 else: +230 target = \ +231 Trough_GUI.calibrations.barriers_open.cal_inv( +232 float(temp_targ), 0)[ +233 0] +234 speed = \ +235 Trough_GUI.calibrations.speed_open.cal_inv(tempspeed, 0)[0] +236 cmdsend.send(['Speed', speed]) +237 cmdsend.send(['Direction', direction]) +238 cmdsend.send(['MoveTo', target]) +239 trough_lock.release() +240 # display data as collected and periodically update status widgets +241 # spawn updater thread that updates both status widgets and the figure. +242 updater = Thread(target=collect_data_updater, +243 args=(trough_lock, cmdsend, +244 datarcv, +245 Trough_GUI.calibrations, +246 Trough_GUI.lastdirection, +247 Trough_GUI.run_updater, +248 Trough_GUI.updater_running, +249 self)) +250 updater.start() +251 return +252 +253 def _on_stop_run(): +254 from IPython import get_ipython +255 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +256 run_start_stop.description = "Done" +257 run_start_stop.disabled = True +258 run_start_stop.button_style = "" +259 # Stop run +260 if Trough_GUI.updater_running.value: +261 Trough_GUI.run_updater.value = False +262 # when collection stops it will call _end_of_run. +263 return +264 +265 run_start_stop.on_click(_on_run_start_stop) +266 +267 run_start_stop.description = "Run" +268 run_start_stop.disabled = False +269 run_start_stop.button_style = "success" +270 collect_control1 = HBox([surf_press, run_start_stop]) +271 position = None +272 x_units = Barr_Units.value +273 if Barr_Units.value == 'cm': +274 position = Bar_Sep +275 elif Barr_Units.value == 'cm^2': +276 position = Bar_Area +277 x_units = "$cm^2$" +278 elif Barr_Units.value == 'Angstrom^2/molec': +279 position = Bar_Area_per_Molec +280 x_units = "$Area\,per\,molecule\,({\overset{\circ}{A}}^2)$" +281 collect_control2 = HBox([degC, position]) +282 self.collect_control = VBox([collect_control1, collect_control2]) +283 x_min = 0 +284 x_max = 1 +285 if float(Barr_Target.value) < float(position.value): +286 x_min = 0.98 * float(Barr_Target.value) +287 x_max = 1.02 * float(position.value) +288 else: +289 x_min = 0.98 * float(position.value) +290 x_max = 1.02 * float(Barr_Target.value) +291 if float(Barr_Speed.value) == 0: +292 x_units = 'Time (s)' +293 x_min = 0 +294 x_max = 600 # 10 minutes +295 self.livefig.update_yaxes(title="$\Pi\,(mN/m)$", +296 range=[0, 60]) +297 self.livefig.update_xaxes(title=x_units, +298 range=[x_min, x_max]) +299 self.livefig.add_scatter(x=[], y=[]) +300 returnReturns
301 def close_collect_control(self): -302 """This closes `self.collect_control` which also minimizes -303 the objects maintained on the Python side.""" -304 self.collect_control.close() -305 self.collect_control = None -306 return +@@ -1597,37 +1600,37 @@302 def close_collect_control(self): +303 """This closes `self.collect_control` which also minimizes +304 the objects maintained on the Python side.""" +305 self.collect_control.close() +306 self.collect_control = None +307 returnReturns
314 def to_html(self): -315 """Create an html string representing a run""" -316 from AdvancedHTMLParser import AdvancedTag as Domel -317 # create the html -318 run_div = Domel('div') -319 run_info = Domel('table') -320 run_info.setAttribute('class','run_info') -321 run_info.setAttribute('id','run_info') -322 run_info.setAttribute('border','1') -323 tr = Domel('tr') -324 tr.appendInnerHTML('<th>ID</th><th>Filename</th><th>Title</th><th>Units' -325 '</th><th>Target</th><th>Speed</th><th>Moles</th>' -326 '<th>Plate Circumference (mm)</th><th>Time</th><th>Time ' -327 'Stamp</th>') -328 run_info.appendChild(tr) -329 tr = Domel('tr') -330 tr.appendInnerHTML('<td id="id">'+str(self.id)+'</td>' -331 '<td id="filename">'+str(self.filename) + '</td>' -332 '<td id="title">'+str(self.title) + '</td>' -333 '<td id="units">' + str(self.units)+'</td>' -334 '<td id="target">'+str(self.target) +'</td>' -335 '<td id="speed">'+str(self.speed)+'</td>' -336 '<td id="moles">' + str(self.moles)+'</td>' -337 '<td id="plate_circ">'+str(self.plate_circ) + '</td>' -338 '<td id="datestr">'+str(self.datestr)+'</td>' -339 '<td id="timestamp">' + str(self.timestamp)+'</td>') -340 run_info.appendChild(tr) -341 run_div.appendChild(run_info) -342 dfstr = self.df.to_html(table_id="run_data") -343 run_div.appendInnerHTML('<!--placeholder to avoid bug that strips table tag-->\n'+dfstr+'\n<!--avoid tag loss-->\n') -344 return run_div.asHTML() +@@ -1647,51 +1650,51 @@315 def to_html(self): +316 """Create an html string representing a run""" +317 from AdvancedHTMLParser import AdvancedTag as Domel +318 # create the html +319 run_div = Domel('div') +320 run_info = Domel('table') +321 run_info.setAttribute('class','run_info') +322 run_info.setAttribute('id','run_info') +323 run_info.setAttribute('border','1') +324 tr = Domel('tr') +325 tr.appendInnerHTML('<th>ID</th><th>Filename</th><th>Title</th><th>Units' +326 '</th><th>Target</th><th>Speed</th><th>Moles</th>' +327 '<th>Plate Circumference (mm)</th><th>Time</th><th>Time ' +328 'Stamp</th>') +329 run_info.appendChild(tr) +330 tr = Domel('tr') +331 tr.appendInnerHTML('<td id="id">'+str(self.id)+'</td>' +332 '<td id="filename">'+str(self.filename) + '</td>' +333 '<td id="title">'+str(self.title) + '</td>' +334 '<td id="units">' + str(self.units)+'</td>' +335 '<td id="target">'+str(self.target) +'</td>' +336 '<td id="speed">'+str(self.speed)+'</td>' +337 '<td id="moles">' + str(self.moles)+'</td>' +338 '<td id="plate_circ">'+str(self.plate_circ) + '</td>' +339 '<td id="datestr">'+str(self.datestr)+'</td>' +340 '<td id="timestamp">' + str(self.timestamp)+'</td>') +341 run_info.appendChild(tr) +342 run_div.appendChild(run_info) +343 dfstr = self.df.to_html(table_id="run_data") +344 run_div.appendInnerHTML('<!--placeholder to avoid bug that strips table tag-->\n'+dfstr+'\n<!--avoid tag loss-->\n') +345 return run_div.asHTML()Returns
346 def write_run(self, dirpath, **kwargs): -347 """ -348 Writes a run file with the base filename `run.filename` into the -349 directory specified. If a file with the current name exists -350 attempts to make the name unique by appending self.timestamp -351 to the filename. Currently only produces -352 an html file that is also human-readable. Other file formats may be -353 available in the future through the use of key word arguments. -354 -355 Parameters -356 ---------- -357 dirpath: -358 pathlike object or string. Empty string means the current working -359 directory. -360 -361 kwargs: -362 optional key word arguments for future adaptability -363 """ -364 from pathlib import Path -365 from warnings import warn -366 fileext = '.trh.run.html' -367 filename = str(self.filename) -368 fullpath = Path(dirpath, filename) -369 if fullpath.exists(): -370 basename = self.filename -371 for k in Path(self.filename).suffixes: -372 basename = basename.replace(k, '') -373 if basename.split("_")[-1].isnumeric(): -374 split = basename.split("_") -375 if int(split[-1]) == self.timestamp: -376 warn("Run file not written as it already exists.") -377 return -378 basename = '' -379 for k in range(0,len(split)-1): -380 basename += split[k] +"_" -381 filename = basename + str(int(self.timestamp)) + fileext -382 self.filename = filename -383 self.write_run(dirpath) -384 return -385 svhtml = '<!DOCTYPE html><html><body>' + self.to_html() + \ -386 '</body></html>' -387 f = open(fullpath, 'w') -388 f.write(svhtml) -389 f.close() -390 return +@@ -1726,114 +1729,114 @@347 def write_run(self, dirpath, **kwargs): +348 """ +349 Writes a run file with the base filename `run.filename` into the +350 directory specified. If a file with the current name exists +351 attempts to make the name unique by appending self.timestamp +352 to the filename. Currently only produces +353 an html file that is also human-readable. Other file formats may be +354 available in the future through the use of key word arguments. +355 +356 Parameters +357 ---------- +358 dirpath: +359 pathlike object or string. Empty string means the current working +360 directory. +361 +362 kwargs: +363 optional key word arguments for future adaptability +364 """ +365 from pathlib import Path +366 from warnings import warn +367 fileext = '.trh.run.html' +368 filename = str(self.filename) +369 fullpath = Path(dirpath, filename) +370 if fullpath.exists(): +371 basename = self.filename +372 for k in Path(self.filename).suffixes: +373 basename = basename.replace(k, '') +374 if basename.split("_")[-1].isnumeric(): +375 split = basename.split("_") +376 if int(split[-1]) == self.timestamp: +377 warn("Run file not written as it already exists.") +378 return +379 basename = '' +380 for k in range(0,len(split)-1): +381 basename += split[k] +"_" +382 filename = basename + str(int(self.timestamp)) + fileext +383 self.filename = filename +384 self.write_run(dirpath) +385 return +386 svhtml = '<!DOCTYPE html><html><body>' + self.to_html() + \ +387 '</body></html>' +388 f = open(fullpath, 'w') +389 f.write(svhtml) +390 f.close() +391 returnParameters
394def Run(run_name): -395 """ -396 This routine creates a GUI for initializing, starting, collecting and -397 completing a run. If the run has been completed it will simply reload it -398 from the local datafile. -399 -400 Parameters -401 ---------- -402 run_name: str or Path -403 This should generally be the name for the file the data will be stored in -404 without a file type extension. Recommend a naming scheme that produces -405 Unique filenames, such as `Trough_run_<username>_<timestamp>`. The file -406 name will be `run_name.trh.run.html`. -407 """ -408 from pathlib import Path -409 from ipywidgets import Text, Dropdown, HBox, VBox, Accordion, Label, Button -410 from IPython.display import display -411 from IPython import get_ipython -412 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -413 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep -414 Bar_Area = Trough_GUI.status_widgets.Bar_Area -415 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec -416 degC = Trough_GUI.status_widgets.degC -417 surf_press = Trough_GUI.status_widgets.surf_press -418 zero_press = Trough_GUI.status_widgets.zero_press -419 plate_circumference = Trough_GUI.status_widgets.plate_circumference -420 moles_molec = Trough_GUI.status_widgets.moles_molec -421 Barr_Units = Trough_GUI.command_widgets.Barr_Units -422 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed -423 Barr_Target = Trough_GUI.command_widgets.Barr_Target -424 name_to_run = {} -425 completed_runs = [] -426 for k in Trough_GUI.runs: -427 name_to_run[k.title]= k -428 completed_runs.append(k.title) -429 # Check if run completed, if so reload data, display and exit -430 # TODO figure out how to handle ID# if loaded into different index -431 datafilepath = Path.cwd()/Path(str(run_name) + '.trh.run.html') # or should it be gz -432 if datafilepath.exists(): -433 # display the data as a live plotly plot. -434 f = open(datafilepath,'r') -435 html = f.read() -436 f.close() -437 Trough_GUI.runs.append(Trough_GUI.Collect_data.trough_run.from_html(html)) -438 display(Trough_GUI.runs[-1]) -439 return -440 # Build run setup GUI -441 run_title = Text(description = "Run Title", -442 value = str(run_name), -443 disabled = False) -444 def changed_base_on(change): -445 # update settings to match the chosen model run -446 if base_on.value == 'None': -447 return -448 modelrun = name_to_run[str(base_on.value)] -449 plate_circumference.value = str(modelrun.plate_circ) -450 moles_molec.value = str(modelrun.moles) -451 Barr_Units.value = str(modelrun.units) -452 Barr_Speed.value = str(modelrun.speed) -453 Barr_Target.value = str(modelrun.target) -454 pass -455 -456 base_on = Dropdown(description = "Base on", -457 options=['None'] + completed_runs, -458 value='None') -459 base_on.observe(changed_base_on, names='value') -460 top_HBox = HBox([run_title, base_on]) -461 # Displayed status widgets -462 status_HBox1 = HBox([Bar_Sep, Bar_Area, Bar_Area_per_Molec]) -463 status_HBox2 = HBox([surf_press, degC]) -464 status_VBox = VBox([status_HBox1, status_HBox2]) -465 status_Acc = Accordion([status_VBox]) -466 status_Acc.set_title(0,"Status") -467 status_Acc.selected_index = 0 -468 # Displayed settings widgets -469 settings_HBox1 = HBox([zero_press, plate_circumference, moles_molec]) -470 settings_HBox2 = HBox([Barr_Units, Barr_Speed]) -471 store_settings = Button(description="Store Settings") -472 -473 def on_store_settings(change): -474 from IPython.display import HTML, clear_output -475 # create the run object -476 id = len(Trough_GUI.runs) -477 Trough_GUI.runs.append(trough_run(id,run_name+'.trh.run.html', run_title.value, -478 Barr_Units.value, -479 float(Barr_Target.value), -480 float(Barr_Speed.value), -481 float(moles_molec.value), -482 float(plate_circumference.value))) -483 # Create the collection display -484 Trough_GUI.runs[-1].init_collect_control() -485 clear_output() -486 display(Trough_GUI.runs[-1].livefig) -487 display(Trough_GUI.runs[-1].collect_control) -488 display(HTML(Trough_GUI.runs[-1].run_caption())) -489 return -490 -491 store_settings.on_click(on_store_settings) -492 settings_HBox3 = HBox([Label("Target", style=longdesc), Barr_Target, -493 store_settings]) -494 settings_VBox = VBox([settings_HBox1,settings_HBox2, settings_HBox3]) -495 settings_Acc = Accordion([settings_VBox]) -496 settings_Acc.set_title(0, "Settings") -497 status_Acc.selected_index = 0 -498 Barr_Target.disabled = False -499 display(top_HBox, status_Acc, settings_Acc) -500 -501 return +@@ -1863,78 +1866,78 @@395def Run(run_name): +396 """ +397 This routine creates a GUI for initializing, starting, collecting and +398 completing a run. If the run has been completed it will simply reload it +399 from the local datafile. +400 +401 Parameters +402 ---------- +403 run_name: str or Path +404 This should generally be the name for the file the data will be stored in +405 without a file type extension. Recommend a naming scheme that produces +406 Unique filenames, such as `Trough_run_<username>_<timestamp>`. The file +407 name will be `run_name.trh.run.html`. +408 """ +409 from pathlib import Path +410 from ipywidgets import Text, Dropdown, HBox, VBox, Accordion, Label, Button +411 from IPython.display import display +412 from IPython import get_ipython +413 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +414 Bar_Sep = Trough_GUI.status_widgets.Bar_Sep +415 Bar_Area = Trough_GUI.status_widgets.Bar_Area +416 Bar_Area_per_Molec = Trough_GUI.status_widgets.Bar_Area_per_Molec +417 degC = Trough_GUI.status_widgets.degC +418 surf_press = Trough_GUI.status_widgets.surf_press +419 zero_press = Trough_GUI.status_widgets.zero_press +420 plate_circumference = Trough_GUI.status_widgets.plate_circumference +421 moles_molec = Trough_GUI.status_widgets.moles_molec +422 Barr_Units = Trough_GUI.command_widgets.Barr_Units +423 Barr_Speed = Trough_GUI.command_widgets.Barr_Speed +424 Barr_Target = Trough_GUI.command_widgets.Barr_Target +425 name_to_run = {} +426 completed_runs = [] +427 for k in Trough_GUI.runs: +428 name_to_run[k.title]= k +429 completed_runs.append(k.title) +430 # Check if run completed, if so reload data, display and exit +431 # TODO figure out how to handle ID# if loaded into different index +432 datafilepath = Path.cwd()/Path(str(run_name) + '.trh.run.html') # or should it be gz +433 if datafilepath.exists(): +434 # display the data as a live plotly plot. +435 f = open(datafilepath,'r') +436 html = f.read() +437 f.close() +438 Trough_GUI.runs.append(Trough_GUI.Collect_data.trough_run.from_html(html)) +439 display(Trough_GUI.runs[-1]) +440 return +441 # Build run setup GUI +442 run_title = Text(description = "Run Title", +443 value = str(run_name), +444 disabled = False) +445 def changed_base_on(change): +446 # update settings to match the chosen model run +447 if base_on.value == 'None': +448 return +449 modelrun = name_to_run[str(base_on.value)] +450 plate_circumference.value = str(modelrun.plate_circ) +451 moles_molec.value = str(modelrun.moles) +452 Barr_Units.value = str(modelrun.units) +453 Barr_Speed.value = str(modelrun.speed) +454 Barr_Target.value = str(modelrun.target) +455 pass +456 +457 base_on = Dropdown(description = "Base on", +458 options=['None'] + completed_runs, +459 value='None') +460 base_on.observe(changed_base_on, names='value') +461 top_HBox = HBox([run_title, base_on]) +462 # Displayed status widgets +463 status_HBox1 = HBox([Bar_Sep, Bar_Area, Bar_Area_per_Molec]) +464 status_HBox2 = HBox([surf_press, degC]) +465 status_VBox = VBox([status_HBox1, status_HBox2]) +466 status_Acc = Accordion([status_VBox]) +467 status_Acc.set_title(0,"Status") +468 status_Acc.selected_index = 0 +469 # Displayed settings widgets +470 settings_HBox1 = HBox([zero_press, plate_circumference, moles_molec]) +471 settings_HBox2 = HBox([Barr_Units, Barr_Speed]) +472 store_settings = Button(description="Store Settings") +473 +474 def on_store_settings(change): +475 from IPython.display import HTML, clear_output +476 # create the run object +477 id = len(Trough_GUI.runs) +478 Trough_GUI.runs.append(trough_run(id,run_name+'.trh.run.html', run_title.value, +479 Barr_Units.value, +480 float(Barr_Target.value), +481 float(Barr_Speed.value), +482 float(moles_molec.value), +483 float(plate_circumference.value))) +484 # Create the collection display +485 Trough_GUI.runs[-1].init_collect_control() +486 clear_output() +487 display(Trough_GUI.runs[-1].livefig) +488 display(Trough_GUI.runs[-1].collect_control) +489 display(HTML(Trough_GUI.runs[-1].run_caption())) +490 return +491 +492 store_settings.on_click(on_store_settings) +493 settings_HBox3 = HBox([Label("Target", style=longdesc), Barr_Target, +494 store_settings]) +495 settings_VBox = VBox([settings_HBox1,settings_HBox2, settings_HBox3]) +496 settings_Acc = Accordion([settings_VBox]) +497 settings_Acc.set_title(0, "Settings") +498 status_Acc.selected_index = 0 +499 Barr_Target.disabled = False +500 display(top_HBox, status_Acc, settings_Acc) +501 +502 returnParameters
503def collect_data_updater(trough_lock, cmdsend, datarcv, cals, lastdirection, -504 run_updater, updater_running, run): -505 """This is run in a separate thread and will update the figure and -506 all status widgets at an interval of 2 seconds or a little longer. While -507 this is running nothing else will be able to talk to the trough. -508 -509 Parameters -510 ---------- -511 trough_lock: threading.lock -512 When acquired this routine will talk to the trough. It is not -513 released until the routine exits to avoid any data loss. It does -514 call the status_widgets updater as often as it can while collecting -515 the data. -516 -517 cmdsend: Pipe -518 End of Pipe to send commands to the Trough. -519 -520 datarcv: Pipe -521 End of Pipe to receive data from the Trough. -522 -523 cals: Trough_GUI.calibrations -524 Used to convert the data to user units. -525 -526 lastdirection: multiprocessing.Value -527 Of type 'i' to indicate last direction the barriers moved. -528 -529 run_updater: multiprocessing.Value -530 Of type 'c_bool'. True if this updater should keep running. -531 -532 updater_running: multiprocessing.Value -533 Of type 'c_bool'. Set to True by this process when it starts -534 and set to False before exiting. -535 -536 run: trough_run -537 This object contains the live figure and the place to store the data. -538 """ -539 import time -540 from IPython import get_ipython -541 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -542 # Set the shared I'm running flag. -543 updater_running.value = True -544 trough_lock.acquire() -545 while run_updater.value: -546 min_next_time = time.time() + 2.0 -547 cmdsend.send(['Send','']) -548 waiting = True -549 while waiting: -550 if datarcv.poll(): -551 datapkg =datarcv.recv() -552 # update figure and then call update_status -553 if len(datapkg[1]) >= 1: -554 update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run) -555 update_dict = {'barr_raw':datapkg[1][-1], -556 'barr_dev':datapkg[2][-1], -557 'bal_raw':datapkg[3][-1], -558 'bal_dev':datapkg[4][-1], -559 'temp_raw':datapkg[5][-1], -560 'temp_dev':datapkg[6][-1], -561 'messages':datapkg[7]} -562 else: -563 # No updated data, so just pass messages -564 update_dict = {'messages':datapkg[7]} -565 Trough_GUI.status_widgets.update_status(update_dict, cals, -566 lastdirection) -567 waiting = False -568 if time.time()< min_next_time: -569 time.sleep(min_next_time - time.time()) -570 # Release lock and set the shared I'm running flag to False before exiting. -571 trough_lock.release() -572 updater_running.value = False -573 run._end_of_run() -574 return +@@ -1986,103 +1989,103 @@504def collect_data_updater(trough_lock, cmdsend, datarcv, cals, lastdirection, +505 run_updater, updater_running, run): +506 """This is run in a separate thread and will update the figure and +507 all status widgets at an interval of 2 seconds or a little longer. While +508 this is running nothing else will be able to talk to the trough. +509 +510 Parameters +511 ---------- +512 trough_lock: threading.lock +513 When acquired this routine will talk to the trough. It is not +514 released until the routine exits to avoid any data loss. It does +515 call the status_widgets updater as often as it can while collecting +516 the data. +517 +518 cmdsend: Pipe +519 End of Pipe to send commands to the Trough. +520 +521 datarcv: Pipe +522 End of Pipe to receive data from the Trough. +523 +524 cals: Trough_GUI.calibrations +525 Used to convert the data to user units. +526 +527 lastdirection: multiprocessing.Value +528 Of type 'i' to indicate last direction the barriers moved. +529 +530 run_updater: multiprocessing.Value +531 Of type 'c_bool'. True if this updater should keep running. +532 +533 updater_running: multiprocessing.Value +534 Of type 'c_bool'. Set to True by this process when it starts +535 and set to False before exiting. +536 +537 run: trough_run +538 This object contains the live figure and the place to store the data. +539 """ +540 import time +541 from IPython import get_ipython +542 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +543 # Set the shared I'm running flag. +544 updater_running.value = True +545 trough_lock.acquire() +546 while run_updater.value: +547 min_next_time = time.time() + 2.0 +548 cmdsend.send(['Send','']) +549 waiting = True +550 while waiting: +551 if datarcv.poll(): +552 datapkg =datarcv.recv() +553 # update figure and then call update_status +554 if len(datapkg[1]) >= 1: +555 update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run) +556 update_dict = {'barr_raw':datapkg[1][-1], +557 'barr_dev':datapkg[2][-1], +558 'bal_raw':datapkg[3][-1], +559 'bal_dev':datapkg[4][-1], +560 'temp_raw':datapkg[5][-1], +561 'temp_dev':datapkg[6][-1], +562 'messages':datapkg[7]} +563 else: +564 # No updated data, so just pass messages +565 update_dict = {'messages':datapkg[7]} +566 Trough_GUI.status_widgets.update_status(update_dict, cals, +567 lastdirection) +568 waiting = False +569 if time.time()< min_next_time: +570 time.sleep(min_next_time - time.time()) +571 # Release lock and set the shared I'm running flag to False before exiting. +572 trough_lock.release() +573 updater_running.value = False +574 run._end_of_run() +575 returnParameters
576def update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run): -577 """Updates the graph and the data storage""" -578 from pandas import DataFrame, concat -579 import numpy as np -580 from IPython import get_ipython -581 Trough_GUI = get_ipython().user_ns["Trough_GUI"] -582 plate_circumference = Trough_GUI.status_widgets.plate_circumference -583 moles_molec = Trough_GUI.status_widgets.moles_molec -584 # do all the calculations on the new data -585 time_stamp = np.array(datapkg[0]) -586 pos_raw = np.array(datapkg[1]) -587 pos_raw_stdev = np.array(datapkg[2]) -588 bal_raw = np.array(datapkg[3]) -589 bal_raw_stdev = np.array(datapkg[4]) -590 temp_raw = np.array(datapkg[5]) -591 temp_raw_stdev = np.array(datapkg[6]) -592 sep_cm = None -593 sep_cm_stdev = None -594 if lastdirection.value < 0: -595 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, -596 pos_raw_stdev) -597 else: -598 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, -599 pos_raw_stdev) -600 sep_cm = np.array(sep_cm) -601 sep_cm_stdev = np.array(sep_cm_stdev) -602 area_sqcm = sep_cm*float(cals.barriers_open.additional_data[ \ -603 "trough width (cm)"]) -604 area_sqcm_stdev = sep_cm_stdev*float(cals.barriers_open.additional_data[ \ -605 "trough width (cm)"]) -606 +diff --git a/docs/Trough_GUI/Monitor_Calibrate.html b/docs/Trough_GUI/Monitor_Calibrate.html index b20afc7..12f83a3 100644 --- a/docs/Trough_GUI/Monitor_Calibrate.html +++ b/docs/Trough_GUI/Monitor_Calibrate.html @@ -58,7 +58,7 @@577def update_collection(datapkg, cals, lastdirection, run_updater, updater_running, run): +578 """Updates the graph and the data storage""" +579 from pandas import DataFrame, concat +580 import numpy as np +581 from IPython import get_ipython +582 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +583 plate_circumference = Trough_GUI.status_widgets.plate_circumference +584 moles_molec = Trough_GUI.status_widgets.moles_molec +585 # do all the calculations on the new data +586 time_stamp = np.array(datapkg[0]) +587 pos_raw = np.array(datapkg[1]) +588 pos_raw_stdev = np.array(datapkg[2]) +589 bal_raw = np.array(datapkg[3]) +590 bal_raw_stdev = np.array(datapkg[4]) +591 temp_raw = np.array(datapkg[5]) +592 temp_raw_stdev = np.array(datapkg[6]) +593 sep_cm = None +594 sep_cm_stdev = None +595 if lastdirection.value < 0: +596 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, +597 pos_raw_stdev) +598 else: +599 sep_cm, sep_cm_stdev = cals.barriers_close.cal_apply(pos_raw, +600 pos_raw_stdev) +601 sep_cm = np.array(sep_cm) +602 sep_cm_stdev = np.array(sep_cm_stdev) +603 area_sqcm = sep_cm*float(cals.barriers_open.additional_data[ \ +604 "trough width (cm)"]) +605 area_sqcm_stdev = sep_cm_stdev*float(cals.barriers_open.additional_data[ \ +606 "trough width (cm)"]) 607 -608 area_per_molec_ang_sq_stdev = area_sqcm_stdev * 1e16 / moles_molec.value / 6.02214076e23 -609 area_per_molec_ang_sq = area_sqcm * 1e16 / moles_molec.value / 6.02214076e23 -610 mgrams, mgrams_err = cals.balance.cal_apply(bal_raw,bal_raw_stdev) -611 mgrams = np.array(mgrams) -612 mgrams_err = np.array(mgrams_err) -613 surf_press_err = mgrams_err * 9.80665 / plate_circumference.value -614 surf_press_data = (Trough_GUI.status_widgets.tare_pi-mgrams)*9.80665\ -615 /plate_circumference.value -616 tempC, tempC_stdev = cals.temperature.cal_apply(temp_raw, temp_raw_stdev) -617 -618 # add data to the dataframe -619 newdata = DataFrame({"time_stamp":time_stamp, -620 "position_raw": pos_raw, -621 "postion_raw_stdev":pos_raw_stdev, -622 "balance_raw":bal_raw, -623 "balance_raw_stdev":bal_raw_stdev, -624 "temperature_raw":temp_raw, -625 "temperature_raw_stdev":temp_raw_stdev, -626 "Separation (cm)":sep_cm, -627 "Separation stdev":sep_cm_stdev, -628 "Area (cm^2)":area_sqcm, -629 "Area stdev":area_sqcm_stdev, -630 "Area per molecule (A^2)":area_per_molec_ang_sq, -631 "Area per molec stdev": area_per_molec_ang_sq_stdev, -632 "mg":mgrams, -633 "mg stdev":mgrams_err, -634 "Surface Pressure (mN/m)":surf_press_data, -635 "Surface Pressure stdev":surf_press_err, -636 "Deg C":tempC, -637 "Deg C stdev":tempC_stdev}) -638 if not isinstance(run.df, DataFrame): -639 run.df = newdata.copy() -640 else: -641 run.df = concat([run.df,newdata], ignore_index=True) # This may be -642 # costly. If so make the data frame only at the end. -643 # TODO should I lock the dataframe or only make np.arrays until done. -644 # update the graph -645 x_data =[] -646 y_data = run.df["Surface Pressure (mN/m)"] -647 lastpos = None -648 initpos = None -649 if run.speed == 0: -650 x_data = run.df["time_stamp"]-run.df["time_stamp"][0] -651 else: -652 if run.units == "cm": -653 x_data = run.df["Separation (cm)"] -654 lastpos = run.df["Separation (cm)"][len(run.df["Separation (cm)"])-1] -655 initpos = run.df["Separation (cm)"][0] -656 if run.units == "cm^2": -657 x_data = run.df["Area (cm^2)"] -658 lastpos = run.df["Area (cm^2)"][len(run.df["Area (cm^2)"])-1] -659 initpos = run.df["Area (cm^2)"][0] -660 if run.units == "Angstrom^2/molec": -661 x_data = run.df["Area per molecule (A^2)"] -662 lastpos = run.df["Area per molecule (A^2)"][len(run.df["Area per molecule (A^2)"])-1] -663 initpos = run.df["Area per molecule (A^2)"][0] -664 run.livefig.data[0].x = x_data -665 run.livefig.data[0].y = y_data -666 if (lastpos < initpos) and (lastpos <= run.target): -667 # Stop collecting -668 run_updater.value = False -669 if (lastpos > initpos) and (lastpos >= run.target): -670 # Stop collecting -671 run_updater.value = False -672 return +608 +609 area_per_molec_ang_sq_stdev = area_sqcm_stdev * 1e16 / moles_molec.value / 6.02214076e23 +610 area_per_molec_ang_sq = area_sqcm * 1e16 / moles_molec.value / 6.02214076e23 +611 mgrams, mgrams_err = cals.balance.cal_apply(bal_raw,bal_raw_stdev) +612 mgrams = np.array(mgrams) +613 mgrams_err = np.array(mgrams_err) +614 surf_press_err = mgrams_err * 9.80665 / plate_circumference.value +615 surf_press_data = (Trough_GUI.status_widgets.tare_pi-mgrams)*9.80665\ +616 /plate_circumference.value +617 tempC, tempC_stdev = cals.temperature.cal_apply(temp_raw, temp_raw_stdev) +618 +619 # add data to the dataframe +620 newdata = DataFrame({"time_stamp":time_stamp, +621 "position_raw": pos_raw, +622 "postion_raw_stdev":pos_raw_stdev, +623 "balance_raw":bal_raw, +624 "balance_raw_stdev":bal_raw_stdev, +625 "temperature_raw":temp_raw, +626 "temperature_raw_stdev":temp_raw_stdev, +627 "Separation (cm)":sep_cm, +628 "Separation stdev":sep_cm_stdev, +629 "Area (cm^2)":area_sqcm, +630 "Area stdev":area_sqcm_stdev, +631 "Area per molecule (A^2)":area_per_molec_ang_sq, +632 "Area per molec stdev": area_per_molec_ang_sq_stdev, +633 "mg":mgrams, +634 "mg stdev":mgrams_err, +635 "Surface Pressure (mN/m)":surf_press_data, +636 "Surface Pressure stdev":surf_press_err, +637 "Deg C":tempC, +638 "Deg C stdev":tempC_stdev}) +639 if not isinstance(run.df, DataFrame): +640 run.df = newdata.copy() +641 else: +642 run.df = concat([run.df,newdata], ignore_index=True) # This may be +643 # costly. If so make the data frame only at the end. +644 # TODO should I lock the dataframe or only make np.arrays until done. +645 # update the graph +646 x_data =[] +647 y_data = run.df["Surface Pressure (mN/m)"] +648 lastpos = None +649 initpos = None +650 if run.speed == 0: +651 x_data = run.df["time_stamp"]-run.df["time_stamp"][0] +652 else: +653 if run.units == "cm": +654 x_data = run.df["Separation (cm)"] +655 lastpos = run.df["Separation (cm)"][len(run.df["Separation (cm)"])-1] +656 initpos = run.df["Separation (cm)"][0] +657 if run.units == "cm^2": +658 x_data = run.df["Area (cm^2)"] +659 lastpos = run.df["Area (cm^2)"][len(run.df["Area (cm^2)"])-1] +660 initpos = run.df["Area (cm^2)"][0] +661 if run.units == "Angstrom^2/molec": +662 x_data = run.df["Area per molecule (A^2)"] +663 lastpos = run.df["Area per molecule (A^2)"][len(run.df["Area per molecule (A^2)"])-1] +664 initpos = run.df["Area per molecule (A^2)"][0] +665 run.livefig.data[0].x = x_data +666 run.livefig.data[0].y = y_data +667 if (lastpos < initpos) and (lastpos <= run.target): +668 # Stop collecting +669 run_updater.value = False +670 if (lastpos > initpos) and (lastpos >= run.target): +671 # Stop collecting +672 run_updater.value = False +673 returnAPI Documentation
- + built with pdocAPI Documentation - + built with pdocAPI Documentation - + built with pdocAPI Documentation - + built with pdocAPI Documentation - + built with pdoc=0.5.0', 'lmfit>=1.0.3', 'round-using-error>=1.1.1', - 'RPi.GPIO>=0.7.0;platform_system=="Linux"', + 'RPi.GPIO>=0.7.0;platform_system=="Linux"', # pi-plates requires + 'spidev>=3.5;platform_system=="Linux"', # pi-plates requires 'pi-plates>=7.21', 'numpy>=1.21', 'plotly>=5.8.2',