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 @@

API Documentation

- + built with pdocAPI Documentation - + built with pdocAPI Documentation - + built with pdocAPI Documentation - + built with pdoc 99 from piplates import DAQC2plate 100 del DAQC2plate 101 except Exception as e: -102 trough_exists = False -103 if trough_exists: -104 TROUGH = Process(target=troughctl, args=(cmdrcv, datasend)) -105 else: -106 print("Unable to find Trough. Using simulation.") -107 from Trough_Control.simulation import simulated_troughctl -108 TROUGH = Process(target=simulated_troughctl, args=(cmdrcv, datasend)) -109 TROUGH.start() -110 time.sleep(0.2) -111 waiting = True -112 while waiting: -113 if datarcv.poll(): -114 datapkg = datarcv.recv() -115 messages = extract_messages(datapkg) -116 print(str(messages)) -117 if "ERROR" in messages: -118 exit() -119 if "Trough ready" in messages: -120 waiting = False -121 return cmdsend, datarcv, TROUGH -122 -123 +102 print("An issue was encountered while accessing the DAQC2plate:" + +103 str(e)) +104 trough_exists = False +105 if trough_exists: +106 TROUGH = Process(target=troughctl, args=(cmdrcv, datasend)) +107 else: +108 print("Unable to find Trough. Using simulation.") +109 from Trough_Control.simulation import simulated_troughctl +110 TROUGH = Process(target=simulated_troughctl, args=(cmdrcv, datasend)) +111 TROUGH.start() +112 time.sleep(0.2) +113 waiting = True +114 while waiting: +115 if datarcv.poll(): +116 datapkg = datarcv.recv() +117 messages = extract_messages(datapkg) +118 print(str(messages)) +119 if "ERROR" in messages: +120 exit() +121 if "Trough ready" in messages: +122 waiting = False +123 return cmdsend, datarcv, TROUGH 124 -125def troughctl(CTLPipe,DATAPipe): -126 """ -127 Will run as separate process taking in commands through a pipe and -128 returning data on demand through a second pipe. -129 Iteration 1, collects data into a fifo and watches barrier position. -130 :param Pipe CTLPipe: pipe commands come in on and messages go out on. -131 :param Pipe DATAPipe: pipe data is sent out on -132 """ -133 import time -134 import numpy as np -135 from collections import deque -136 from multiprocessing import Pipe -137 from piplates import DAQC2plate as DAQC2 -138 from sys import exit -139 -140 def bundle_to_send(que_lst): -141 """ -142 -143 Parameters -144 ---------- -145 que_lst: list -146 list of deques. -147 -148 Returns -149 ------- -150 list -151 list of lists. One list for each deque. -152 """ -153 -154 pkg_lst = [] -155 for k in que_lst: -156 # print ('que_lst element: '+ str(k)) -157 tmp_lst = [] -158 for j in k: -159 tmp_lst.append(j) -160 pkg_lst.append(tmp_lst) -161 return pkg_lst -162 -163 def barrier_at_limit_check(openlimit, closelimit): -164 """ -165 Checks if barrier is at or beyond limit and stops barrier if it is moving -166 in a direction that would make it worse. -167 :param float openlimit: lowest voltage allowed for opening -168 :param float closelimit: highest voltage allowed for closing -169 -170 Returns -171 ======= -172 True or False. If True also shuts down power to barrier -173 """ -174 -175 direction = 0 # -1 closing, 0 stopped, 1 openning. -176 if (etol_call(DAQC2.getDAC,(0, 0)) >= 2.5): -177 direction = -1 -178 else: -179 direction = 1 -180 position = etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)) -181 if (position >= closelimit) and (direction == -1): -182 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -183 return True -184 if (position <= openlimit) and (direction == 1): -185 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -186 return True -187 return False -188 -189 def take_over_barrier_monitoring(): -190 """ -191 Call this to run something in a tight loop that needs to take over watching -192 the barriers. Check if a trough controller is registered at /tmp/troughctl.pid. -193 If one is store it's pid and replace with own. Revert on exit to avoid -194 crashing. -195 -196 Returns -197 ------- -198 int: -199 pid for process that was watching the barriers. -200 """ -201 import os -202 pidpath = '/tmp/troughctl.pid' -203 ctlpid = None -204 if os.path.exists(pidpath): -205 file = open(pidpath, 'r') -206 ctlpid = int(file.readline()) -207 file.close() -208 pid = os.getpid() -209 # print(str(pid)) -210 file = open(pidpath, 'w') -211 file.write(str(pid) + '\n') -212 file.close() -213 # Do not proceed until this file exists on the file system -214 while not os.path.exists(pidpath): -215 pass # just waiting for file system to catch up. -216 # Read it to make sure -217 file = open(pidpath, 'r') -218 checkpid = file.readline() -219 file.close() -220 if int(checkpid) != pid: -221 raise FileNotFoundError('Checkpid = ' + checkpid + ', but should = ' + str(pid)) -222 return ctlpid -223 -224 def return_barrier_monitoring_to_prev_process(ctlpid): -225 """ -226 -227 Parameters -228 ---------- -229 ctlpid : int -230 Process id of the process that was watching the barriers before. -231 -232 Returns -233 ------- -234 -235 """ -236 import os -237 pidpath = '/tmp/troughctl.pid' -238 if ctlpid is not None: -239 file = open(pidpath, 'w') -240 file.write(str(ctlpid) + '\n') -241 file.close() -242 elif os.path.exists(pidpath): -243 os.remove(pidpath) -244 elif os.path.exists(pidpath): # double check +125 +126 +127def troughctl(CTLPipe,DATAPipe): +128 """ +129 Will run as separate process taking in commands through a pipe and +130 returning data on demand through a second pipe. +131 Iteration 1, collects data into a fifo and watches barrier position. +132 :param Pipe CTLPipe: pipe commands come in on and messages go out on. +133 :param Pipe DATAPipe: pipe data is sent out on +134 """ +135 import time +136 import numpy as np +137 from collections import deque +138 from multiprocessing import Pipe +139 from piplates import DAQC2plate as DAQC2 +140 from sys import exit +141 +142 def bundle_to_send(que_lst): +143 """ +144 +145 Parameters +146 ---------- +147 que_lst: list +148 list of deques. +149 +150 Returns +151 ------- +152 list +153 list of lists. One list for each deque. +154 """ +155 +156 pkg_lst = [] +157 for k in que_lst: +158 # print ('que_lst element: '+ str(k)) +159 tmp_lst = [] +160 for j in k: +161 tmp_lst.append(j) +162 pkg_lst.append(tmp_lst) +163 return pkg_lst +164 +165 def barrier_at_limit_check(openlimit, closelimit): +166 """ +167 Checks if barrier is at or beyond limit and stops barrier if it is moving +168 in a direction that would make it worse. +169 :param float openlimit: lowest voltage allowed for opening +170 :param float closelimit: highest voltage allowed for closing +171 +172 Returns +173 ======= +174 True or False. If True also shuts down power to barrier +175 """ +176 +177 direction = 0 # -1 closing, 0 stopped, 1 openning. +178 if (etol_call(DAQC2.getDAC,(0, 0)) >= 2.5): +179 direction = -1 +180 else: +181 direction = 1 +182 position = etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)) +183 if (position >= closelimit) and (direction == -1): +184 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +185 return True +186 if (position <= openlimit) and (direction == 1): +187 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +188 return True +189 return False +190 +191 def take_over_barrier_monitoring(): +192 """ +193 Call this to run something in a tight loop that needs to take over watching +194 the barriers. Check if a trough controller is registered at /tmp/troughctl.pid. +195 If one is store it's pid and replace with own. Revert on exit to avoid +196 crashing. +197 +198 Returns +199 ------- +200 int: +201 pid for process that was watching the barriers. +202 """ +203 import os +204 pidpath = '/tmp/troughctl.pid' +205 ctlpid = None +206 if os.path.exists(pidpath): +207 file = open(pidpath, 'r') +208 ctlpid = int(file.readline()) +209 file.close() +210 pid = os.getpid() +211 # print(str(pid)) +212 file = open(pidpath, 'w') +213 file.write(str(pid) + '\n') +214 file.close() +215 # Do not proceed until this file exists on the file system +216 while not os.path.exists(pidpath): +217 pass # just waiting for file system to catch up. +218 # Read it to make sure +219 file = open(pidpath, 'r') +220 checkpid = file.readline() +221 file.close() +222 if int(checkpid) != pid: +223 raise FileNotFoundError('Checkpid = ' + checkpid + ', but should = ' + str(pid)) +224 return ctlpid +225 +226 def return_barrier_monitoring_to_prev_process(ctlpid): +227 """ +228 +229 Parameters +230 ---------- +231 ctlpid : int +232 Process id of the process that was watching the barriers before. +233 +234 Returns +235 ------- +236 +237 """ +238 import os +239 pidpath = '/tmp/troughctl.pid' +240 if ctlpid is not None: +241 file = open(pidpath, 'w') +242 file.write(str(ctlpid) + '\n') +243 file.close() +244 elif os.path.exists(pidpath): 245 os.remove(pidpath) -246 pass -247 -248 def get_power_supply_volts(): -249 """ -250 Returns the negative and positive voltages from the power supply corrected -251 for the inline voltage divider allowing measurement up to a bit more -252 than +/- 15 V. -253 -254 :returns float V_neg: the negative voltage. -255 :returns float V_pos: the positive voltage. -256 """ -257 V_neg = 1.3875*etol_call(DAQC2.getADC, (0,6)) -258 V_pos = 1.3729*etol_call(DAQC2.getADC, (0,7)) -259 -260 return V_neg, V_pos +246 elif os.path.exists(pidpath): # double check +247 os.remove(pidpath) +248 pass +249 +250 def get_power_supply_volts(): +251 """ +252 Returns the negative and positive voltages from the power supply corrected +253 for the inline voltage divider allowing measurement up to a bit more +254 than +/- 15 V. +255 +256 :returns float V_neg: the negative voltage. +257 :returns float V_pos: the positive voltage. +258 """ +259 V_neg = 1.3875*etol_call(DAQC2.getADC, (0,6)) +260 V_pos = 1.3729*etol_call(DAQC2.getADC, (0,7)) 261 -262 def start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, -263 minopenV): -264 """ -265 Will start the barriers including giving a little boost if running -266 voltage is below the starting voltage. -267 """ -268 DAC_V = calcDAC_V(speed, direction, maxcloseV, mincloseV, maxopenV, -269 minopenV) -270 # print("speed:"+str(speed)+", direction:"+str(direction)+", DAC_V:"+str(DAC_V)) -271 if (DAC_V > startopenV) and (DAC_V < startcloseV) and (direction != 0): -272 # need a boost to start moving -273 if speed == 1: -274 DAQC2.setDAC(0, 0, startcloseV) -275 else: -276 DAQC2.setDAC(0, 0, startopenV) -277 DAQC2.setDOUTbit(0, 0) -278 time.sleep(0.25) -279 DAQC2.setDAC(0, 0, DAC_V) -280 DAQC2.setDOUTbit(0, 0) -281 pass -282 -283 def write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen): -284 """Write the motorcal to a file in ~/.Trough/calibrations.""" -285 from AdvancedHTMLParser import AdvancedTag as Domel -286 from datetime import datetime -287 from time import time -288 from pathlib import Path -289 calib_div = Domel('div') -290 calib_title = Domel('h3') -291 calib_title.setAttribute('id','title') -292 calib_title.appendInnerHTML('Calibration of Motor') -293 calib_div.appendChild(calib_title) -294 -295 time_table = Domel('table') -296 time_table.setAttribute('class','time_table') -297 time_table.setAttribute('id', 'time_table') -298 time_table.setAttribute('border', '1') -299 tr = Domel('tr') -300 tr.appendInnerHTML('<th>ISO time</th><th>Timestamp</th>') -301 time_table.append(tr) -302 tr = Domel('tr') -303 timestamp = time() -304 isotime = (datetime.fromtimestamp(timestamp)).isoformat() -305 tr.appendInnerHTML('<td>' + str(isotime) + '</td>' -306 '<td id="timestamp">' + str(timestamp) + '</td>') -307 time_table.append(tr) -308 calib_div.append(time_table) -309 -310 calib_info = Domel('table') -311 calib_info.setAttribute('class', 'calib_info') -312 calib_info.setAttribute('id', 'calib_info') -313 calib_info.setAttribute('border', '1') -314 tr = Domel('tr') -315 tr.appendInnerHTML('<th>Calibration of</th><th>Max Value</th>' -316 '<th>Min Value</th><th>Start Value</th>') -317 calib_info.appendChild(tr) -318 tr = Domel('tr') -319 tr.appendInnerHTML('<th id = "name"> Opening </th>' -320 '<td id = "maxopen">' + str(maxopen) + '</td>' -321 '<td id = "minopen">' + str(minopen) + '</td>' -322 '<td id = "startopen">' + str(startopen) + '</td>') -323 calib_info.appendChild(tr) -324 tr = Domel('tr') -325 tr.appendInnerHTML('<th id = "name"> Closing </th>' -326 '<td id = "maxclose">' + str(maxclose) + '</td>' -327 '<td id = "minclose">' + str(minclose) + '</td>' -328 '<td id = "startclose">' + str(startclose) + '</td>') -329 calib_info.appendChild(tr) -330 calib_div.appendChild(calib_info) -331 -332 -333 fileext = '.trh.cal.html' -334 filename = 'motorcal'+fileext -335 dirpath = Path('~/.Trough/calibrations').expanduser() -336 fullpath = Path(dirpath, filename) -337 svhtml = '<!DOCTYPE html><html><body>' + calib_div.asHTML() + \ -338 '</body></html>' -339 f = open(fullpath, 'w') -340 f.write(svhtml) -341 f.close() -342 pass -343 -344 def read_motorcal(): -345 """Reads the motor calibration file in ~/.Trough/calibrations -346 if it exists and returns the calibration parameters.""" -347 from AdvancedHTMLParser import AdvancedHTMLParser as Parser -348 from pathlib import Path -349 from os.path import exists -350 -351 maxopen = 0 -352 minopen = 0 -353 startopen = 0 -354 maxclose = 0 -355 minclose = 0 -356 startclose = 0 -357 timestamp = 0 -358 -359 filepath = Path('~/.Trough/calibrations/motorcal.trh.cal.html').expanduser() -360 html = "" -361 if exists(filepath): -362 f = open(filepath, 'r') -363 html = f.read() -364 f.close() -365 document = Parser() -366 document.parseStr(html) -367 if document.getElementById('title').text.strip() != 'Calibration of Motor': -368 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp -369 maxclose = float(document.getElementById('maxclose').text) -370 minclose = float(document.getElementById('minclose').text) -371 startclose = float(document.getElementById('startclose').text) -372 maxopen = float(document.getElementById('maxopen').text) -373 minopen = float(document.getElementById('minopen').text) -374 startopen = float(document.getElementById('startopen').text) -375 timestamp = float(document.getElementById('timestamp').text) -376 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp -377 -378 def motorcal(barriermin, barriermax): -379 ''' -380 :param float barriermin: minimum voltage for barrier (do not open more). -381 :param float barriermax: maximum voltage for barrier (do not close more). -382 :returns float maxclose: DAC setting for maximum close speed. -383 :returns float minclose: DAC setting for minimum close speed. -384 :returns float startclose: DAC setting providing minimum voltage to start closing. -385 :returns float maxopen: DAC setting for maximum close speed. -386 :returns float minopen: DAC setting for minimum close speed. -387 :returns float startopen: DAC setting providing minimum voltage to start opening. -388 ''' -389 -390 import os, time -391 # Since this runs in a tight loop needs to take over watching barriers. -392 # Check if a trough controller is registered at /tmp/troughctl.pid. -393 # If one is store it's pid and replace with own. Will revert on exit without -394 # crashing. -395 ctlpid = take_over_barrier_monitoring() -396 -397 # Set base values -398 maxclose = 4 -399 minclose = 2.6 -400 startclose = 2.6 -401 maxopen = 0 -402 minopen = 2.4 -403 startopen = 2.4 -404 # Calibrate the barrier. This assumes a DAQC2 pi-plate is the controlling interface. -405 # First move to fully open. -406 # print('Moving to fully open...') -407 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -408 if position > barriermin: -409 # need to open some -410 atlimit = False -411 DAQC2.setDAC(0, 0, maxopen) # set fast open -412 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -413 time.sleep(1) -414 while not atlimit: -415 atlimit = barrier_at_limit_check(barriermin, barriermax) -416 # Get rid of any hysteresis in the close direction -417 # print('Removing close hysteresis...') -418 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -419 stoppos = position + 0.05 -420 if stoppos > barriermax: -421 stoppos = barriermax -422 DAQC2.setDAC(0, 0, maxclose) # set fast close -423 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -424 time.sleep(1) -425 atlimit = False -426 while not atlimit: -427 atlimit = barrier_at_limit_check(barriermin, stoppos) -428 # Find closing stall voltage -429 # print('Finding closing stall voltage...') -430 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -431 DAQC2.setDAC(0, 0, maxclose) # set fast close -432 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -433 testclose = maxclose -434 time.sleep(2) -435 atlimit = False -436 while not atlimit: -437 atlimit = barrier_at_limit_check(barriermin, barriermax) -438 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -439 # if position > 6.5: -440 # print ('old: '+str(oldposition)+' position: '+str(position)) -441 if (position - oldposition) < 0.01: -442 # because there can be noise take a new reading and check again -443 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -444 if (position - oldposition) < 0.01: -445 minclose = testclose -446 atlimit = True -447 DAQC2.clrDOUTbit(0, 0) -448 oldposition = position -449 testclose = testclose - 0.05 -450 DAQC2.setDAC(0, 0, testclose) -451 time.sleep(2) -452 -453 # Find minimum closing start voltage -454 # print('Finding minimum closing start voltage...') -455 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -456 startclose = minclose -457 atlimit = False -458 while not atlimit: -459 atlimit = barrier_at_limit_check(barriermin, barriermax) -460 DAQC2.setDAC(0, 0, startclose) -461 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -462 time.sleep(2) -463 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -464 if (position - oldposition) < 0.01: -465 # because there can be noise take a new reading and check again -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 startclose = startclose + 0.05 -469 else: -470 atlimit = True -471 DAQC2.clrDOUTbit(0, 0) -472 startclose = startclose + 0.05 # To provide a margin. -473 # Move to fully closed -474 # print('Moving to fully closed...') -475 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -476 if position < barriermax: -477 # need to close some -478 atlimit = False -479 DAQC2.setDAC(0, 0, maxclose) # set fast close -480 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -481 time.sleep(1) -482 while not atlimit: -483 atlimit = barrier_at_limit_check(barriermin, barriermax) -484 -485 # Get rid of any hysteresis in the open direction -486 # print('Removing open hysteresis...') -487 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -488 stoppos = position - 0.05 -489 if stoppos < barriermin: -490 stoppos = barriermin -491 DAQC2.setDAC(0, 0, maxopen) # set fast close -492 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -493 time.sleep(1) -494 atlimit = False -495 while not atlimit: -496 atlimit = barrier_at_limit_check(stoppos, barriermax) -497 -498 # Find openning stall voltage -499 # print('Finding openning stall voltage...') -500 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -501 DAQC2.setDAC(0, 0, maxopen) # set fast close -502 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -503 testopen = maxopen -504 time.sleep(2) -505 atlimit = False -506 while not atlimit: -507 atlimit = barrier_at_limit_check(barriermin, barriermax) -508 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -509 # if position > 6.5: -510 # print ('old: '+str(oldposition)+' position: '+str(position)) -511 if (oldposition - position) < 0.01: -512 # because there can be noise take a new reading and check again -513 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -514 if (oldposition - position) < 0.01: -515 minopen = testopen -516 atlimit = True -517 DAQC2.clrDOUTbit(0, 0) -518 oldposition = position -519 testopen = testopen + 0.05 -520 DAQC2.setDAC(0, 0, testopen) -521 time.sleep(2) -522 -523 # Find minimum opening start voltage -524 # print('Finding minimum openning start voltage...') -525 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -526 startopen = minopen -527 atlimit = False -528 while not atlimit: -529 atlimit = barrier_at_limit_check(barriermin, barriermax) -530 DAQC2.setDAC(0, 0, startopen) -531 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers -532 time.sleep(2) -533 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals -534 if (oldposition - position) < 0.01: -535 # because there can be noise take a new reading and check again -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 startopen = startopen - 0.05 -539 else: -540 atlimit = True -541 DAQC2.clrDOUTbit(0, 0) -542 startopen = startopen - 0.05 # To provide a margin. -543 -544 write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen) +262 return V_neg, V_pos +263 +264 def start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, +265 minopenV): +266 """ +267 Will start the barriers including giving a little boost if running +268 voltage is below the starting voltage. +269 """ +270 DAC_V = calcDAC_V(speed, direction, maxcloseV, mincloseV, maxopenV, +271 minopenV) +272 # print("speed:"+str(speed)+", direction:"+str(direction)+", DAC_V:"+str(DAC_V)) +273 if (DAC_V > startopenV) and (DAC_V < startcloseV) and (direction != 0): +274 # need a boost to start moving +275 if speed == 1: +276 DAQC2.setDAC(0, 0, startcloseV) +277 else: +278 DAQC2.setDAC(0, 0, startopenV) +279 DAQC2.setDOUTbit(0, 0) +280 time.sleep(0.25) +281 DAQC2.setDAC(0, 0, DAC_V) +282 DAQC2.setDOUTbit(0, 0) +283 pass +284 +285 def write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen): +286 """Write the motorcal to a file in ~/.Trough/calibrations.""" +287 from AdvancedHTMLParser import AdvancedTag as Domel +288 from datetime import datetime +289 from time import time +290 from pathlib import Path +291 calib_div = Domel('div') +292 calib_title = Domel('h3') +293 calib_title.setAttribute('id','title') +294 calib_title.appendInnerHTML('Calibration of Motor') +295 calib_div.appendChild(calib_title) +296 +297 time_table = Domel('table') +298 time_table.setAttribute('class','time_table') +299 time_table.setAttribute('id', 'time_table') +300 time_table.setAttribute('border', '1') +301 tr = Domel('tr') +302 tr.appendInnerHTML('<th>ISO time</th><th>Timestamp</th>') +303 time_table.append(tr) +304 tr = Domel('tr') +305 timestamp = time() +306 isotime = (datetime.fromtimestamp(timestamp)).isoformat() +307 tr.appendInnerHTML('<td>' + str(isotime) + '</td>' +308 '<td id="timestamp">' + str(timestamp) + '</td>') +309 time_table.append(tr) +310 calib_div.append(time_table) +311 +312 calib_info = Domel('table') +313 calib_info.setAttribute('class', 'calib_info') +314 calib_info.setAttribute('id', 'calib_info') +315 calib_info.setAttribute('border', '1') +316 tr = Domel('tr') +317 tr.appendInnerHTML('<th>Calibration of</th><th>Max Value</th>' +318 '<th>Min Value</th><th>Start Value</th>') +319 calib_info.appendChild(tr) +320 tr = Domel('tr') +321 tr.appendInnerHTML('<th id = "name"> Opening </th>' +322 '<td id = "maxopen">' + str(maxopen) + '</td>' +323 '<td id = "minopen">' + str(minopen) + '</td>' +324 '<td id = "startopen">' + str(startopen) + '</td>') +325 calib_info.appendChild(tr) +326 tr = Domel('tr') +327 tr.appendInnerHTML('<th id = "name"> Closing </th>' +328 '<td id = "maxclose">' + str(maxclose) + '</td>' +329 '<td id = "minclose">' + str(minclose) + '</td>' +330 '<td id = "startclose">' + str(startclose) + '</td>') +331 calib_info.appendChild(tr) +332 calib_div.appendChild(calib_info) +333 +334 +335 fileext = '.trh.cal.html' +336 filename = 'motorcal'+fileext +337 dirpath = Path('~/.Trough/calibrations').expanduser() +338 fullpath = Path(dirpath, filename) +339 svhtml = '<!DOCTYPE html><html><body>' + calib_div.asHTML() + \ +340 '</body></html>' +341 f = open(fullpath, 'w') +342 f.write(svhtml) +343 f.close() +344 pass +345 +346 def read_motorcal(): +347 """Reads the motor calibration file in ~/.Trough/calibrations +348 if it exists and returns the calibration parameters.""" +349 from AdvancedHTMLParser import AdvancedHTMLParser as Parser +350 from pathlib import Path +351 from os.path import exists +352 +353 maxopen = 0 +354 minopen = 0 +355 startopen = 0 +356 maxclose = 0 +357 minclose = 0 +358 startclose = 0 +359 timestamp = 0 +360 +361 filepath = Path('~/.Trough/calibrations/motorcal.trh.cal.html').expanduser() +362 html = "" +363 if exists(filepath): +364 f = open(filepath, 'r') +365 html = f.read() +366 f.close() +367 document = Parser() +368 document.parseStr(html) +369 if document.getElementById('title').text.strip() != 'Calibration of Motor': +370 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp +371 maxclose = float(document.getElementById('maxclose').text) +372 minclose = float(document.getElementById('minclose').text) +373 startclose = float(document.getElementById('startclose').text) +374 maxopen = float(document.getElementById('maxopen').text) +375 minopen = float(document.getElementById('minopen').text) +376 startopen = float(document.getElementById('startopen').text) +377 timestamp = float(document.getElementById('timestamp').text) +378 return maxclose, minclose, startclose, maxopen, minopen, startopen, timestamp +379 +380 def motorcal(barriermin, barriermax): +381 ''' +382 :param float barriermin: minimum voltage for barrier (do not open more). +383 :param float barriermax: maximum voltage for barrier (do not close more). +384 :returns float maxclose: DAC setting for maximum close speed. +385 :returns float minclose: DAC setting for minimum close speed. +386 :returns float startclose: DAC setting providing minimum voltage to start closing. +387 :returns float maxopen: DAC setting for maximum close speed. +388 :returns float minopen: DAC setting for minimum close speed. +389 :returns float startopen: DAC setting providing minimum voltage to start opening. +390 ''' +391 +392 import os, time +393 # Since this runs in a tight loop needs to take over watching barriers. +394 # Check if a trough controller is registered at /tmp/troughctl.pid. +395 # If one is store it's pid and replace with own. Will revert on exit without +396 # crashing. +397 ctlpid = take_over_barrier_monitoring() +398 +399 # Set base values +400 maxclose = 4 +401 minclose = 2.6 +402 startclose = 2.6 +403 maxopen = 0 +404 minopen = 2.4 +405 startopen = 2.4 +406 # Calibrate the barrier. This assumes a DAQC2 pi-plate is the controlling interface. +407 # First move to fully open. +408 # print('Moving to fully open...') +409 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +410 if position > barriermin: +411 # need to open some +412 atlimit = False +413 DAQC2.setDAC(0, 0, maxopen) # set fast open +414 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +415 time.sleep(1) +416 while not atlimit: +417 atlimit = barrier_at_limit_check(barriermin, barriermax) +418 # Get rid of any hysteresis in the close direction +419 # print('Removing close hysteresis...') +420 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +421 stoppos = position + 0.05 +422 if stoppos > barriermax: +423 stoppos = barriermax +424 DAQC2.setDAC(0, 0, maxclose) # set fast close +425 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +426 time.sleep(1) +427 atlimit = False +428 while not atlimit: +429 atlimit = barrier_at_limit_check(barriermin, stoppos) +430 # Find closing stall voltage +431 # print('Finding closing stall voltage...') +432 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +433 DAQC2.setDAC(0, 0, maxclose) # set fast close +434 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +435 testclose = maxclose +436 time.sleep(2) +437 atlimit = False +438 while not atlimit: +439 atlimit = barrier_at_limit_check(barriermin, barriermax) +440 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +441 # if position > 6.5: +442 # print ('old: '+str(oldposition)+' position: '+str(position)) +443 if (position - oldposition) < 0.01: +444 # because there can be noise take a new reading and check again +445 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +446 if (position - oldposition) < 0.01: +447 minclose = testclose +448 atlimit = True +449 DAQC2.clrDOUTbit(0, 0) +450 oldposition = position +451 testclose = testclose - 0.05 +452 DAQC2.setDAC(0, 0, testclose) +453 time.sleep(2) +454 +455 # Find minimum closing start voltage +456 # print('Finding minimum closing start voltage...') +457 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +458 startclose = minclose +459 atlimit = False +460 while not atlimit: +461 atlimit = barrier_at_limit_check(barriermin, barriermax) +462 DAQC2.setDAC(0, 0, startclose) +463 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +464 time.sleep(2) +465 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +466 if (position - oldposition) < 0.01: +467 # because there can be noise take a new reading and check again +468 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +469 if (position - oldposition) < 0.01: +470 startclose = startclose + 0.05 +471 else: +472 atlimit = True +473 DAQC2.clrDOUTbit(0, 0) +474 startclose = startclose + 0.05 # To provide a margin. +475 # Move to fully closed +476 # print('Moving to fully closed...') +477 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +478 if position < barriermax: +479 # need to close some +480 atlimit = False +481 DAQC2.setDAC(0, 0, maxclose) # set fast close +482 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +483 time.sleep(1) +484 while not atlimit: +485 atlimit = barrier_at_limit_check(barriermin, barriermax) +486 +487 # Get rid of any hysteresis in the open direction +488 # print('Removing open hysteresis...') +489 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +490 stoppos = position - 0.05 +491 if stoppos < barriermin: +492 stoppos = barriermin +493 DAQC2.setDAC(0, 0, maxopen) # set fast close +494 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +495 time.sleep(1) +496 atlimit = False +497 while not atlimit: +498 atlimit = barrier_at_limit_check(stoppos, barriermax) +499 +500 # Find openning stall voltage +501 # print('Finding openning stall voltage...') +502 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +503 DAQC2.setDAC(0, 0, maxopen) # set fast close +504 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +505 testopen = maxopen +506 time.sleep(2) +507 atlimit = False +508 while not atlimit: +509 atlimit = barrier_at_limit_check(barriermin, barriermax) +510 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +511 # if position > 6.5: +512 # print ('old: '+str(oldposition)+' position: '+str(position)) +513 if (oldposition - position) < 0.01: +514 # because there can be noise take a new reading and check again +515 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +516 if (oldposition - position) < 0.01: +517 minopen = testopen +518 atlimit = True +519 DAQC2.clrDOUTbit(0, 0) +520 oldposition = position +521 testopen = testopen + 0.05 +522 DAQC2.setDAC(0, 0, testopen) +523 time.sleep(2) +524 +525 # Find minimum opening start voltage +526 # print('Finding minimum openning start voltage...') +527 oldposition = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +528 startopen = minopen +529 atlimit = False +530 while not atlimit: +531 atlimit = barrier_at_limit_check(barriermin, barriermax) +532 DAQC2.setDAC(0, 0, startopen) +533 DAQC2.setDOUTbit(0, 0) # turn on power/start barriers +534 time.sleep(2) +535 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +536 if (oldposition - position) < 0.01: +537 # because there can be noise take a new reading and check again +538 position = round(etol_call(DAQC2.getADC,(0, 0)) - etol_call(DAQC2.getADC,(0, 1)), 2) # not stable past 2 decimals +539 if (oldposition - position) < 0.01: +540 startopen = startopen - 0.05 +541 else: +542 atlimit = True +543 DAQC2.clrDOUTbit(0, 0) +544 startopen = startopen - 0.05 # To provide a margin. 545 -546 # Return control to previous trough controller -547 return_barrier_monitoring_to_prev_process(ctlpid) -548 -549 # Return the results -550 return (maxclose, minclose, startclose, maxopen, minopen, startopen) -551 -552 def calcDAC_V(speed, direction, maxclose, minclose, maxopen, minopen): -553 ''' -554 :param float speed: fraction of maximum speed -555 :param int direction: -1 close, 0 don't move, 1 open -556 :param float maxclose: DAC V for maximum close speed -557 :param float minclose: DAC V for minimum close speed -558 :param float maxopen: DAC V for maximum open speed -559 :param float minopen: DAC V for minimum open speed -560 -561 :return float DAC_V: -562 ''' -563 DAC_V = (minclose+minopen)/2 # default to not moving -564 if direction == -1: -565 DAC_V = (maxclose-minclose)*speed + minclose -566 if direction == 1: -567 DAC_V = (maxopen - minopen)*speed + minopen -568 return DAC_V -569 -570 # Take over the barrier monitoring because we are in a tight loop. -571 # Unless some other process tries to access the A-to-D this should -572 # make most of the fault tolerance unnecessary. -573 ctlpid = take_over_barrier_monitoring() -574 # TODO Should this all be wrapped in a try... so that if -575 # anything stops this it gives up barrier monitoring? -576 pos_F = deque(maxlen=20) -577 pos_F_std = deque(maxlen=20) -578 pos_V = deque(maxlen=20) -579 pos_V_std = deque(maxlen=20) -580 bal_V = deque(maxlen=20) -581 bal_std = deque(maxlen=20) -582 therm_V = deque(maxlen=20) -583 therm_std = deque(maxlen=20) -584 time_stamp = deque(maxlen=20) -585 cmd_deque = deque() -586 messages = deque() -587 que_lst = [time_stamp, pos_F, pos_F_std, bal_V, bal_std, therm_V, therm_std, messages] -588 que_lst_labels = ["time stamp", "position fraction", "position standard deviation", -589 "balance voltage", "balance standard deviation", "thermistor voltage", -590 "thermistor standard deviation", "messages"] -591 timedelta = 0.500 # seconds -592 openmin = 0.02 # minimum voltage allowed when opening. -593 openlimit = openmin -594 closemax = 7.40 # maximum voltage allowed when closing. -595 closelimit = closemax -596 # Check that power supply is on. If not we cannot do anything. -597 PS_minus, PS_plus = get_power_supply_volts() -598 if PS_minus > -10 or PS_plus < 10: -599 messages.append("ERROR") -600 messages.append("Power supply appears to be off (or malfunctioning). Try again...") -601 DATAPipe.send(bundle_to_send(que_lst)) -602 # make sure no power to barriers -603 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -604 # Close connections -605 DATAPipe.close() -606 CTLPipe.close() -607 # give up process id -608 return_barrier_monitoring_to_prev_process(ctlpid) -609 run = False -610 exit() -611 messages.append("Checking Motor Calibration. Please wait...") -612 DATAPipe.send(bundle_to_send(que_lst)) -613 messages.clear() -614 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV, lasttime = read_motorcal() -615 # check that the calibration is 12 hours or less old -616 if lasttime < time.time() - 43200: -617 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal(openlimit, closelimit) -618 messages.append("Trough ready") -619 DATAPipe.send(bundle_to_send(que_lst)) -620 messages.clear() -621 speed = 0 # 0 to 1 fraction of maximum speed. -622 direction = 0 # -1 close, 0 don't move, 1 open -623 run = True -624 loopcount = 0 -625 while run: -626 poshigh = [] -627 poslow = [] -628 balhigh = [] -629 ballow = [] -630 thermhigh = [] -631 thermlow = [] -632 -633 starttime = time.time() -634 stopat = starttime + timedelta - 0.200 # leave 200 ms for communications and control -635 #loopcount +=1 -636 #print('Starting a data record: '+str(loopcount)) -637 #itcount = 0 -638 while (time.time() < stopat): -639 tempposlow = None -640 tempposhigh= None -641 tempballow = None -642 tempbalhigh = None -643 tempthermlow = None -644 tempthermhigh = None -645 #itcount +=1 -646 #print(str(itcount)+",",end = "") -647 tempposlow = etol_call(DAQC2.getADC,(0, 1)) -648 temposhigh = etol_call(DAQC2.getADC,(0, 0)) -649 tempballow = etol_call(DAQC2.getADC,(0, 3)) -650 tempbalhigh = etol_call(DAQC2.getADC,(0, 2)) -651 tempthermlow = etol_call(DAQC2.getADC,(0, 4)) -652 tempthermhigh = etol_call(DAQC2.getADC,(0, 5)) -653 datagood = True -654 if isnumber(tempposlow): -655 poslow.append(tempposlow) -656 else: -657 datagood = False -658 if isnumber(temposhigh): -659 poshigh.append(temposhigh) -660 else: -661 datagood = False -662 if isnumber(tempballow): -663 ballow.append(tempballow) -664 else: -665 datagood = False -666 if isnumber(tempbalhigh): -667 balhigh.append(tempbalhigh) -668 else: -669 datagood = False -670 if isnumber(tempthermlow): -671 thermlow.append(tempthermlow) -672 else: -673 datagood = False -674 if isnumber(tempthermhigh): -675 thermhigh.append(tempthermhigh) -676 else: -677 datagood = False -678 if not datagood: -679 return_barrier_monitoring_to_prev_process(ctlpid) -680 raise ValueError('Not getting numeric values from A-to-D!') -681 -682 time_stamp.append((starttime + stopat) / 2) -683 # position -684 #print("poshigh: "+str(poshigh)) -685 ndata = len(poshigh) -686 if ndata >= 1: -687 high = np.array(poshigh, dtype=np.float64) -688 #print("poslow: "+str(poslow)) -689 low = np.array(poslow, dtype=np.float64) -690 vals = high - low -691 avg = (np.sum(vals)) / ndata -692 stdev = np.std(vals, ddof=1, dtype=np.float64) -693 stdev_avg = stdev / np.sqrt(float(ndata)) -694 pos_frac = 1.00 - (avg - openmin)/(closemax - openmin) -695 pos_frac_std = stdev_avg/(closemax - openmin) -696 pos_V.append(avg) -697 pos_V_std.append(stdev_avg) -698 pos_F.append(pos_frac) -699 pos_F_std.append(pos_frac_std) -700 # balance -701 ndata = len(balhigh) -702 high = np.array(balhigh, dtype=np.float64) -703 low = np.array(ballow, dtype=np.float64) -704 vals = high - low -705 avg = (np.sum(vals)) / ndata -706 stdev = np.std(vals, ddof=1, dtype=np.float64) -707 stdev_avg = stdev / np.sqrt(float(ndata)) -708 bal_V.append(avg) -709 bal_std.append(stdev_avg) -710 # thermistor -711 ndata = len(thermhigh) -712 high = np.array(thermhigh, dtype=np.float64) -713 low = np.array(thermlow, dtype=np.float64) -714 vals = high - low -715 avg = (np.sum(vals)) / ndata -716 stdev = np.std(vals, ddof=1, dtype=np.float64) -717 stdev_avg = stdev / np.sqrt(float(ndata)) -718 therm_V.append(avg) -719 therm_std.append(stdev_avg) -720 -721 # Check barrier positions -722 if openlimit < openmin: -723 openlimit = openmin -724 if closelimit > closemax: -725 closelimit = closemax -726 barrier_at_limit = barrier_at_limit_check(openlimit,closelimit) -727 # TODO: Send warnings and error messages -728 # Check command pipe and update command queue -729 #print('Checking commands...') -730 while CTLPipe.poll(): -731 cmd_deque.append(CTLPipe.recv()) -732 # Each command is a python list. -733 # element 1: cmd name (string) -734 # element 2: single command parameter (number or string) -735 # Execute commands in queue -736 #print('Executing commands...') -737 while len(cmd_deque) > 0: -738 cmd = cmd_deque.popleft() -739 # execute the command -740 #print(str(cmd)) -741 if cmd[0] == 'Stop': -742 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -743 elif cmd[0] == 'Send': -744 #print('Got a "Send" command.') -745 # send current contents of the data deques -746 DATAPipe.send(bundle_to_send(que_lst)) -747 # purge the sent content -748 time_stamp.clear() -749 pos_V.clear() -750 pos_V_std.clear() -751 pos_F.clear() -752 pos_F_std.clear() -753 bal_V.clear() -754 bal_std.clear() -755 therm_V.clear() -756 therm_std.clear() -757 messages.clear() -758 elif cmd[0] == 'Start': -759 # start barriers moving using current direction and speed -760 if speed > 1: -761 speed = 1 -762 if speed < 0: -763 speed = 0 -764 if (direction != -1) and (direction != 0) and (direction != 1): -765 direction = 0 -766 closelimit = closemax -767 openlimit = openmin -768 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, -769 minopenV) -770 elif cmd[0] == 'Direction': -771 # set the direction -772 direction = cmd[1] -773 if (direction != -1) and (direction != 0) and (direction != 1): -774 direction = 0 -775 elif cmd[0] == 'Speed': -776 # set the speed -777 speed = cmd[1] -778 if speed > 1: -779 speed = 1 -780 if speed < 0: -781 speed = 0 -782 pass -783 elif cmd[0] == 'MoveTo': -784 # Move to fraction of open 0 .. 1. -785 # set the stop position -786 to_pos = (1.0-float(cmd[1]))*closemax + float(cmd[1])*openmin -787 # adjust direction if necessary -788 # get current position -789 position = etol_call(DAQC2.getADC,(0, 0)) - \ -790 etol_call(DAQC2.getADC,(0, 1)) -791 if position > to_pos: -792 direction = 1 -793 openlimit = to_pos -794 else: -795 direction = -1 -796 closelimit = to_pos -797 # start the barriers -798 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, -799 minopenV) -800 pass -801 elif cmd[0] == 'MotorCal': -802 # calibrate the voltages for starting motor and speeds -803 messages.append("Checking Motor Calibration. Please wait...") -804 DATAPipe.send(bundle_to_send(que_lst)) -805 messages.clear() -806 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal( -807 openlimit, closelimit) -808 messages.append("Trough ready") -809 DATAPipe.send(bundle_to_send(que_lst)) -810 messages.clear() -811 pass -812 elif cmd[0] == 'ConstPi': -813 # maintain a constant pressure -814 # not yet implemented -815 messages.append('Constant pressure mode not yet implemented.') -816 DATAPipe.send(bundle_to_send(que_lst)) -817 messages.clear() -818 pass -819 elif cmd[0] == 'DataLabels': -820 # send data labels as a message -821 messages.append(que_lst_labels) -822 # send current contents of the data deques -823 DATAPipe.send(bundle_to_send(que_lst)) -824 # purge the sent content -825 time_stamp.clear() -826 pos_V.clear() -827 pos_V_std.clear() -828 bal_V.clear() -829 bal_std.clear() -830 therm_V.clear() -831 therm_std.clear() -832 messages.clear() -833 elif cmd[0] == 'ShutDown': -834 # shutdown trough -835 # make sure no power to barriers -836 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers -837 # Close connections -838 DATAPipe.close() -839 CTLPipe.close() -840 # give up process id -841 return_barrier_monitoring_to_prev_process(ctlpid) -842 run = False -843 exit() -844 # Delay if have not used up all 200 ms -845 used = time.time() - starttime -846 if used < 0.495: -847 time.sleep(0.495 - used) -848 # shutdown automatically if no communication from controller? +546 write_motorcal(maxclose, minclose, startclose, maxopen, minopen, startopen) +547 +548 # Return control to previous trough controller +549 return_barrier_monitoring_to_prev_process(ctlpid) +550 +551 # Return the results +552 return (maxclose, minclose, startclose, maxopen, minopen, startopen) +553 +554 def calcDAC_V(speed, direction, maxclose, minclose, maxopen, minopen): +555 ''' +556 :param float speed: fraction of maximum speed +557 :param int direction: -1 close, 0 don't move, 1 open +558 :param float maxclose: DAC V for maximum close speed +559 :param float minclose: DAC V for minimum close speed +560 :param float maxopen: DAC V for maximum open speed +561 :param float minopen: DAC V for minimum open speed +562 +563 :return float DAC_V: +564 ''' +565 DAC_V = (minclose+minopen)/2 # default to not moving +566 if direction == -1: +567 DAC_V = (maxclose-minclose)*speed + minclose +568 if direction == 1: +569 DAC_V = (maxopen - minopen)*speed + minopen +570 return DAC_V +571 +572 # Take over the barrier monitoring because we are in a tight loop. +573 # Unless some other process tries to access the A-to-D this should +574 # make most of the fault tolerance unnecessary. +575 ctlpid = take_over_barrier_monitoring() +576 # TODO Should this all be wrapped in a try... so that if +577 # anything stops this it gives up barrier monitoring? +578 pos_F = deque(maxlen=20) +579 pos_F_std = deque(maxlen=20) +580 pos_V = deque(maxlen=20) +581 pos_V_std = deque(maxlen=20) +582 bal_V = deque(maxlen=20) +583 bal_std = deque(maxlen=20) +584 therm_V = deque(maxlen=20) +585 therm_std = deque(maxlen=20) +586 time_stamp = deque(maxlen=20) +587 cmd_deque = deque() +588 messages = deque() +589 que_lst = [time_stamp, pos_F, pos_F_std, bal_V, bal_std, therm_V, therm_std, messages] +590 que_lst_labels = ["time stamp", "position fraction", "position standard deviation", +591 "balance voltage", "balance standard deviation", "thermistor voltage", +592 "thermistor standard deviation", "messages"] +593 timedelta = 0.500 # seconds +594 openmin = 0.02 # minimum voltage allowed when opening. +595 openlimit = openmin +596 closemax = 7.40 # maximum voltage allowed when closing. +597 closelimit = closemax +598 # Check that power supply is on. If not we cannot do anything. +599 PS_minus, PS_plus = get_power_supply_volts() +600 if PS_minus > -10 or PS_plus < 10: +601 messages.append("ERROR") +602 messages.append("Power supply appears to be off (or malfunctioning). Try again...") +603 DATAPipe.send(bundle_to_send(que_lst)) +604 # make sure no power to barriers +605 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +606 # Close connections +607 DATAPipe.close() +608 CTLPipe.close() +609 # give up process id +610 return_barrier_monitoring_to_prev_process(ctlpid) +611 run = False +612 exit() +613 messages.append("Checking Motor Calibration. Please wait...") +614 DATAPipe.send(bundle_to_send(que_lst)) +615 messages.clear() +616 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV, lasttime = read_motorcal() +617 # check that the calibration is 12 hours or less old +618 if lasttime < time.time() - 43200: +619 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal(openlimit, closelimit) +620 messages.append("Trough ready") +621 DATAPipe.send(bundle_to_send(que_lst)) +622 messages.clear() +623 speed = 0 # 0 to 1 fraction of maximum speed. +624 direction = 0 # -1 close, 0 don't move, 1 open +625 run = True +626 loopcount = 0 +627 while run: +628 poshigh = [] +629 poslow = [] +630 balhigh = [] +631 ballow = [] +632 thermhigh = [] +633 thermlow = [] +634 +635 starttime = time.time() +636 stopat = starttime + timedelta - 0.200 # leave 200 ms for communications and control +637 #loopcount +=1 +638 #print('Starting a data record: '+str(loopcount)) +639 #itcount = 0 +640 while (time.time() < stopat): +641 tempposlow = None +642 tempposhigh= None +643 tempballow = None +644 tempbalhigh = None +645 tempthermlow = None +646 tempthermhigh = None +647 #itcount +=1 +648 #print(str(itcount)+",",end = "") +649 tempposlow = etol_call(DAQC2.getADC,(0, 1)) +650 temposhigh = etol_call(DAQC2.getADC,(0, 0)) +651 tempballow = etol_call(DAQC2.getADC,(0, 3)) +652 tempbalhigh = etol_call(DAQC2.getADC,(0, 2)) +653 tempthermlow = etol_call(DAQC2.getADC,(0, 4)) +654 tempthermhigh = etol_call(DAQC2.getADC,(0, 5)) +655 datagood = True +656 if isnumber(tempposlow): +657 poslow.append(tempposlow) +658 else: +659 datagood = False +660 if isnumber(temposhigh): +661 poshigh.append(temposhigh) +662 else: +663 datagood = False +664 if isnumber(tempballow): +665 ballow.append(tempballow) +666 else: +667 datagood = False +668 if isnumber(tempbalhigh): +669 balhigh.append(tempbalhigh) +670 else: +671 datagood = False +672 if isnumber(tempthermlow): +673 thermlow.append(tempthermlow) +674 else: +675 datagood = False +676 if isnumber(tempthermhigh): +677 thermhigh.append(tempthermhigh) +678 else: +679 datagood = False +680 if not datagood: +681 return_barrier_monitoring_to_prev_process(ctlpid) +682 raise ValueError('Not getting numeric values from A-to-D!') +683 +684 time_stamp.append((starttime + stopat) / 2) +685 # position +686 #print("poshigh: "+str(poshigh)) +687 ndata = len(poshigh) +688 if ndata >= 1: +689 high = np.array(poshigh, dtype=np.float64) +690 #print("poslow: "+str(poslow)) +691 low = np.array(poslow, dtype=np.float64) +692 vals = high - low +693 avg = (np.sum(vals)) / ndata +694 stdev = np.std(vals, ddof=1, dtype=np.float64) +695 stdev_avg = stdev / np.sqrt(float(ndata)) +696 pos_frac = 1.00 - (avg - openmin)/(closemax - openmin) +697 pos_frac_std = stdev_avg/(closemax - openmin) +698 pos_V.append(avg) +699 pos_V_std.append(stdev_avg) +700 pos_F.append(pos_frac) +701 pos_F_std.append(pos_frac_std) +702 # balance +703 ndata = len(balhigh) +704 high = np.array(balhigh, dtype=np.float64) +705 low = np.array(ballow, dtype=np.float64) +706 vals = high - low +707 avg = (np.sum(vals)) / ndata +708 stdev = np.std(vals, ddof=1, dtype=np.float64) +709 stdev_avg = stdev / np.sqrt(float(ndata)) +710 bal_V.append(avg) +711 bal_std.append(stdev_avg) +712 # thermistor +713 ndata = len(thermhigh) +714 high = np.array(thermhigh, dtype=np.float64) +715 low = np.array(thermlow, dtype=np.float64) +716 vals = high - low +717 avg = (np.sum(vals)) / ndata +718 stdev = np.std(vals, ddof=1, dtype=np.float64) +719 stdev_avg = stdev / np.sqrt(float(ndata)) +720 therm_V.append(avg) +721 therm_std.append(stdev_avg) +722 +723 # Check barrier positions +724 if openlimit < openmin: +725 openlimit = openmin +726 if closelimit > closemax: +727 closelimit = closemax +728 barrier_at_limit = barrier_at_limit_check(openlimit,closelimit) +729 # TODO: Send warnings and error messages +730 # Check command pipe and update command queue +731 #print('Checking commands...') +732 while CTLPipe.poll(): +733 cmd_deque.append(CTLPipe.recv()) +734 # Each command is a python list. +735 # element 1: cmd name (string) +736 # element 2: single command parameter (number or string) +737 # Execute commands in queue +738 #print('Executing commands...') +739 while len(cmd_deque) > 0: +740 cmd = cmd_deque.popleft() +741 # execute the command +742 #print(str(cmd)) +743 if cmd[0] == 'Stop': +744 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +745 elif cmd[0] == 'Send': +746 #print('Got a "Send" command.') +747 # send current contents of the data deques +748 DATAPipe.send(bundle_to_send(que_lst)) +749 # purge the sent content +750 time_stamp.clear() +751 pos_V.clear() +752 pos_V_std.clear() +753 pos_F.clear() +754 pos_F_std.clear() +755 bal_V.clear() +756 bal_std.clear() +757 therm_V.clear() +758 therm_std.clear() +759 messages.clear() +760 elif cmd[0] == 'Start': +761 # start barriers moving using current direction and speed +762 if speed > 1: +763 speed = 1 +764 if speed < 0: +765 speed = 0 +766 if (direction != -1) and (direction != 0) and (direction != 1): +767 direction = 0 +768 closelimit = closemax +769 openlimit = openmin +770 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, +771 minopenV) +772 elif cmd[0] == 'Direction': +773 # set the direction +774 direction = cmd[1] +775 if (direction != -1) and (direction != 0) and (direction != 1): +776 direction = 0 +777 elif cmd[0] == 'Speed': +778 # set the speed +779 speed = cmd[1] +780 if speed > 1: +781 speed = 1 +782 if speed < 0: +783 speed = 0 +784 pass +785 elif cmd[0] == 'MoveTo': +786 # Move to fraction of open 0 .. 1. +787 # set the stop position +788 to_pos = (1.0-float(cmd[1]))*closemax + float(cmd[1])*openmin +789 # adjust direction if necessary +790 # get current position +791 position = etol_call(DAQC2.getADC,(0, 0)) - \ +792 etol_call(DAQC2.getADC,(0, 1)) +793 if position > to_pos: +794 direction = 1 +795 openlimit = to_pos +796 else: +797 direction = -1 +798 closelimit = to_pos +799 # start the barriers +800 start_barriers(speed, direction, maxcloseV, mincloseV, maxopenV, +801 minopenV) +802 pass +803 elif cmd[0] == 'MotorCal': +804 # calibrate the voltages for starting motor and speeds +805 messages.append("Checking Motor Calibration. Please wait...") +806 DATAPipe.send(bundle_to_send(que_lst)) +807 messages.clear() +808 maxcloseV, mincloseV, startcloseV, maxopenV, minopenV, startopenV = motorcal( +809 openlimit, closelimit) +810 messages.append("Trough ready") +811 DATAPipe.send(bundle_to_send(que_lst)) +812 messages.clear() +813 pass +814 elif cmd[0] == 'ConstPi': +815 # maintain a constant pressure +816 # not yet implemented +817 messages.append('Constant pressure mode not yet implemented.') +818 DATAPipe.send(bundle_to_send(que_lst)) +819 messages.clear() +820 pass +821 elif cmd[0] == 'DataLabels': +822 # send data labels as a message +823 messages.append(que_lst_labels) +824 # send current contents of the data deques +825 DATAPipe.send(bundle_to_send(que_lst)) +826 # purge the sent content +827 time_stamp.clear() +828 pos_V.clear() +829 pos_V_std.clear() +830 bal_V.clear() +831 bal_std.clear() +832 therm_V.clear() +833 therm_std.clear() +834 messages.clear() +835 elif cmd[0] == 'ShutDown': +836 # shutdown trough +837 # make sure no power to barriers +838 DAQC2.clrDOUTbit(0, 0) # switch off power/stop barriers +839 # Close connections +840 DATAPipe.close() +841 CTLPipe.close() +842 # give up process id +843 return_barrier_monitoring_to_prev_process(ctlpid) +844 run = False +845 exit() +846 # Delay if have not used up all 200 ms +847 used = time.time() - starttime +848 if used < 0.495: +849 time.sleep(0.495 - used) +850 # shutdown automatically if no communication from controller? @@ -1137,26 +1139,28 @@

Returns

100 from piplates import DAQC2plate 101 del DAQC2plate 102 except Exception as e: -103 trough_exists = False -104 if trough_exists: -105 TROUGH = Process(target=troughctl, args=(cmdrcv, datasend)) -106 else: -107 print("Unable to find Trough. Using simulation.") -108 from Trough_Control.simulation import simulated_troughctl -109 TROUGH = Process(target=simulated_troughctl, args=(cmdrcv, datasend)) -110 TROUGH.start() -111 time.sleep(0.2) -112 waiting = True -113 while waiting: -114 if datarcv.poll(): -115 datapkg = datarcv.recv() -116 messages = extract_messages(datapkg) -117 print(str(messages)) -118 if "ERROR" in messages: -119 exit() -120 if "Trough ready" in messages: -121 waiting = False -122 return cmdsend, datarcv, TROUGH +103 print("An issue was encountered while accessing the DAQC2plate:" + +104 str(e)) +105 trough_exists = False +106 if trough_exists: +107 TROUGH = Process(target=troughctl, args=(cmdrcv, datasend)) +108 else: +109 print("Unable to find Trough. Using simulation.") +110 from Trough_Control.simulation import simulated_troughctl +111 TROUGH = Process(target=simulated_troughctl, args=(cmdrcv, datasend)) +112 TROUGH.start() +113 time.sleep(0.2) +114 waiting = True +115 while waiting: +116 if datarcv.poll(): +117 datapkg = datarcv.recv() +118 messages = extract_messages(datapkg) +119 print(str(messages)) +120 if "ERROR" in messages: +121 exit() +122 if "Trough ready" in messages: +123 waiting = False +124 return cmdsend, datarcv, TROUGH @@ -1188,730 +1192,730 @@

Returns

-
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
+            
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?
 
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 @@

API Documentation

-
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
built with pdocAPI Documentation -
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
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
@@ -857,324 +858,325 @@

70 self.livefig.update_xaxes(title="$Area\,(cm^2)$") 71 if self.units == 'Angstrom^2/molec': 72 x_data = self.df['Area per molecule (A^2)'] - 73 self.livefig.update_xaxes(title="$Area per 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 + 73 self.livefig.update_xaxes(title="$Area\,per\," + 74 "molecule\,({\overset{\circ}{A}}^2)$") + 75 else: + 76 x_data = self.df['time_stamp']-self.df['time_stamp'][0] + 77 self.livefig.add_scatter(y=self.df['Surface Pressure (mN/m)'], x=x_data) + 78 + 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) +110 +111 def run_caption(self): +112 """Returns an html table with info about the run to use as a caption""" +113 caption = '' +114 caption += '<table><tr><th>Run ID</th><th>Title</th>' +115 caption += '<th>Storage File Name</th>' +116 caption += '<th>Target (' + str(self.units) + ')</th>' +117 caption += '<th>Speed (' + str(self.units) + '/min)</th>' +118 caption += '<th>Moles</th><th>Plate Circumference (mm)</th></tr>' +119 caption += '<tr><td>' + str(self.id) + '</td>' +120 caption += '<td>' + str(self.title) + '</td>' +121 caption += '<td>' + str(self.filename) + '</td>' +122 caption += '<td>' + str(self.target) + '</td>' +123 caption += '<td>' + str(self.speed) + '</td>' +124 caption += '<td>' + str(self.moles) + '</td>' +125 caption += '<td>' + str(self.plate_circ) + '</td></tr>' +126 caption += '</table>' +127 return caption +128 +129 def _end_of_run(self): +130 from IPython import get_ipython +131 Trough_GUI = get_ipython().user_ns["Trough_GUI"] +132 # This hides the control stuff +133 self.close_collect_control() +134 # Store data +135 self.write_run('') +136 # start background updating +137 if not Trough_GUI.updater_running.value: +138 Trough_GUI.run_updater.value = True +139 Trough_GUI.start_status_updater() +140 return +141 +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 return +301 +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 return +308 +309 def __repr__(self): +310 from IPython.display import HTML, display +311 display(self.livefig) +312 display(HTML(self.run_caption())) +313 return '' +314 +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() +346 +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 return @@ -1250,10 +1252,11 @@

70 self.livefig.update_xaxes(title="$Area\,(cm^2)$") 71 if self.units == 'Angstrom^2/molec': 72 x_data = self.df['Area per molecule (A^2)'] -73 self.livefig.update_xaxes(title="$Area per 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) +73 self.livefig.update_xaxes(title="$Area\,per\," +74 "molecule\,({\overset{\circ}{A}}^2)$") +75 else: +76 x_data = self.df['time_stamp']-self.df['time_stamp'][0] +77 self.livefig.add_scatter(y=self.df['Surface Pressure (mN/m)'], x=x_data) @@ -1296,37 +1299,37 @@

Parameters

-
 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)
+            
 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)
 
@@ -1356,23 +1359,23 @@

Returns

-
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
+            
111    def run_caption(self):
+112        """Returns an html table with info about the run to use as a caption"""
+113        caption = ''
+114        caption += '<table><tr><th>Run ID</th><th>Title</th>'
+115        caption += '<th>Storage File Name</th>'
+116        caption += '<th>Target (' + str(self.units) + ')</th>'
+117        caption += '<th>Speed (' + str(self.units) + '/min)</th>'
+118        caption += '<th>Moles</th><th>Plate Circumference (mm)</th></tr>'
+119        caption += '<tr><td>' + str(self.id) + '</td>'
+120        caption += '<td>' + str(self.title) + '</td>'
+121        caption += '<td>' + str(self.filename) + '</td>'
+122        caption += '<td>' + str(self.target) + '</td>'
+123        caption += '<td>' + str(self.speed) + '</td>'
+124        caption += '<td>' + str(self.moles) + '</td>'
+125        caption += '<td>' + str(self.plate_circ) + '</td></tr>'
+126        caption += '</table>'
+127        return caption
 
@@ -1392,165 +1395,165 @@

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
+            
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        return
 
@@ -1571,12 +1574,12 @@

Returns

-
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
+            
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        return
 
@@ -1597,37 +1600,37 @@

Returns

-
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()
+            
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()
 
@@ -1647,51 +1650,51 @@

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
+            
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        return
 
@@ -1726,114 +1729,114 @@

Parameters

-
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
+            
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    return
 
@@ -1863,78 +1866,78 @@

Parameters

-
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
+            
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    return
 
@@ -1986,103 +1989,103 @@

Parameters

-
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
+            
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    return
 
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 @@

API Documentation

-
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
built with pdocAPI Documentation -
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
built with pdocAPI Documentation -
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
built with pdocAPI Documentation -
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
built with pdocAPI Documentation -
Langmuir_Trough v0.5.0
+
Langmuir_Trough v0.5.1
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',