From 6bc876766e2695e4f3f5afa8eb7dc1435fc4bdc3 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 21 Jul 2016 14:08:38 +0100 Subject: [PATCH 0001/1466] Initial commit --- .gitignore | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..72364f99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,89 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject From f2cd48091059a6fc57bcef91aec97627e0997644 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 21 Jul 2016 14:09:17 +0100 Subject: [PATCH 0002/1466] Create README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..92e4666e --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# DeviceEmulator +Emulates physical devices with a simple Telnet server + +[Developer notes](https://github.com/ISISComputingGroup/ibex_developers_manual/wiki/Emulating-Devices/) From af770cf25d01996c07e94daca45b6c0c44065c56 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 21 Jul 2016 14:39:48 +0100 Subject: [PATCH 0003/1466] Add starter emulators and test framework --- example_emulator/example_emulator.py | 27 ++++++++++++++++++ mkspdr2000_emulator/mkspdr2000_emulator.py | 32 ++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 example_emulator/example_emulator.py create mode 100644 mkspdr2000_emulator/mkspdr2000_emulator.py diff --git a/example_emulator/example_emulator.py b/example_emulator/example_emulator.py new file mode 100644 index 00000000..5ad04871 --- /dev/null +++ b/example_emulator/example_emulator.py @@ -0,0 +1,27 @@ +import sys +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class TemplateEmulator: + def __init__(self): + self.state = 0 + + def process(self, data): + if data == 'r_state': + return 'state ' + str(int(self.state)) + + if data.startswith('w_state'): + self.state = data.split()[-1] + return 'w_state 1' + + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = TemplateEmulator() + TelnetEngine().start(dev, port_file) + + + diff --git a/mkspdr2000_emulator/mkspdr2000_emulator.py b/mkspdr2000_emulator/mkspdr2000_emulator.py new file mode 100644 index 00000000..18ffc2fd --- /dev/null +++ b/mkspdr2000_emulator/mkspdr2000_emulator.py @@ -0,0 +1,32 @@ +import sys, random +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class TemplateEmulator: + def __init__(self): + self.state = 0 + + def process(self, data): + if data == 'p': + return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) + + if data == 'f': + return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) + + if data == 'v': + return "Emulated device v1.0" + + if data == 'u': + return 'mBar' + + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = TemplateEmulator() + TelnetEngine().start(dev, port_file) + + + From c15d2bfc2b97fa25276b29d99452cc98d4bd307e Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 25 Jul 2016 09:24:21 +0100 Subject: [PATCH 0004/1466] Edit emulator to make it work better with setup --- example_emulator/example_emulator.py | 35 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/example_emulator/example_emulator.py b/example_emulator/example_emulator.py index 5ad04871..4fb92c1a 100644 --- a/example_emulator/example_emulator.py +++ b/example_emulator/example_emulator.py @@ -1,27 +1,26 @@ import sys + sys.path.append("../test_framework") from telnet_engine import TelnetEngine + class TemplateEmulator: - def __init__(self): - self.state = 0 - - def process(self, data): - if data == 'r_state': - return 'state ' + str(int(self.state)) - - if data.startswith('w_state'): - self.state = data.split()[-1] - return 'w_state 1' - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = TemplateEmulator() - TelnetEngine().start(dev, port_file) + def __init__(self): + self.state = 0 + def process(self, data): + if data == 'r_state': + return 'state ' + str(int(self.state)) + if data.startswith('w_state'): + self.state = data.split()[-1] + return 'w_state 1' + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = TemplateEmulator() + TelnetEngine().start(dev, port_file) From 8734454a3b238dfa34507ed38174c4132c9f88c0 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 25 Jul 2016 09:24:56 +0100 Subject: [PATCH 0005/1466] Add emulator for Tpg26x --- tpg26x/Tpg26xEmulator.py | 54 ++++++++++++++++++++++++++++++++++++ tpg26x/tpg26xTests.py | 60 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 tpg26x/Tpg26xEmulator.py create mode 100644 tpg26x/tpg26xTests.py diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py new file mode 100644 index 00000000..308a0f1a --- /dev/null +++ b/tpg26x/Tpg26xEmulator.py @@ -0,0 +1,54 @@ +import sys + +sys.path.append("../test_framework") + + +from telnet_engine import TelnetEngine + + +class Tpg26xEmulator: + def __init__(self): + self.pressure2 = 0.08 + self.pressure1 = 0.3 + self.error2 = 1 + self.error1 = 0 + self.params = [] + self.enquire = None + + def process(self, data): + """ + process the incoming data + :param data: data + :type data: str + :return: + """ + if data == chr(5): + return self._make_enquirey() + else: + self.enquire = None + self.params = None + tokens = data.split(',') + if len(tokens) > 0: + self.enquire = tokens[0] + if len(tokens) > 1: + self.params = tokens[1:] + + return "\x06" + + + def _make_enquirey(self): + if self.enquire == "PRX": + #x,sx.xxxxEsxx,y,sy.yyyyEsyy + return "{error1:1d},{pressure1:+011.4E},{error2:1d},{pressure2:011.4E}".format( + error1=self.error1, + pressure1=self.pressure1, + error2=self.error2, + pressure2=self.pressure2 + ) + elif self.enquire == "UNI": + return "1" + +if __name__ == "__main__": + port_file = __file__ + dev = tpg26xEmulator() + TelnetEngine().start(dev, port_file) diff --git a/tpg26x/tpg26xTests.py b/tpg26x/tpg26xTests.py new file mode 100644 index 00000000..a266b4b2 --- /dev/null +++ b/tpg26x/tpg26xTests.py @@ -0,0 +1,60 @@ +import os +import unittest +from time import sleep + +from Tpg26xEmulator import Tpg26xEmulator +from emulator import Emulator +from epics_engine import EpicsEngine +from ioc import Ioc + +class TestFunctionalityOfTPG26x(unittest.TestCase): + engine = None + emulator = None + + @classmethod + def setUpClass(cls): + TestFunctionalityOfTPG26x.emulator = Tpg26xEmulator() + + ioc_name = "TPG268X_01" + ioc_top = os.path.abspath(r"C:\Instrument\Apps\EPICS\ioc\master\TPG26x") + proc_serv_exe = r"C:\Instrument\Apps\EPICS\tools\master\cygwin_bin\procServ.exe" + cls.ioc = Ioc(ioc_name, ioc_top, proc_serv_exe=proc_serv_exe) + cls.ioc.set_host("localhost") + cls.ioc.to_be_uncommented = ["drvAsynIPPortConfigure", "dbLoadRecords(\"db/in_out.db\""] + + emu_name = "TPG268xEmulator" + emu_top = os.path.dirname(os.path.abspath(__file__)) + emu_file = "Tpg26xEmulator.py" + cls.emu = Emulator(emu_name, emu_top, emu_file, proc_serv_exe=proc_serv_exe) + + cls.engine = EpicsEngine(cls.ioc, cls.emu, make=False) + cls.engine.start_emulators() + cls.ioc.set_tcp_port(cls.emu.get_port()) + #cls.engine.start_iocs() + + cls.pressure_pv = "NDW1407:HGV27692:" + ioc_name + "1:PRESSURE" + cls.value_rbv_pv = ioc_name + ":VALUE_RBV" + cls.state_safe_pv = ioc_name + ":STATE_SAFE" + cls.state_scan = 0.5 + + @classmethod + def tearDownClass(cls): + cls.engine.stop() + cls.engine.cleanup() + + def setUp(self): + #print self.engine.caget(self.pressure_pv) + #self.emu.open_connection() + #self.emu.write("emulator_command:reset") + pass + + def tearDown(self): + self.emu.close_connection() + + def test_GIVEN_no_errors_and_set_pressures_WHEN_measure_THEN_no_error(self): + sleep(100) + assert True + + +if __name__ == '__main__': + unittest.main() From 304f66acde3286e5c8f9254d4330f8dc04d798c9 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 26 Jul 2016 09:53:24 +0100 Subject: [PATCH 0006/1466] Refactor emulator to make operation clearer --- .gitignore | 2 + tpg26x/Tpg26xEmulator.py | 92 +++++++++++++++++++++++++++++++--------- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 72364f99..c8b5255b 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,5 @@ ENV/ # Rope project settings .ropeproject +*.pid +*.port diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py index 308a0f1a..b6d312e1 100644 --- a/tpg26x/Tpg26xEmulator.py +++ b/tpg26x/Tpg26xEmulator.py @@ -1,42 +1,81 @@ import sys +from telnet_engine import TelnetEngine, ENQ_SIGNAL, ACK_SIGNAL, NAK_SIGNAL -sys.path.append("../test_framework") - +"""Prefix for command sent to emulator to change the state of the emulator""" +EMULATOR_COMMAND_PREFIX = "emulator:" -from telnet_engine import TelnetEngine +sys.path.append("../test_framework") class Tpg26xEmulator: + """ + Emulator for the TPG26x pressure reading device + """ + def __init__(self): + """ + Constructor + :return: + """ self.pressure2 = 0.08 self.pressure1 = 0.3 self.error2 = 1 self.error1 = 0 self.params = [] self.enquire = None + self.units = 1 def process(self, data): """ - process the incoming data - :param data: data + process the incoming data and return the answer + :param data: data sent :type data: str - :return: + :return: response """ - if data == chr(5): - return self._make_enquirey() - else: - self.enquire = None - self.params = None - tokens = data.split(',') - if len(tokens) > 0: - self.enquire = tokens[0] - if len(tokens) > 1: - self.params = tokens[1:] + try: + if data.startswith(EMULATOR_COMMAND_PREFIX): + return self._do_emulator_command(data[len(EMULATOR_COMMAND_PREFIX):]) + elif data == chr(5): + return self._make_enquiry() + else: + self.enquire = None + self.params = None + tokens = data.split(',') + if len(tokens) > 0: + self.enquire = tokens[0] + return ENQ_SIGNAL + if len(tokens) > 1: + return self._set_value(tokens[1:]) + return None - return "\x06" + except (ValueError, TypeError) as ex: + print "Exception thrown during emulation: {0}".format(ex) + def _do_emulator_command(self, command): + """ + Perform the emulator state change based on the command + :param command: command that was sent (without the prfix) + :return: response + """ + parameters = command.split() + if parameters[0] == "set:pressure1": + self.pressure1 = float(parameters[1]) + elif parameters[0] == "set:pressure2": + self.pressure2 = float(parameters[1]) + elif parameters[0] == "set:error1": + self.error1 = int(parameters[1]) + elif parameters[0] == "set:error2": + self.error2 = int(parameters[1]) + elif parameters[0] == "set:units": + self.units = int(parameters[1]) + return None - def _make_enquirey(self): + def _make_enquiry(self): + """ + Most commands sent are in two parts a command which either sets or asks for a value and then + an enquirey that gets the response. This function deals with the response to an enquiry + :return: response + """ if self.enquire == "PRX": #x,sx.xxxxEsxx,y,sy.yyyyEsyy return "{error1:1d},{pressure1:+011.4E},{error2:1d},{pressure2:011.4E}".format( @@ -46,9 +85,22 @@ def _make_enquirey(self): pressure2=self.pressure2 ) elif self.enquire == "UNI": - return "1" + return "{0:1d}".format(self.units) + + def _set_value(self, values): + """ + Set a value based on an instruction from the IOC + :param values: value to set + :return: response + """ + if self.enquire == "UNI": + self.units = int(values[0]) + return ACK_SIGNAL + else: + print "Error trying to set {0} which the simulator can not".format(self.enquire) + return NAK_SIGNAL if __name__ == "__main__": port_file = __file__ - dev = tpg26xEmulator() + dev = Tpg26xEmulator() TelnetEngine().start(dev, port_file) From 2f9ca7cd185d3c28bdb92d815bb4e40cc2afbace Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 26 Jul 2016 10:15:14 +0100 Subject: [PATCH 0007/1466] Fix refactoring --- tpg26x/Tpg26xEmulator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py index b6d312e1..c4b05a00 100644 --- a/tpg26x/Tpg26xEmulator.py +++ b/tpg26x/Tpg26xEmulator.py @@ -41,12 +41,14 @@ def process(self, data): self.enquire = None self.params = None tokens = data.split(',') + if len(tokens) == 0: + return None if len(tokens) > 0: self.enquire = tokens[0] - return ENQ_SIGNAL if len(tokens) > 1: return self._set_value(tokens[1:]) - return None + else: + return ENQ_SIGNAL except (ValueError, TypeError) as ex: print "Exception thrown during emulation: {0}".format(ex) From 158906ac9640935e790fb689f58ddd3d4f621e00 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 28 Jul 2016 14:33:49 +0100 Subject: [PATCH 0008/1466] Create LICENSE --- LICENSE | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f735bee0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,203 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, +NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. From fd5c8910b8943f5683015c1ab9c27440d34ca24e Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 28 Jul 2016 17:18:12 +0100 Subject: [PATCH 0009/1466] Fixed typo in telnet engine From 0f11cd7a568c68c69476dff9bf3f118a6a856a24 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 28 Jul 2016 17:18:58 +0100 Subject: [PATCH 0010/1466] Ticket 1404: embryo emulator for lakeshore 336 --- .../lakeshore336_emulator.py | 47 +++++++++++++++++++ .../lakeshore336_emulator.py.port | 1 + 2 files changed, 48 insertions(+) create mode 100644 lakeshore336_emulator/lakeshore336_emulator.py create mode 100644 lakeshore336_emulator/lakeshore336_emulator.py.port diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py new file mode 100644 index 00000000..51930aaf --- /dev/null +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -0,0 +1,47 @@ +import sys +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class LakeshoreInput: + def __init__(self): + self.name = "Default Name" + +class Lakeshore336Emulator: + def __init__(self): + self.id = "EmulatedDevice" + + self.inputs = dict() + self.inputs["A"] = LakeshoreInput() + self.inputs["B"] = LakeshoreInput() + self.inputs["C"] = LakeshoreInput() + self.inputs["D"] = LakeshoreInput() + + def process(self, data): + if data == "*IDN?": + return self._reply("LSCI," + self.id) + + if data.startswith("INNAME?"): + index = data[-1] + return self._reply(self.inputs[index].name) + + if data.startswith("INNAME"): + tokens = data[len("INNAME "):].split(",") + index = tokens[0] + new_name = tokens[1].strip("\"") + self.inputs[index].name = new_name + return None + + return None + + def _reply(self, msg): + return msg + "\r\n" + + +if __name__ == "__main__": + port_file = __file__ + dev = Lakeshore336Emulator() + TelnetEngine().start(dev, port_file) + + + diff --git a/lakeshore336_emulator/lakeshore336_emulator.py.port b/lakeshore336_emulator/lakeshore336_emulator.py.port new file mode 100644 index 00000000..5eb711a0 --- /dev/null +++ b/lakeshore336_emulator/lakeshore336_emulator.py.port @@ -0,0 +1 @@ +52202 \ No newline at end of file From 8481a7a71849b2cc21b6cc8232603ee0039b5c11 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 28 Jul 2016 17:19:45 +0100 Subject: [PATCH 0011/1466] Ticket 1404: the port file was added by mistake --- lakeshore336_emulator/lakeshore336_emulator.py.port | 1 - 1 file changed, 1 deletion(-) delete mode 100644 lakeshore336_emulator/lakeshore336_emulator.py.port diff --git a/lakeshore336_emulator/lakeshore336_emulator.py.port b/lakeshore336_emulator/lakeshore336_emulator.py.port deleted file mode 100644 index 5eb711a0..00000000 --- a/lakeshore336_emulator/lakeshore336_emulator.py.port +++ /dev/null @@ -1 +0,0 @@ -52202 \ No newline at end of file From 409afaa4c179e28f6a07e17c5b57fe1efb9d5149 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Fri, 29 Jul 2016 10:38:35 +0100 Subject: [PATCH 0012/1466] Ticket 1404: emulator handles queries for input paramters --- .../lakeshore336_emulator.py | 117 ++++++++++++++++-- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py index 51930aaf..7bd19447 100644 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -1,4 +1,4 @@ -import sys +import sys, random sys.path.append("../test_framework") from telnet_engine import TelnetEngine @@ -6,6 +6,40 @@ class LakeshoreInput: def __init__(self): self.name = "Default Name" + self._temperature = 0.0 + self.raw_voltage = random.uniform(0, 1000) + self.has_high_alarm = False + self.has_low_alarm = False + self.alarm_enabled = True + self.alarm_high_setpoint = 1000.0 + self.alarm_low_setpoint = 10.0 + self.alarm_deadband = 0.5 + self.alarm_latching = False + self.alarm_audible = True + self.alarm_visible = True + self.reading_status = 0 + self.curve_number = 15 + self.sensor_type = 3 + self.autorange_on = True + self.range = 5 + self.compensation_on = True + self.units = 1 + + + def get_temperature(self): + return_value = self._temperature; + self._temperature += 0.1 + return return_value + + +class CalibrationCurve: + def __init__(self): + self.name = "Emulator curve " # Must be 15 chars + self.serial = "0123456789" # Must be 10 chars + self.format = 1 + self.limit = 1000.0 + self.coeff = 2 + class Lakeshore336Emulator: def __init__(self): @@ -17,26 +51,89 @@ def __init__(self): self.inputs["C"] = LakeshoreInput() self.inputs["D"] = LakeshoreInput() + self.curves = dict() + self.curves["15"] = CalibrationCurve() + def process(self, data): + msg = self._reply(data) + if msg is not None: + return msg + "\r\n" + + return None + + def _reply(self, data): if data == "*IDN?": - return self._reply("LSCI," + self.id) + return "LSCI,%s" % (self.id) if data.startswith("INNAME?"): - index = data[-1] - return self._reply(self.inputs[index].name) + return self._get_target_input(data).name if data.startswith("INNAME"): - tokens = data[len("INNAME "):].split(",") - index = tokens[0] - new_name = tokens[1].strip("\"") - self.inputs[index].name = new_name + self._set_input_name(data) return None + if data.startswith("KRDG?"): + return str(self._get_target_input(data).get_temperature()) + + if data.startswith("SRDG?"): + return str(self._get_target_input(data).raw_voltage) + + if data.startswith("ALARMST?"): + return self._get_alarm_status(data) + + if data.startswith("ALARM?"): + return self._get_alarm_settings(data) + + if data.startswith("RDGST?"): + return str(self._get_target_input(data).reading_status) + + if data.startswith("INCRV?"): + index = data[-1] + return str(self.inputs[index].curve_number) + + if data.startswith("CRVHDR?"): + return self._get_curve_header(data) + + if data.startswith("INTYPE?"): + return self._get_input_type(data) + return None - def _reply(self, msg): - return msg + "\r\n" + def _bool_to_int(self, bool): + return 1 if bool else 0 + + def _get_target_input(self, data): + index = data[-1] + return self.inputs[index] + + def _get_target_curve(self, data): + index = data.split()[-1] + return self.curves[index] + + def _set_input_name(self, data): + tokens = data[len("INNAME "):].split(",") + index = tokens[0] + new_name = tokens[1].strip("\"") + self.inputs[index].name = new_name + + def _get_alarm_status(self, data): + input = self._get_target_input(data) + return "%d,%d" % (self._bool_to_int(input.has_high_alarm), self._bool_to_int(input.has_low_alarm)) + + def _get_alarm_settings(self, data): + input = self._get_target_input(data) + return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ + input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ + input.alarm_visible) + + def _get_curve_header(self, data): + curve = self._get_target_curve(data) + return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) + def _get_input_type(self, data): + input = self._get_target_input(data) + return "%d,%d,%d,%d,%d" % (input.sensor_type, self._bool_to_int(input.autorange_on), input.range, \ + self._bool_to_int(input.compensation_on), input.units) if __name__ == "__main__": port_file = __file__ From 92b5fce4db755f4d5c209ed631abb7837ea62811 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Fri, 29 Jul 2016 12:08:33 +0100 Subject: [PATCH 0013/1466] Ticket 1404: refactored emulator to use command pattern --- .../lakeshore336_emulator.py | 230 +++++++++++++----- 1 file changed, 165 insertions(+), 65 deletions(-) diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py index 7bd19447..5d9912a9 100644 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -32,6 +32,13 @@ def get_temperature(self): return return_value +class LakeshoreOutput: + def __init__(self): + self.temp_setpoint = 100.0 + self.ramp_on = True + self.ramp_rate = 2.0 + + class CalibrationCurve: def __init__(self): self.name = "Emulator curve " # Must be 15 chars @@ -40,20 +47,144 @@ def __init__(self): self.limit = 1000.0 self.coeff = 2 +class GetIdCommand: + def __init__(self, emulator): + self.id = "*IDN?" + self._emu = emulator + + def execute_and_reply(self, data): + return "LSCI,%s" % (self._emu.id) + +class GetInputNameCommand: + def __init__(self, emulator): + self.id = "INNAME?" + self._emu = emulator + + def execute_and_reply(self, data): + return self._emu.get_target_input(data).name + +class SetInputNameCommand: + def __init__(self, emulator): + self.id = "INNAME" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + new_name = tokens[1].strip("\"") + self._emu.inputs[index].name = new_name + return None + +class GetInputTempCommand: + def __init__(self, emulator): + self.id = "KRDG?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_input(data).get_temperature()) + +class GetInputVoltageCommand: + def __init__(self, emulator): + self.id = "SRDG?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_input(data).raw_voltage) + +class GetAlarmStatusCommand: + def __init__(self, emulator): + self.id = "ALARMST?" + self._emu = emulator + + def execute_and_reply(self, data): + input = self._emu.get_target_input(data) + return "%d,%d" % (self._emu.bool_to_int(input.has_high_alarm), self._emu.bool_to_int(input.has_low_alarm)) + +class GetAlarmSettingsCommand: + def __init__(self, emulator): + self.id = "ALARM?" + self._emu = emulator + + def execute_and_reply(self, data): + input = self._emu.get_target_input(data) + return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ + input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ + input.alarm_visible) + +class GetReadingStatusCommand: + def __init__(self, emulator): + self.id = "RDGST?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_input(data).reading_status) + +class GetInputCurveNumberCommand: + def __init__(self, emulator): + self.id = "INCRV?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_input(data).curve_number) + +class GetCurveHeaderCommand: + def __init__(self, emulator): + self.id = "CRVHDR?" + self._emu = emulator + + def execute_and_reply(self, data): + curve = self._emu.get_target_curve(data) + return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) + +class GetInputTypeCommand: + def __init__(self, emulator): + self.id = "INTYPE?" + self._emu = emulator + + def execute_and_reply(self, data): + input = self._emu.get_target_input(data) + return "%d,%d,%d,%d,%d" % (input.sensor_type, self._emu.bool_to_int(input.autorange_on), input.range, \ + self._emu.bool_to_int(input.compensation_on), input.units) + class Lakeshore336Emulator: - def __init__(self): + def __init__(self):# self.id = "EmulatedDevice" + self._populate_inputs() + self._populate_curves() + self._populate_outputs() + self._populate_commands() + def _populate_inputs(self): self.inputs = dict() self.inputs["A"] = LakeshoreInput() self.inputs["B"] = LakeshoreInput() self.inputs["C"] = LakeshoreInput() self.inputs["D"] = LakeshoreInput() + def _populate_outputs(self): + self.outputs = dict() + self.outputs["1"] = LakeshoreOutput() + self.outputs["2"] = LakeshoreOutput() + + def _populate_curves(self): self.curves = dict() self.curves["15"] = CalibrationCurve() + def _populate_commands(self): + self.commands = list() + self.commands.append(GetIdCommand(self)) + self.commands.append(GetInputNameCommand(self)) + self.commands.append(SetInputNameCommand(self)) + self.commands.append(GetInputTempCommand(self)) + self.commands.append(GetInputVoltageCommand(self)) + self.commands.append(GetAlarmStatusCommand(self)) + self.commands.append(GetAlarmSettingsCommand(self)) + self.commands.append(GetReadingStatusCommand(self)) + self.commands.append(GetInputCurveNumberCommand(self)) + self.commands.append(GetCurveHeaderCommand(self)) + self.commands.append(GetInputTypeCommand(self)) + def process(self, data): msg = self._reply(data) if msg is not None: @@ -62,78 +193,47 @@ def process(self, data): return None def _reply(self, data): - if data == "*IDN?": - return "LSCI,%s" % (self.id) - - if data.startswith("INNAME?"): - return self._get_target_input(data).name - - if data.startswith("INNAME"): - self._set_input_name(data) - return None - - if data.startswith("KRDG?"): - return str(self._get_target_input(data).get_temperature()) - - if data.startswith("SRDG?"): - return str(self._get_target_input(data).raw_voltage) - - if data.startswith("ALARMST?"): - return self._get_alarm_status(data) - - if data.startswith("ALARM?"): - return self._get_alarm_settings(data) - - if data.startswith("RDGST?"): - return str(self._get_target_input(data).reading_status) - - if data.startswith("INCRV?"): - index = data[-1] - return str(self.inputs[index].curve_number) - - if data.startswith("CRVHDR?"): - return self._get_curve_header(data) - - if data.startswith("INTYPE?"): - return self._get_input_type(data) - - return None - - def _bool_to_int(self, bool): + command_id = data.split()[0] + (command,) = [cmd for cmd in self.commands if cmd.id == command_id] + return command.execute_and_reply(data) + +# def _reply(self, data): +# if data.startswith("SETP?"): +# return str(self._get_target_output(data).temp_setpoint) +# +# if data.startswith("SETP "): +# self._set_setpoint(data) +# return None +# +# if data.startswith("RAMP?") +# +# return None +# + def bool_to_int(self, bool): return 1 if bool else 0 - def _get_target_input(self, data): + def get_target_input(self, data): index = data[-1] return self.inputs[index] - def _get_target_curve(self, data): + def get_target_curve(self, data): index = data.split()[-1] return self.curves[index] +# +# def _get_target_output(self, data): +# index = data[-1] +# return self.outputs[index] +# + def get_set_tokens(self, data, command): + return data[len(command + " "):].split(",") +# +# +# def _set_setpoint(self, data): +# tokens = self._get_set_tokens(data, "SETP") +# index = tokens[0] +# new_temp = float(tokens[1]) +# self.outputs[index].temp_setpoint = new_temp - def _set_input_name(self, data): - tokens = data[len("INNAME "):].split(",") - index = tokens[0] - new_name = tokens[1].strip("\"") - self.inputs[index].name = new_name - - def _get_alarm_status(self, data): - input = self._get_target_input(data) - return "%d,%d" % (self._bool_to_int(input.has_high_alarm), self._bool_to_int(input.has_low_alarm)) - - def _get_alarm_settings(self, data): - input = self._get_target_input(data) - return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ - input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ - input.alarm_visible) - - def _get_curve_header(self, data): - curve = self._get_target_curve(data) - return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) - - def _get_input_type(self, data): - input = self._get_target_input(data) - return "%d,%d,%d,%d,%d" % (input.sensor_type, self._bool_to_int(input.autorange_on), input.range, \ - self._bool_to_int(input.compensation_on), input.units) if __name__ == "__main__": port_file = __file__ From 3bd5f0112be28575af703ff2872283a552e99f14 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Fri, 29 Jul 2016 14:35:32 +0100 Subject: [PATCH 0014/1466] Ticket 1404: added support for output queries and settings --- .../lakeshore336_emulator.py | 215 +++++++++++++++--- 1 file changed, 188 insertions(+), 27 deletions(-) diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py index 5d9912a9..f81791e4 100644 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -37,6 +37,14 @@ def __init__(self): self.temp_setpoint = 100.0 self.ramp_on = True self.ramp_rate = 2.0 + self.heater_range = 2 + self.manual_output = 20.0 + self.pid = (1.0, 2.0, 3.0) + self.output_mode = 1 + self.control_input = 1 + self.powerup_enabled = True + self.heater_output = 21.0 + self.heater_status = 2 class CalibrationCurve: @@ -47,6 +55,10 @@ def __init__(self): self.limit = 1000.0 self.coeff = 2 +class NullCommand: + def execute_and_reply(self, data): + return None + class GetIdCommand: def __init__(self, emulator): self.id = "*IDN?" @@ -146,6 +158,152 @@ def execute_and_reply(self, data): return "%d,%d,%d,%d,%d" % (input.sensor_type, self._emu.bool_to_int(input.autorange_on), input.range, \ self._emu.bool_to_int(input.compensation_on), input.units) +class GetSetpointCommand: + def __init__(self, emulator): + self.id = "SETP?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_output(data).temp_setpoint) + +class SetSetpointCommand: + def __init__(self, emulator): + self.id = "SETP" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + new_temp = float(tokens[1]) + self._emu.outputs[index].temp_setpoint = new_temp + return None + +class GetOutputRampCommand: + def __init__(self, emulator): + self.id = "RAMP?" + self._emu = emulator + + def execute_and_reply(self, data): + output = self._emu.get_target_output(data) + return "%d,%f" % (output.ramp_on, output.ramp_rate) + +class SetOutputRampCommand: + def __init__(self, emulator): + self.id = "RAMP" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + ramp_on = self._emu.int_to_bool(int(tokens[1])) + ramp_rate = float(tokens[2]) + self._emu.outputs[index].ramp_on = ramp_on + self._emu.outputs[index].ramp_rate = ramp_rate + return None + +class GetHeaterRangeCommand: + def __init__(self, emulator): + self.id = "RANGE?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_output(data).heater_range) + +class SetHeaterRangeCommand: + def __init__(self, emulator): + self.id = "RANGE" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + range = int(tokens[1]) + self._emu.outputs[index].heater_range = range + return None + +class GetManualOutputCommand: + def __init__(self, emulator): + self.id = "MOUT?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_output(data).manual_output) + +class SetManualOutputCommand: + def __init__(self, emulator): + self.id = "MOUT" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + output = float(tokens[1]) + self._emu.outputs[index].manual_output = output + return None + +class GetPIDCommand: + def __init__(self, emulator): + self.id = "PID?" + self._emu = emulator + + def execute_and_reply(self, data): + return "%f,%f,%f" % self._emu.get_target_output(data).pid + +class SetPIDCommand: + def __init__(self, emulator): + self.id = "PID" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + self._emu.outputs[index].pid = tuple([float(t) for t in tokens[1:]]) + return None + +class GetOutputModeCommand: + def __init__(self, emulator): + self.id = "OUTMODE?" + self._emu = emulator + + def execute_and_reply(self, data): + output = self._emu.get_target_output(data) + return "%d,%d,%d" % (output.output_mode, output.control_input, self._emu.bool_to_int(output.powerup_enabled)) + +class SetOutputModeCommand: + def __init__(self, emulator): + self.id = "OUTMODE" + self._emu = emulator + + def execute_and_reply(self, data): + tokens = self._emu.get_set_tokens(data, self.id) + index = tokens[0] + output = self._emu.get_target_output(index) + (mode, ctr_input, powerup) = [int(t) for t in tokens[1:]] + output.output_mode = mode + output.control_input = ctr_input + output.powerup_enabled = self._emu.int_to_bool(powerup) + return None + +class GetHeaterOutputCommand: + def __init__(self, emulator): + self.id = "HTR?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_output(data).heater_output) + +class GetHeaterStatusCommand: + def __init__(self, emulator): + self.id = "HTRST?" + self._emu = emulator + + def execute_and_reply(self, data): + return str(self._emu.get_target_output(data).heater_status) + +class StartAutotuneCommand(NullCommand): + def __init__(self, emulator): + self.id = "ATUNE" + class Lakeshore336Emulator: def __init__(self):# @@ -184,6 +342,21 @@ def _populate_commands(self): self.commands.append(GetInputCurveNumberCommand(self)) self.commands.append(GetCurveHeaderCommand(self)) self.commands.append(GetInputTypeCommand(self)) + self.commands.append(GetSetpointCommand(self)) + self.commands.append(SetSetpointCommand(self)) + self.commands.append(GetOutputRampCommand(self)) + self.commands.append(SetOutputRampCommand(self)) + self.commands.append(GetHeaterRangeCommand(self)) + self.commands.append(SetHeaterRangeCommand(self)) + self.commands.append(GetManualOutputCommand(self)) + self.commands.append(SetManualOutputCommand(self)) + self.commands.append(GetPIDCommand(self)) + self.commands.append(SetPIDCommand(self)) + self.commands.append(GetOutputModeCommand(self)) + self.commands.append(SetOutputModeCommand(self)) + self.commands.append(GetHeaterOutputCommand(self)) + self.commands.append(GetHeaterStatusCommand(self)) + self.commands.append(StartAutotuneCommand(self)) def process(self, data): msg = self._reply(data) @@ -194,24 +367,19 @@ def process(self, data): def _reply(self, data): command_id = data.split()[0] - (command,) = [cmd for cmd in self.commands if cmd.id == command_id] - return command.execute_and_reply(data) - -# def _reply(self, data): -# if data.startswith("SETP?"): -# return str(self._get_target_output(data).temp_setpoint) -# -# if data.startswith("SETP "): -# self._set_setpoint(data) -# return None -# -# if data.startswith("RAMP?") -# -# return None -# + try: + (command,) = [cmd for cmd in self.commands if cmd.id == command_id] + return command.execute_and_reply(data) + except ValueError: + print "***\n*** Command \"%s\" not supported! ***\n***" % (data) + return None + def bool_to_int(self, bool): return 1 if bool else 0 + def int_to_bool(self, value): + return value == 1 + def get_target_input(self, data): index = data[-1] return self.inputs[index] @@ -219,20 +387,13 @@ def get_target_input(self, data): def get_target_curve(self, data): index = data.split()[-1] return self.curves[index] -# -# def _get_target_output(self, data): -# index = data[-1] -# return self.outputs[index] -# + + def get_target_output(self, data): + index = data[-1] + return self.outputs[index] + def get_set_tokens(self, data, command): return data[len(command + " "):].split(",") -# -# -# def _set_setpoint(self, data): -# tokens = self._get_set_tokens(data, "SETP") -# index = tokens[0] -# new_temp = float(tokens[1]) -# self.outputs[index].temp_setpoint = new_temp if __name__ == "__main__": From 8c8f0ff33dfc1b70b6faa226c81a504c27faa06d Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Fri, 29 Jul 2016 14:37:30 +0100 Subject: [PATCH 0015/1466] Emulators should ignore port files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 72364f99..16dec71e 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,6 @@ ENV/ # Rope project settings .ropeproject + +# Port files +.port From 211aff0acb4eae3c9a9b5e0abafaecd1803fa71f Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Fri, 29 Jul 2016 14:38:38 +0100 Subject: [PATCH 0016/1466] Fixed previous commit --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 16dec71e..020002af 100644 --- a/.gitignore +++ b/.gitignore @@ -89,4 +89,4 @@ ENV/ .ropeproject # Port files -.port +*.port From 05be3f0f72e50540384bd8d8b6af961595ee42cb Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 3 Aug 2016 09:55:01 +0100 Subject: [PATCH 0017/1466] Move tpg test to different branch because it doesn't work --- tpg26x/tpg26xTests.py | 60 ------------------------------------------- 1 file changed, 60 deletions(-) delete mode 100644 tpg26x/tpg26xTests.py diff --git a/tpg26x/tpg26xTests.py b/tpg26x/tpg26xTests.py deleted file mode 100644 index a266b4b2..00000000 --- a/tpg26x/tpg26xTests.py +++ /dev/null @@ -1,60 +0,0 @@ -import os -import unittest -from time import sleep - -from Tpg26xEmulator import Tpg26xEmulator -from emulator import Emulator -from epics_engine import EpicsEngine -from ioc import Ioc - -class TestFunctionalityOfTPG26x(unittest.TestCase): - engine = None - emulator = None - - @classmethod - def setUpClass(cls): - TestFunctionalityOfTPG26x.emulator = Tpg26xEmulator() - - ioc_name = "TPG268X_01" - ioc_top = os.path.abspath(r"C:\Instrument\Apps\EPICS\ioc\master\TPG26x") - proc_serv_exe = r"C:\Instrument\Apps\EPICS\tools\master\cygwin_bin\procServ.exe" - cls.ioc = Ioc(ioc_name, ioc_top, proc_serv_exe=proc_serv_exe) - cls.ioc.set_host("localhost") - cls.ioc.to_be_uncommented = ["drvAsynIPPortConfigure", "dbLoadRecords(\"db/in_out.db\""] - - emu_name = "TPG268xEmulator" - emu_top = os.path.dirname(os.path.abspath(__file__)) - emu_file = "Tpg26xEmulator.py" - cls.emu = Emulator(emu_name, emu_top, emu_file, proc_serv_exe=proc_serv_exe) - - cls.engine = EpicsEngine(cls.ioc, cls.emu, make=False) - cls.engine.start_emulators() - cls.ioc.set_tcp_port(cls.emu.get_port()) - #cls.engine.start_iocs() - - cls.pressure_pv = "NDW1407:HGV27692:" + ioc_name + "1:PRESSURE" - cls.value_rbv_pv = ioc_name + ":VALUE_RBV" - cls.state_safe_pv = ioc_name + ":STATE_SAFE" - cls.state_scan = 0.5 - - @classmethod - def tearDownClass(cls): - cls.engine.stop() - cls.engine.cleanup() - - def setUp(self): - #print self.engine.caget(self.pressure_pv) - #self.emu.open_connection() - #self.emu.write("emulator_command:reset") - pass - - def tearDown(self): - self.emu.close_connection() - - def test_GIVEN_no_errors_and_set_pressures_WHEN_measure_THEN_no_error(self): - sleep(100) - assert True - - -if __name__ == '__main__': - unittest.main() From a6200e72481cc07883e03049e7a550647c8d6f4b Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 3 Aug 2016 16:11:43 +0100 Subject: [PATCH 0018/1466] Use constants from telnet for ACK. Make test_framework into a module --- tpg26x/Tpg26xEmulator.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py index c4b05a00..d6e07ff2 100644 --- a/tpg26x/Tpg26xEmulator.py +++ b/tpg26x/Tpg26xEmulator.py @@ -1,11 +1,11 @@ import sys -from telnet_engine import TelnetEngine, ENQ_SIGNAL, ACK_SIGNAL, NAK_SIGNAL +sys.path.append("..") + +from test_framework.telnet_engine import ENQ_SIGNAL, ACK_SIGNAL, NAK_SIGNAL, TelnetEngine """Prefix for command sent to emulator to change the state of the emulator""" EMULATOR_COMMAND_PREFIX = "emulator:" -sys.path.append("../test_framework") - class Tpg26xEmulator: """ @@ -35,7 +35,7 @@ def process(self, data): try: if data.startswith(EMULATOR_COMMAND_PREFIX): return self._do_emulator_command(data[len(EMULATOR_COMMAND_PREFIX):]) - elif data == chr(5): + elif data == ENQ_SIGNAL: return self._make_enquiry() else: self.enquire = None @@ -48,10 +48,11 @@ def process(self, data): if len(tokens) > 1: return self._set_value(tokens[1:]) else: - return ENQ_SIGNAL + return ACK_SIGNAL except (ValueError, TypeError) as ex: print "Exception thrown during emulation: {0}".format(ex) + return NAK_SIGNAL def _do_emulator_command(self, command): """ From 6ea69fa9fc9c8b80c0cee56d67897a2a8d4f7f52 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 3 Aug 2016 16:59:12 +0100 Subject: [PATCH 0019/1466] Add cryo valve emulator --- .../iris_cryo_valve_emulator.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 iris_cryo_valve_emulator/iris_cryo_valve_emulator.py diff --git a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py new file mode 100644 index 00000000..f31b8a35 --- /dev/null +++ b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py @@ -0,0 +1,35 @@ +import sys +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class CryValveEmulator: + + CLOSED = "CLOSED" + OPEN = "OPEN" + + def __init__(self): + self.state = CryValveEmulator.CLOSED + + def process(self, data): + if data == 'CLOSE': + self.state = CryValveEmulator.CLOSED + return "" + + if data == 'OPEN': + self.state = CryValveEmulator.OPEN + return "" + + if data == '?': + return "SOLENOID " + self.state + + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = CryValveEmulator() + TelnetEngine().start(dev, port_file) + + + From d2a07a36bd7272af359f3e3b5700ac2c1cb7f43a Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 3 Aug 2016 17:11:34 +0100 Subject: [PATCH 0020/1466] Update response terminator --- iris_cryo_valve_emulator/iris_cryo_valve_emulator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py index f31b8a35..27e73580 100644 --- a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py +++ b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py @@ -7,6 +7,7 @@ class CryValveEmulator: CLOSED = "CLOSED" OPEN = "OPEN" + TERMINATOR = "\r" def __init__(self): self.state = CryValveEmulator.CLOSED @@ -14,14 +15,14 @@ def __init__(self): def process(self, data): if data == 'CLOSE': self.state = CryValveEmulator.CLOSED - return "" + return CryValveEmulator.TERMINATOR if data == 'OPEN': self.state = CryValveEmulator.OPEN - return "" + return CryValveEmulator.TERMINATOR if data == '?': - return "SOLENOID " + self.state + return "SOLENOID " + self.state + CryValveEmulator.TERMINATOR return None From 1126b78917ca7289a73b1c9ef706bab9cbef74bd Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 4 Aug 2016 16:07:36 +0100 Subject: [PATCH 0021/1466] Port files should be ignored --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 72364f99..020002af 100644 --- a/.gitignore +++ b/.gitignore @@ -87,3 +87,6 @@ ENV/ # Rope project settings .ropeproject + +# Port files +*.port From f3afeefebe222739b472eaea555b6707b61f067c Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Mon, 8 Aug 2016 10:41:15 +0100 Subject: [PATCH 0022/1466] Ticket 1404: emulator commands now have base class --- .../lakeshore336_emulator.py | 143 ++++++++---------- 1 file changed, 61 insertions(+), 82 deletions(-) diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py index f81791e4..1266ef3e 100644 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -3,7 +3,7 @@ from telnet_engine import TelnetEngine -class LakeshoreInput: +class LakeshoreInput(object): def __init__(self): self.name = "Default Name" self._temperature = 0.0 @@ -32,7 +32,7 @@ def get_temperature(self): return return_value -class LakeshoreOutput: +class LakeshoreOutput(object): def __init__(self): self.temp_setpoint = 100.0 self.ramp_on = True @@ -47,7 +47,7 @@ def __init__(self): self.heater_status = 2 -class CalibrationCurve: +class CalibrationCurve(object): def __init__(self): self.name = "Emulator curve " # Must be 15 chars self.serial = "0123456789" # Must be 10 chars @@ -55,30 +55,31 @@ def __init__(self): self.limit = 1000.0 self.coeff = 2 -class NullCommand: +class Command(object): + def __init__(self, emulator, id): + self._emu = emulator + self.id = id + def execute_and_reply(self, data): return None -class GetIdCommand: +class GetIdCommand(Command): def __init__(self, emulator): - self.id = "*IDN?" - self._emu = emulator + super(GetIdCommand, self).__init__(emulator, "*IDN?") def execute_and_reply(self, data): return "LSCI,%s" % (self._emu.id) -class GetInputNameCommand: +class GetInputNameCommand(Command): def __init__(self, emulator): - self.id = "INNAME?" - self._emu = emulator + super(GetInputNameCommand, self).__init__(emulator, "INNAME?") def execute_and_reply(self, data): return self._emu.get_target_input(data).name -class SetInputNameCommand: +class SetInputNameCommand(Command): def __init__(self, emulator): - self.id = "INNAME" - self._emu = emulator + super(SetInputNameCommand, self).__init__(emulator, "INNAME") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -87,35 +88,31 @@ def execute_and_reply(self, data): self._emu.inputs[index].name = new_name return None -class GetInputTempCommand: +class GetInputTempCommand(Command): def __init__(self, emulator): - self.id = "KRDG?" - self._emu = emulator + super(GetInputTempCommand, self).__init__(emulator, "KRDG?") def execute_and_reply(self, data): return str(self._emu.get_target_input(data).get_temperature()) -class GetInputVoltageCommand: +class GetInputVoltageCommand(Command): def __init__(self, emulator): - self.id = "SRDG?" - self._emu = emulator + super(GetInputVoltageCommand, self).__init__(emulator, "SRDG?") def execute_and_reply(self, data): return str(self._emu.get_target_input(data).raw_voltage) -class GetAlarmStatusCommand: +class GetAlarmStatusCommand(Command): def __init__(self, emulator): - self.id = "ALARMST?" - self._emu = emulator + super(GetAlarmStatusCommand, self).__init__(emulator, "ALARMST?") def execute_and_reply(self, data): input = self._emu.get_target_input(data) return "%d,%d" % (self._emu.bool_to_int(input.has_high_alarm), self._emu.bool_to_int(input.has_low_alarm)) -class GetAlarmSettingsCommand: +class GetAlarmSettingsCommand(Command): def __init__(self, emulator): - self.id = "ALARM?" - self._emu = emulator + super(GetAlarmSettingsCommand, self).__init__(emulator, "ALARM?") def execute_and_reply(self, data): input = self._emu.get_target_input(data) @@ -123,53 +120,47 @@ def execute_and_reply(self, data): input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ input.alarm_visible) -class GetReadingStatusCommand: +class GetReadingStatusCommand(Command): def __init__(self, emulator): - self.id = "RDGST?" - self._emu = emulator + super(GetReadingStatusCommand, self).__init__(emulator, "RDGST?") def execute_and_reply(self, data): return str(self._emu.get_target_input(data).reading_status) -class GetInputCurveNumberCommand: +class GetInputCurveNumberCommand(Command): def __init__(self, emulator): - self.id = "INCRV?" - self._emu = emulator + super(GetInputCurveNumberCommand, self).__init__(emulator, "INCRV?") def execute_and_reply(self, data): return str(self._emu.get_target_input(data).curve_number) -class GetCurveHeaderCommand: +class GetCurveHeaderCommand(Command): def __init__(self, emulator): - self.id = "CRVHDR?" - self._emu = emulator + super(GetCurveHeaderCommand, self).__init__(emulator, "CRVHDR?") def execute_and_reply(self, data): curve = self._emu.get_target_curve(data) return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) -class GetInputTypeCommand: +class GetInputTypeCommand(Command): def __init__(self, emulator): - self.id = "INTYPE?" - self._emu = emulator + super(GetInputTypeCommand, self).__init__(emulator, "INTYPE?") def execute_and_reply(self, data): input = self._emu.get_target_input(data) return "%d,%d,%d,%d,%d" % (input.sensor_type, self._emu.bool_to_int(input.autorange_on), input.range, \ self._emu.bool_to_int(input.compensation_on), input.units) -class GetSetpointCommand: +class GetSetpointCommand(Command): def __init__(self, emulator): - self.id = "SETP?" - self._emu = emulator + super(GetSetpointCommand, self).__init__(emulator, "SETP?") def execute_and_reply(self, data): return str(self._emu.get_target_output(data).temp_setpoint) -class SetSetpointCommand: +class SetSetpointCommand(Command): def __init__(self, emulator): - self.id = "SETP" - self._emu = emulator + super(SetSetpointCommand, self).__init__(emulator, "SETP") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -178,19 +169,17 @@ def execute_and_reply(self, data): self._emu.outputs[index].temp_setpoint = new_temp return None -class GetOutputRampCommand: +class GetOutputRampCommand(Command): def __init__(self, emulator): - self.id = "RAMP?" - self._emu = emulator + super(GetOutputRampCommand, self).__init__(emulator, "RAMP?") def execute_and_reply(self, data): output = self._emu.get_target_output(data) return "%d,%f" % (output.ramp_on, output.ramp_rate) -class SetOutputRampCommand: +class SetOutputRampCommand(Command): def __init__(self, emulator): - self.id = "RAMP" - self._emu = emulator + super(SetOutputRampCommand, self).__init__(emulator, "RAMP") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -201,18 +190,16 @@ def execute_and_reply(self, data): self._emu.outputs[index].ramp_rate = ramp_rate return None -class GetHeaterRangeCommand: +class GetHeaterRangeCommand(Command): def __init__(self, emulator): - self.id = "RANGE?" - self._emu = emulator + super(GetHeaterRangeCommand, self).__init__(emulator, "RANGE?") def execute_and_reply(self, data): return str(self._emu.get_target_output(data).heater_range) -class SetHeaterRangeCommand: +class SetHeaterRangeCommand(Command): def __init__(self, emulator): - self.id = "RANGE" - self._emu = emulator + super(SetHeaterRangeCommand, self).__init__(emulator, "RANGE") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -221,18 +208,16 @@ def execute_and_reply(self, data): self._emu.outputs[index].heater_range = range return None -class GetManualOutputCommand: +class GetManualOutputCommand(Command): def __init__(self, emulator): - self.id = "MOUT?" - self._emu = emulator + super(GetManualOutputCommand, self).__init__(emulator, "MOUT?") def execute_and_reply(self, data): return str(self._emu.get_target_output(data).manual_output) -class SetManualOutputCommand: +class SetManualOutputCommand(Command): def __init__(self, emulator): - self.id = "MOUT" - self._emu = emulator + super(SetManualOutputCommand, self).__init__(emulator, "MOUT") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -241,18 +226,16 @@ def execute_and_reply(self, data): self._emu.outputs[index].manual_output = output return None -class GetPIDCommand: +class GetPIDCommand(Command): def __init__(self, emulator): - self.id = "PID?" - self._emu = emulator + super(GetPIDCommand, self).__init__(emulator, "PID?") def execute_and_reply(self, data): return "%f,%f,%f" % self._emu.get_target_output(data).pid -class SetPIDCommand: +class SetPIDCommand(Command): def __init__(self, emulator): - self.id = "PID" - self._emu = emulator + super(SetPIDCommand, self).__init__(emulator, "PID") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -260,19 +243,17 @@ def execute_and_reply(self, data): self._emu.outputs[index].pid = tuple([float(t) for t in tokens[1:]]) return None -class GetOutputModeCommand: +class GetOutputModeCommand(Command): def __init__(self, emulator): - self.id = "OUTMODE?" - self._emu = emulator + super(GetOutputModeCommand, self).__init__(emulator, "OUTMODE?") def execute_and_reply(self, data): output = self._emu.get_target_output(data) return "%d,%d,%d" % (output.output_mode, output.control_input, self._emu.bool_to_int(output.powerup_enabled)) -class SetOutputModeCommand: +class SetOutputModeCommand(Command): def __init__(self, emulator): - self.id = "OUTMODE" - self._emu = emulator + super(SetOutputModeCommand, self).__init__(emulator, "OUTMODE") def execute_and_reply(self, data): tokens = self._emu.get_set_tokens(data, self.id) @@ -284,28 +265,26 @@ def execute_and_reply(self, data): output.powerup_enabled = self._emu.int_to_bool(powerup) return None -class GetHeaterOutputCommand: +class GetHeaterOutputCommand(Command): def __init__(self, emulator): - self.id = "HTR?" - self._emu = emulator + super(GetHeaterOutputCommand, self).__init__(emulator, "HTR?") def execute_and_reply(self, data): return str(self._emu.get_target_output(data).heater_output) -class GetHeaterStatusCommand: +class GetHeaterStatusCommand(Command): def __init__(self, emulator): - self.id = "HTRST?" - self._emu = emulator + super(GetHeaterStatusCommand, self).__init__(emulator, "HTRST?") def execute_and_reply(self, data): return str(self._emu.get_target_output(data).heater_status) -class StartAutotuneCommand(NullCommand): +class StartAutotuneCommand(Command): def __init__(self, emulator): - self.id = "ATUNE" + super(StartAutotuneCommand, self).__init__(emulator, "ATUNE") -class Lakeshore336Emulator: +class Lakeshore336Emulator(object): def __init__(self):# self.id = "EmulatedDevice" self._populate_inputs() From 713eacbd487cf95237d64691cc5d4d9871a7350d Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Mon, 8 Aug 2016 17:57:37 +0100 Subject: [PATCH 0023/1466] Ticket 1404: refactored emulator so now model holding state is in a separate class --- .../lakeshore336_emulator.py | 339 +++++++++--------- 1 file changed, 177 insertions(+), 162 deletions(-) diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py index 1266ef3e..e163b168 100644 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -55,287 +55,323 @@ def __init__(self): self.limit = 1000.0 self.coeff = 2 +class LakeshoreModel(object): + def __init__(self): + self.id = "EmulatedDevice" + self._populate_inputs() + self._populate_curves() + self._populate_outputs() + + def _populate_inputs(self): + self._inputs = dict() + self._inputs["A"] = LakeshoreInput() + self._inputs["B"] = LakeshoreInput() + self._inputs["C"] = LakeshoreInput() + self._inputs["D"] = LakeshoreInput() + + def _populate_outputs(self): + self._outputs = dict() + self._outputs["1"] = LakeshoreOutput() + self._outputs["2"] = LakeshoreOutput() + + def _populate_curves(self): + self._curves = dict() + self._curves["15"] = CalibrationCurve() + + def get_input(self, input_key): + return self._inputs[input_key] + + def get_output(self, output_key): + return self._outputs[output_key] + + def get_curve(self, curve_key): + return self._curves[curve_key] + + class Command(object): - def __init__(self, emulator, id): - self._emu = emulator + def __init__(self, model, id): + self._model = model self.id = id def execute_and_reply(self, data): return None + def _get_target_input(self, data): + index = data[-1] + return self._model.get_input(index) + + def _get_target_output(self, data): + index = data[-1] + return self._model.get_output(index) + + def _get_target_curve(self, data): + index = data.split()[-1] + return self._model.get_curve(index) + + def _get_set_tokens(self, data): + return data[len(self.id + " "):].split(",") + + def _bool_to_int(self, bool): + return 1 if bool else 0 + + def _int_to_bool(self, value): + return value == 1 + + class GetIdCommand(Command): - def __init__(self, emulator): - super(GetIdCommand, self).__init__(emulator, "*IDN?") + def __init__(self, model): + super(GetIdCommand, self).__init__(model, "*IDN?") def execute_and_reply(self, data): - return "LSCI,%s" % (self._emu.id) + return "LSCI,%s" % (self._model.id) class GetInputNameCommand(Command): - def __init__(self, emulator): - super(GetInputNameCommand, self).__init__(emulator, "INNAME?") + def __init__(self, model): + super(GetInputNameCommand, self).__init__(model, "INNAME?") def execute_and_reply(self, data): - return self._emu.get_target_input(data).name + return self._get_target_input(data).name class SetInputNameCommand(Command): - def __init__(self, emulator): - super(SetInputNameCommand, self).__init__(emulator, "INNAME") + def __init__(self, model): + super(SetInputNameCommand, self).__init__(model, "INNAME") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] new_name = tokens[1].strip("\"") - self._emu.inputs[index].name = new_name + self._model.get_input(index).name = new_name return None class GetInputTempCommand(Command): - def __init__(self, emulator): - super(GetInputTempCommand, self).__init__(emulator, "KRDG?") + def __init__(self, model): + super(GetInputTempCommand, self).__init__(model, "KRDG?") def execute_and_reply(self, data): - return str(self._emu.get_target_input(data).get_temperature()) + return str(self._get_target_input(data).get_temperature()) class GetInputVoltageCommand(Command): - def __init__(self, emulator): - super(GetInputVoltageCommand, self).__init__(emulator, "SRDG?") + def __init__(self, model): + super(GetInputVoltageCommand, self).__init__(model, "SRDG?") def execute_and_reply(self, data): - return str(self._emu.get_target_input(data).raw_voltage) + return str(self._get_target_input(data).raw_voltage) class GetAlarmStatusCommand(Command): - def __init__(self, emulator): - super(GetAlarmStatusCommand, self).__init__(emulator, "ALARMST?") + def __init__(self, model): + super(GetAlarmStatusCommand, self).__init__(model, "ALARMST?") def execute_and_reply(self, data): - input = self._emu.get_target_input(data) - return "%d,%d" % (self._emu.bool_to_int(input.has_high_alarm), self._emu.bool_to_int(input.has_low_alarm)) + input = self._get_target_input(data) + return "%d,%d" % (self._bool_to_int(input.has_high_alarm), self._bool_to_int(input.has_low_alarm)) class GetAlarmSettingsCommand(Command): - def __init__(self, emulator): - super(GetAlarmSettingsCommand, self).__init__(emulator, "ALARM?") + def __init__(self, model): + super(GetAlarmSettingsCommand, self).__init__(model, "ALARM?") def execute_and_reply(self, data): - input = self._emu.get_target_input(data) + input = self._get_target_input(data) return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ input.alarm_visible) class GetReadingStatusCommand(Command): - def __init__(self, emulator): - super(GetReadingStatusCommand, self).__init__(emulator, "RDGST?") + def __init__(self, model): + super(GetReadingStatusCommand, self).__init__(model, "RDGST?") def execute_and_reply(self, data): - return str(self._emu.get_target_input(data).reading_status) + return str(self._get_target_input(data).reading_status) class GetInputCurveNumberCommand(Command): - def __init__(self, emulator): - super(GetInputCurveNumberCommand, self).__init__(emulator, "INCRV?") + def __init__(self, model): + super(GetInputCurveNumberCommand, self).__init__(model, "INCRV?") def execute_and_reply(self, data): - return str(self._emu.get_target_input(data).curve_number) + return str(self._get_target_input(data).curve_number) class GetCurveHeaderCommand(Command): - def __init__(self, emulator): - super(GetCurveHeaderCommand, self).__init__(emulator, "CRVHDR?") + def __init__(self, model): + super(GetCurveHeaderCommand, self).__init__(model, "CRVHDR?") def execute_and_reply(self, data): - curve = self._emu.get_target_curve(data) + curve = self._get_target_curve(data) return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) class GetInputTypeCommand(Command): - def __init__(self, emulator): - super(GetInputTypeCommand, self).__init__(emulator, "INTYPE?") + def __init__(self, model): + super(GetInputTypeCommand, self).__init__(model, "INTYPE?") def execute_and_reply(self, data): - input = self._emu.get_target_input(data) - return "%d,%d,%d,%d,%d" % (input.sensor_type, self._emu.bool_to_int(input.autorange_on), input.range, \ - self._emu.bool_to_int(input.compensation_on), input.units) + input = self._get_target_input(data) + return "%d,%d,%d,%d,%d" % (input.sensor_type, self._bool_to_int(input.autorange_on), input.range, \ + self._bool_to_int(input.compensation_on), input.units) class GetSetpointCommand(Command): - def __init__(self, emulator): - super(GetSetpointCommand, self).__init__(emulator, "SETP?") + def __init__(self, model): + super(GetSetpointCommand, self).__init__(model, "SETP?") def execute_and_reply(self, data): - return str(self._emu.get_target_output(data).temp_setpoint) + return str(self._get_target_output(data).temp_setpoint) class SetSetpointCommand(Command): - def __init__(self, emulator): - super(SetSetpointCommand, self).__init__(emulator, "SETP") + def __init__(self, model): + super(SetSetpointCommand, self).__init__(model, "SETP") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] new_temp = float(tokens[1]) - self._emu.outputs[index].temp_setpoint = new_temp + self._model.get_output(index).temp_setpoint = new_temp return None class GetOutputRampCommand(Command): - def __init__(self, emulator): - super(GetOutputRampCommand, self).__init__(emulator, "RAMP?") + def __init__(self, model): + super(GetOutputRampCommand, self).__init__(model, "RAMP?") def execute_and_reply(self, data): - output = self._emu.get_target_output(data) + output = self._get_target_output(data) return "%d,%f" % (output.ramp_on, output.ramp_rate) class SetOutputRampCommand(Command): - def __init__(self, emulator): - super(SetOutputRampCommand, self).__init__(emulator, "RAMP") + def __init__(self, model): + super(SetOutputRampCommand, self).__init__(model, "RAMP") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] - ramp_on = self._emu.int_to_bool(int(tokens[1])) + ramp_on = self._int_to_bool(int(tokens[1])) ramp_rate = float(tokens[2]) - self._emu.outputs[index].ramp_on = ramp_on - self._emu.outputs[index].ramp_rate = ramp_rate + self._model.get_output(index).ramp_on = ramp_on + self._model.get_output(index).ramp_rate = ramp_rate return None class GetHeaterRangeCommand(Command): - def __init__(self, emulator): - super(GetHeaterRangeCommand, self).__init__(emulator, "RANGE?") + def __init__(self, model): + super(GetHeaterRangeCommand, self).__init__(model, "RANGE?") def execute_and_reply(self, data): - return str(self._emu.get_target_output(data).heater_range) + return str(self._get_target_output(data).heater_range) class SetHeaterRangeCommand(Command): - def __init__(self, emulator): - super(SetHeaterRangeCommand, self).__init__(emulator, "RANGE") + def __init__(self, model): + super(SetHeaterRangeCommand, self).__init__(model, "RANGE") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] range = int(tokens[1]) - self._emu.outputs[index].heater_range = range + self._model.get_output(index).heater_range = range return None class GetManualOutputCommand(Command): - def __init__(self, emulator): - super(GetManualOutputCommand, self).__init__(emulator, "MOUT?") + def __init__(self, model): + super(GetManualOutputCommand, self).__init__(model, "MOUT?") def execute_and_reply(self, data): - return str(self._emu.get_target_output(data).manual_output) + return str(self._get_target_output(data).manual_output) class SetManualOutputCommand(Command): - def __init__(self, emulator): - super(SetManualOutputCommand, self).__init__(emulator, "MOUT") + def __init__(self, model): + super(SetManualOutputCommand, self).__init__(model, "MOUT") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] - output = float(tokens[1]) - self._emu.outputs[index].manual_output = output + man_output = float(tokens[1]) + self._model.get_output(index).manual_output = man_output return None class GetPIDCommand(Command): - def __init__(self, emulator): - super(GetPIDCommand, self).__init__(emulator, "PID?") + def __init__(self, model): + super(GetPIDCommand, self).__init__(model, "PID?") def execute_and_reply(self, data): - return "%f,%f,%f" % self._emu.get_target_output(data).pid + return "%f,%f,%f" % self._get_target_output(data).pid class SetPIDCommand(Command): - def __init__(self, emulator): - super(SetPIDCommand, self).__init__(emulator, "PID") + def __init__(self, model): + super(SetPIDCommand, self).__init__(model, "PID") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] - self._emu.outputs[index].pid = tuple([float(t) for t in tokens[1:]]) + self._model.get_output(index).pid = tuple([float(t) for t in tokens[1:]]) return None class GetOutputModeCommand(Command): - def __init__(self, emulator): - super(GetOutputModeCommand, self).__init__(emulator, "OUTMODE?") + def __init__(self, model): + super(GetOutputModeCommand, self).__init__(model, "OUTMODE?") def execute_and_reply(self, data): - output = self._emu.get_target_output(data) - return "%d,%d,%d" % (output.output_mode, output.control_input, self._emu.bool_to_int(output.powerup_enabled)) + output = self._get_target_output(data) + return "%d,%d,%d" % (output.output_mode, output.control_input, self._bool_to_int(output.powerup_enabled)) class SetOutputModeCommand(Command): - def __init__(self, emulator): - super(SetOutputModeCommand, self).__init__(emulator, "OUTMODE") + def __init__(self, model): + super(SetOutputModeCommand, self).__init__(model, "OUTMODE") def execute_and_reply(self, data): - tokens = self._emu.get_set_tokens(data, self.id) + tokens = self._get_set_tokens(data) index = tokens[0] - output = self._emu.get_target_output(index) + output = self._get_target_output(index) (mode, ctr_input, powerup) = [int(t) for t in tokens[1:]] output.output_mode = mode output.control_input = ctr_input - output.powerup_enabled = self._emu.int_to_bool(powerup) + output.powerup_enabled = self._int_to_bool(powerup) return None class GetHeaterOutputCommand(Command): - def __init__(self, emulator): - super(GetHeaterOutputCommand, self).__init__(emulator, "HTR?") + def __init__(self, model): + super(GetHeaterOutputCommand, self).__init__(model, "HTR?") def execute_and_reply(self, data): - return str(self._emu.get_target_output(data).heater_output) + return str(self._get_target_output(data).heater_output) class GetHeaterStatusCommand(Command): - def __init__(self, emulator): - super(GetHeaterStatusCommand, self).__init__(emulator, "HTRST?") + def __init__(self, model): + super(GetHeaterStatusCommand, self).__init__(model, "HTRST?") def execute_and_reply(self, data): - return str(self._emu.get_target_output(data).heater_status) + return str(self._get_target_output(data).heater_status) class StartAutotuneCommand(Command): - def __init__(self, emulator): - super(StartAutotuneCommand, self).__init__(emulator, "ATUNE") + def __init__(self, model): + super(StartAutotuneCommand, self).__init__(model, "ATUNE") class Lakeshore336Emulator(object): - def __init__(self):# - self.id = "EmulatedDevice" - self._populate_inputs() - self._populate_curves() - self._populate_outputs() + def __init__(self): + self._model = LakeshoreModel() self._populate_commands() - def _populate_inputs(self): - self.inputs = dict() - self.inputs["A"] = LakeshoreInput() - self.inputs["B"] = LakeshoreInput() - self.inputs["C"] = LakeshoreInput() - self.inputs["D"] = LakeshoreInput() - - def _populate_outputs(self): - self.outputs = dict() - self.outputs["1"] = LakeshoreOutput() - self.outputs["2"] = LakeshoreOutput() - - def _populate_curves(self): - self.curves = dict() - self.curves["15"] = CalibrationCurve() - def _populate_commands(self): self.commands = list() - self.commands.append(GetIdCommand(self)) - self.commands.append(GetInputNameCommand(self)) - self.commands.append(SetInputNameCommand(self)) - self.commands.append(GetInputTempCommand(self)) - self.commands.append(GetInputVoltageCommand(self)) - self.commands.append(GetAlarmStatusCommand(self)) - self.commands.append(GetAlarmSettingsCommand(self)) - self.commands.append(GetReadingStatusCommand(self)) - self.commands.append(GetInputCurveNumberCommand(self)) - self.commands.append(GetCurveHeaderCommand(self)) - self.commands.append(GetInputTypeCommand(self)) - self.commands.append(GetSetpointCommand(self)) - self.commands.append(SetSetpointCommand(self)) - self.commands.append(GetOutputRampCommand(self)) - self.commands.append(SetOutputRampCommand(self)) - self.commands.append(GetHeaterRangeCommand(self)) - self.commands.append(SetHeaterRangeCommand(self)) - self.commands.append(GetManualOutputCommand(self)) - self.commands.append(SetManualOutputCommand(self)) - self.commands.append(GetPIDCommand(self)) - self.commands.append(SetPIDCommand(self)) - self.commands.append(GetOutputModeCommand(self)) - self.commands.append(SetOutputModeCommand(self)) - self.commands.append(GetHeaterOutputCommand(self)) - self.commands.append(GetHeaterStatusCommand(self)) - self.commands.append(StartAutotuneCommand(self)) + self.commands.append(GetIdCommand(self._model)) + self.commands.append(GetInputNameCommand(self._model)) + self.commands.append(SetInputNameCommand(self._model)) + self.commands.append(GetInputTempCommand(self._model)) + self.commands.append(GetInputVoltageCommand(self._model)) + self.commands.append(GetAlarmStatusCommand(self._model)) + self.commands.append(GetAlarmSettingsCommand(self._model)) + self.commands.append(GetReadingStatusCommand(self._model)) + self.commands.append(GetInputCurveNumberCommand(self._model)) + self.commands.append(GetCurveHeaderCommand(self._model)) + self.commands.append(GetInputTypeCommand(self._model)) + self.commands.append(GetSetpointCommand(self._model)) + self.commands.append(SetSetpointCommand(self._model)) + self.commands.append(GetOutputRampCommand(self._model)) + self.commands.append(SetOutputRampCommand(self._model)) + self.commands.append(GetHeaterRangeCommand(self._model)) + self.commands.append(SetHeaterRangeCommand(self._model)) + self.commands.append(GetManualOutputCommand(self._model)) + self.commands.append(SetManualOutputCommand(self._model)) + self.commands.append(GetPIDCommand(self._model)) + self.commands.append(SetPIDCommand(self._model)) + self.commands.append(GetOutputModeCommand(self._model)) + self.commands.append(SetOutputModeCommand(self._model)) + self.commands.append(GetHeaterOutputCommand(self._model)) + self.commands.append(GetHeaterStatusCommand(self._model)) + self.commands.append(StartAutotuneCommand(self._model)) def process(self, data): msg = self._reply(data) @@ -353,27 +389,6 @@ def _reply(self, data): print "***\n*** Command \"%s\" not supported! ***\n***" % (data) return None - def bool_to_int(self, bool): - return 1 if bool else 0 - - def int_to_bool(self, value): - return value == 1 - - def get_target_input(self, data): - index = data[-1] - return self.inputs[index] - - def get_target_curve(self, data): - index = data.split()[-1] - return self.curves[index] - - def get_target_output(self, data): - index = data[-1] - return self.outputs[index] - - def get_set_tokens(self, data, command): - return data[len(command + " "):].split(",") - if __name__ == "__main__": port_file = __file__ From e93945b929ccdae326ee397e5879be34790a860d Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Wed, 10 Aug 2016 10:36:23 +0100 Subject: [PATCH 0024/1466] Ticket 1404: changed the way temperature and heater output are generated, so they get displayed better in CSS graph --- .../lakeshore336_emulator.py | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py index e163b168..905635b0 100644 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -4,9 +4,9 @@ from telnet_engine import TelnetEngine class LakeshoreInput(object): - def __init__(self): + def __init__(self, start_temp=0.0): self.name = "Default Name" - self._temperature = 0.0 + self._temperature = start_temp self.raw_voltage = random.uniform(0, 1000) self.has_high_alarm = False self.has_low_alarm = False @@ -33,7 +33,7 @@ def get_temperature(self): class LakeshoreOutput(object): - def __init__(self): + def __init__(self, start_heater_output=0.0): self.temp_setpoint = 100.0 self.ramp_on = True self.ramp_rate = 2.0 @@ -43,9 +43,16 @@ def __init__(self): self.output_mode = 1 self.control_input = 1 self.powerup_enabled = True - self.heater_output = 21.0 + self._heater_output = start_heater_output + self._heater_increment = 10.0 self.heater_status = 2 + def get_heater_output(self): + return_value = self._heater_output + self._heater_output += self._heater_increment + self._heater_increment *= -1 + return return_value + class CalibrationCurve(object): def __init__(self): @@ -64,15 +71,15 @@ def __init__(self): def _populate_inputs(self): self._inputs = dict() - self._inputs["A"] = LakeshoreInput() - self._inputs["B"] = LakeshoreInput() - self._inputs["C"] = LakeshoreInput() - self._inputs["D"] = LakeshoreInput() + self._inputs["A"] = LakeshoreInput(0.0) + self._inputs["B"] = LakeshoreInput(5.0) + self._inputs["C"] = LakeshoreInput(10.0) + self._inputs["D"] = LakeshoreInput(20.0) def _populate_outputs(self): self._outputs = dict() - self._outputs["1"] = LakeshoreOutput() - self._outputs["2"] = LakeshoreOutput() + self._outputs["1"] = LakeshoreOutput(50.0) + self._outputs["2"] = LakeshoreOutput(80.0) def _populate_curves(self): self._curves = dict() @@ -325,7 +332,7 @@ def __init__(self, model): super(GetHeaterOutputCommand, self).__init__(model, "HTR?") def execute_and_reply(self, data): - return str(self._get_target_output(data).heater_output) + return str(self._get_target_output(data).get_heater_output()) class GetHeaterStatusCommand(Command): def __init__(self, model): From 8add4173988ebb92455fbb9027565c2660084c44 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 24 Aug 2016 11:24:57 +0100 Subject: [PATCH 0025/1466] Add emulator --- linkam95_emulator/linkam95_emulator.py | 226 +++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 linkam95_emulator/linkam95_emulator.py diff --git a/linkam95_emulator/linkam95_emulator.py b/linkam95_emulator/linkam95_emulator.py new file mode 100644 index 00000000..964699f0 --- /dev/null +++ b/linkam95_emulator/linkam95_emulator.py @@ -0,0 +1,226 @@ +import sys, random +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +PUMP_MODE_AUTO = "AUTO" +PUMP_MODE_MANUAL = "MANUAL" + +T95_STATE_STOP = "stopped" +T95_STATE_HEAT = "heat" +T95_STATE_COOL = "cool" +T95_STATE_HOLD = "hold" + +class LinkamModel(object): + def __init__(self): + self.id = "EmulatedDevice" + self.ramp_state = T95_STATE_STOP + self.temperature_sp = 0.0 + self.ramp_rate = 1.0 + self.pump_mode = PUMP_MODE_AUTO + self.temperature = 0.0 + self.pump_speed = 0 + self.command_hold = False + + def get_status_bytes(self): + Tarray = [0x80] * 10 + + # Status byte (SB1) + Tarray[0] = { + T95_STATE_STOP: 0x01, + T95_STATE_HEAT: 0x10, + T95_STATE_COOL: 0x20, + T95_STATE_HOLD: 0x30, + }.get(self.ramp_state, 0x01) + if Tarray[0] == 0x30 and self.command_hold: + Tarray[0] = 0x50 + + # Pump status byte (PB1) + Tarray[2] = 0x80 + self.pump_speed + + # Temperature + self._update_temperature() + Tarray[6:10] = [ord(x) for x in "%04x" % (int(self.temperature * 10) & 0xFFFF)] + + return ''.join(chr(c) for c in Tarray) + + def start(self): + if self.ramp_state == T95_STATE_STOP: + self.ramp_state = T95_STATE_HOLD + self._update_state() + + def stop(self): + self.ramp_state = T95_STATE_STOP + + def heat(self): + self.unhold() + + def cool(self): + self.unhold() + + def hold(self): + self.command_hold = True + self._update_state() + + def unhold(self): + self.command_hold = False + self._update_state() + + def _update_state(self): + if self.ramp_state == T95_STATE_STOP: + return + elif self.command_hold: + self.ramp_state = T95_STATE_HOLD + elif self.temperature_sp < self.temperature: + self.ramp_state = T95_STATE_COOL + elif self.temperature_sp > self.temperature: + self.ramp_state = T95_STATE_HEAT + else: + self.ramp_state = T95_STATE_HOLD + + def _update_temperature(self): + import math + if self.ramp_state!=T95_STATE_HOLD and self.ramp_state!=T95_STATE_STOP: + self.temperature += math.copysign(1, self.temperature_sp-self.temperature)*self.ramp_rate/60.0 + if self.ramp_state == T95_STATE_COOL: + self.pump_speed = min(int(self.ramp_rate/3),30) + else: + self.pump_speed = 0 + self._update_state() + +class Command(object): + def __init__(self, model, id): + self._model = model + self.id = id + + def execute_and_reply(self, data): + return None + + def _get_set_token(self, data): + return data[2:] + + +class GetStatusCommand(Command): + def __init__(self, model): + super(GetStatusCommand, self).__init__(model, "T") + + def execute_and_reply(self, data): + return self._model.get_status_bytes() + +class SetRateCommand(Command): + def __init__(self, model): + super(SetRateCommand, self).__init__(model, "R") + + def execute_and_reply(self, data): + self._model.ramp_rate = int(self._get_set_token(data))/100.0 + return None + +class SetLimitCommand(Command): + def __init__(self, model): + super(SetLimitCommand, self).__init__(model, "L") + + def execute_and_reply(self, data): + self._model.temperature_sp = float(self._get_set_token(data))/10.0 + return None + +class StartRampControlCommand(Command): + def __init__(self, model): + super(StartRampControlCommand, self).__init__(model, "S") + + def execute_and_reply(self, data): + self._model.start() + return None + +class StopRampControlCommand(Command): + def __init__(self, model): + super(StopRampControlCommand, self).__init__(model, "E") + + def execute_and_reply(self, data): + self._model.stop() + return None + +class ForceHeatControlCommand(Command): + def __init__(self, model): + super(ForceHeatControlCommand, self).__init__(model, "H") + + def execute_and_reply(self, data): + self._model.heat() + return None + +class ForceCoolControlCommand(Command): + def __init__(self, model): + super(ForceCoolControlCommand, self).__init__(model, "C") + + def execute_and_reply(self, data): + self._model.cool() + return None + +class HoldControlCommand(Command): + def __init__(self, model): + super(HoldControlCommand, self).__init__(model, "O") + + def execute_and_reply(self, data): + self._model.hold() + return None + +class SetPumpStateCommand(Command): + def __init__(self, model): + super(SetPumpStateCommand, self).__init__(model, "P") + + def execute_and_reply(self, data): + token = self._get_set_token(data) + if token == "a": + self._model.pump_mode = PUMP_MODE_AUTO + elif token == "m": + self._model.pump_mode = PUMP_MODE_MANUAL + else: + try: + speed = ord(token) + if token in range(0,31): + self._model.pump_speed = token + except: + pass + return None + +class Linkam95Emulator(object): + def __init__(self): + self._model = LinkamModel() + self._populate_commands() + + def _populate_commands(self): + self.commands = list() + self.commands.append(SetPumpStateCommand(self._model)) + self.commands.append(HoldControlCommand(self._model)) + self.commands.append(ForceCoolControlCommand(self._model)) + self.commands.append(ForceHeatControlCommand(self._model)) + self.commands.append(StopRampControlCommand(self._model)) + self.commands.append(StartRampControlCommand(self._model)) + self.commands.append(SetLimitCommand(self._model)) + self.commands.append(SetRateCommand(self._model)) + self.commands.append(GetStatusCommand(self._model)) + + def process(self, data): + msg = self._reply(data) + if msg is None: + msg = "" + return msg + "\r" + + return None + + def _reply(self, data): + command_id = data[0] + try: + (command,) = [cmd for cmd in self.commands if cmd.id == command_id] + return command.execute_and_reply(data) + except ValueError: + print "***\n*** Command \"%s\" not supported! ***\n***" % (data) + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = Linkam95Emulator() + TelnetEngine().start(dev, port_file) + + + From 279f3a806b31dda75986ca7092e627e393f344ce Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Wed, 19 Oct 2016 15:50:55 +0100 Subject: [PATCH 0026/1466] Ticket 1524: moved clf-type emulators into own folder --- example_emulator/example_emulator.py | 26 -- .../iris_cryo_valve_emulator.py | 36 -- .../lakeshore336_emulator.py | 406 ------------------ linkam95_emulator/linkam95_emulator.py | 226 ---------- mkspdr2000_emulator/mkspdr2000_emulator.py | 32 -- tpg26x/Tpg26xEmulator.py | 109 ----- 6 files changed, 835 deletions(-) delete mode 100644 example_emulator/example_emulator.py delete mode 100644 iris_cryo_valve_emulator/iris_cryo_valve_emulator.py delete mode 100644 lakeshore336_emulator/lakeshore336_emulator.py delete mode 100644 linkam95_emulator/linkam95_emulator.py delete mode 100644 mkspdr2000_emulator/mkspdr2000_emulator.py delete mode 100644 tpg26x/Tpg26xEmulator.py diff --git a/example_emulator/example_emulator.py b/example_emulator/example_emulator.py deleted file mode 100644 index 4fb92c1a..00000000 --- a/example_emulator/example_emulator.py +++ /dev/null @@ -1,26 +0,0 @@ -import sys - -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - - -class TemplateEmulator: - def __init__(self): - self.state = 0 - - def process(self, data): - if data == 'r_state': - return 'state ' + str(int(self.state)) - - if data.startswith('w_state'): - self.state = data.split()[-1] - return 'w_state 1' - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = TemplateEmulator() - TelnetEngine().start(dev, port_file) diff --git a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py deleted file mode 100644 index 27e73580..00000000 --- a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py +++ /dev/null @@ -1,36 +0,0 @@ -import sys -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -class CryValveEmulator: - - CLOSED = "CLOSED" - OPEN = "OPEN" - TERMINATOR = "\r" - - def __init__(self): - self.state = CryValveEmulator.CLOSED - - def process(self, data): - if data == 'CLOSE': - self.state = CryValveEmulator.CLOSED - return CryValveEmulator.TERMINATOR - - if data == 'OPEN': - self.state = CryValveEmulator.OPEN - return CryValveEmulator.TERMINATOR - - if data == '?': - return "SOLENOID " + self.state + CryValveEmulator.TERMINATOR - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = CryValveEmulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py deleted file mode 100644 index 905635b0..00000000 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ /dev/null @@ -1,406 +0,0 @@ -import sys, random -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -class LakeshoreInput(object): - def __init__(self, start_temp=0.0): - self.name = "Default Name" - self._temperature = start_temp - self.raw_voltage = random.uniform(0, 1000) - self.has_high_alarm = False - self.has_low_alarm = False - self.alarm_enabled = True - self.alarm_high_setpoint = 1000.0 - self.alarm_low_setpoint = 10.0 - self.alarm_deadband = 0.5 - self.alarm_latching = False - self.alarm_audible = True - self.alarm_visible = True - self.reading_status = 0 - self.curve_number = 15 - self.sensor_type = 3 - self.autorange_on = True - self.range = 5 - self.compensation_on = True - self.units = 1 - - - def get_temperature(self): - return_value = self._temperature; - self._temperature += 0.1 - return return_value - - -class LakeshoreOutput(object): - def __init__(self, start_heater_output=0.0): - self.temp_setpoint = 100.0 - self.ramp_on = True - self.ramp_rate = 2.0 - self.heater_range = 2 - self.manual_output = 20.0 - self.pid = (1.0, 2.0, 3.0) - self.output_mode = 1 - self.control_input = 1 - self.powerup_enabled = True - self._heater_output = start_heater_output - self._heater_increment = 10.0 - self.heater_status = 2 - - def get_heater_output(self): - return_value = self._heater_output - self._heater_output += self._heater_increment - self._heater_increment *= -1 - return return_value - - -class CalibrationCurve(object): - def __init__(self): - self.name = "Emulator curve " # Must be 15 chars - self.serial = "0123456789" # Must be 10 chars - self.format = 1 - self.limit = 1000.0 - self.coeff = 2 - -class LakeshoreModel(object): - def __init__(self): - self.id = "EmulatedDevice" - self._populate_inputs() - self._populate_curves() - self._populate_outputs() - - def _populate_inputs(self): - self._inputs = dict() - self._inputs["A"] = LakeshoreInput(0.0) - self._inputs["B"] = LakeshoreInput(5.0) - self._inputs["C"] = LakeshoreInput(10.0) - self._inputs["D"] = LakeshoreInput(20.0) - - def _populate_outputs(self): - self._outputs = dict() - self._outputs["1"] = LakeshoreOutput(50.0) - self._outputs["2"] = LakeshoreOutput(80.0) - - def _populate_curves(self): - self._curves = dict() - self._curves["15"] = CalibrationCurve() - - def get_input(self, input_key): - return self._inputs[input_key] - - def get_output(self, output_key): - return self._outputs[output_key] - - def get_curve(self, curve_key): - return self._curves[curve_key] - - -class Command(object): - def __init__(self, model, id): - self._model = model - self.id = id - - def execute_and_reply(self, data): - return None - - def _get_target_input(self, data): - index = data[-1] - return self._model.get_input(index) - - def _get_target_output(self, data): - index = data[-1] - return self._model.get_output(index) - - def _get_target_curve(self, data): - index = data.split()[-1] - return self._model.get_curve(index) - - def _get_set_tokens(self, data): - return data[len(self.id + " "):].split(",") - - def _bool_to_int(self, bool): - return 1 if bool else 0 - - def _int_to_bool(self, value): - return value == 1 - - -class GetIdCommand(Command): - def __init__(self, model): - super(GetIdCommand, self).__init__(model, "*IDN?") - - def execute_and_reply(self, data): - return "LSCI,%s" % (self._model.id) - -class GetInputNameCommand(Command): - def __init__(self, model): - super(GetInputNameCommand, self).__init__(model, "INNAME?") - - def execute_and_reply(self, data): - return self._get_target_input(data).name - -class SetInputNameCommand(Command): - def __init__(self, model): - super(SetInputNameCommand, self).__init__(model, "INNAME") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - new_name = tokens[1].strip("\"") - self._model.get_input(index).name = new_name - return None - -class GetInputTempCommand(Command): - def __init__(self, model): - super(GetInputTempCommand, self).__init__(model, "KRDG?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).get_temperature()) - -class GetInputVoltageCommand(Command): - def __init__(self, model): - super(GetInputVoltageCommand, self).__init__(model, "SRDG?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).raw_voltage) - -class GetAlarmStatusCommand(Command): - def __init__(self, model): - super(GetAlarmStatusCommand, self).__init__(model, "ALARMST?") - - def execute_and_reply(self, data): - input = self._get_target_input(data) - return "%d,%d" % (self._bool_to_int(input.has_high_alarm), self._bool_to_int(input.has_low_alarm)) - -class GetAlarmSettingsCommand(Command): - def __init__(self, model): - super(GetAlarmSettingsCommand, self).__init__(model, "ALARM?") - - def execute_and_reply(self, data): - input = self._get_target_input(data) - return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ - input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ - input.alarm_visible) - -class GetReadingStatusCommand(Command): - def __init__(self, model): - super(GetReadingStatusCommand, self).__init__(model, "RDGST?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).reading_status) - -class GetInputCurveNumberCommand(Command): - def __init__(self, model): - super(GetInputCurveNumberCommand, self).__init__(model, "INCRV?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).curve_number) - -class GetCurveHeaderCommand(Command): - def __init__(self, model): - super(GetCurveHeaderCommand, self).__init__(model, "CRVHDR?") - - def execute_and_reply(self, data): - curve = self._get_target_curve(data) - return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) - -class GetInputTypeCommand(Command): - def __init__(self, model): - super(GetInputTypeCommand, self).__init__(model, "INTYPE?") - - def execute_and_reply(self, data): - input = self._get_target_input(data) - return "%d,%d,%d,%d,%d" % (input.sensor_type, self._bool_to_int(input.autorange_on), input.range, \ - self._bool_to_int(input.compensation_on), input.units) - -class GetSetpointCommand(Command): - def __init__(self, model): - super(GetSetpointCommand, self).__init__(model, "SETP?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).temp_setpoint) - -class SetSetpointCommand(Command): - def __init__(self, model): - super(SetSetpointCommand, self).__init__(model, "SETP") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - new_temp = float(tokens[1]) - self._model.get_output(index).temp_setpoint = new_temp - return None - -class GetOutputRampCommand(Command): - def __init__(self, model): - super(GetOutputRampCommand, self).__init__(model, "RAMP?") - - def execute_and_reply(self, data): - output = self._get_target_output(data) - return "%d,%f" % (output.ramp_on, output.ramp_rate) - -class SetOutputRampCommand(Command): - def __init__(self, model): - super(SetOutputRampCommand, self).__init__(model, "RAMP") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - ramp_on = self._int_to_bool(int(tokens[1])) - ramp_rate = float(tokens[2]) - self._model.get_output(index).ramp_on = ramp_on - self._model.get_output(index).ramp_rate = ramp_rate - return None - -class GetHeaterRangeCommand(Command): - def __init__(self, model): - super(GetHeaterRangeCommand, self).__init__(model, "RANGE?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).heater_range) - -class SetHeaterRangeCommand(Command): - def __init__(self, model): - super(SetHeaterRangeCommand, self).__init__(model, "RANGE") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - range = int(tokens[1]) - self._model.get_output(index).heater_range = range - return None - -class GetManualOutputCommand(Command): - def __init__(self, model): - super(GetManualOutputCommand, self).__init__(model, "MOUT?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).manual_output) - -class SetManualOutputCommand(Command): - def __init__(self, model): - super(SetManualOutputCommand, self).__init__(model, "MOUT") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - man_output = float(tokens[1]) - self._model.get_output(index).manual_output = man_output - return None - -class GetPIDCommand(Command): - def __init__(self, model): - super(GetPIDCommand, self).__init__(model, "PID?") - - def execute_and_reply(self, data): - return "%f,%f,%f" % self._get_target_output(data).pid - -class SetPIDCommand(Command): - def __init__(self, model): - super(SetPIDCommand, self).__init__(model, "PID") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - self._model.get_output(index).pid = tuple([float(t) for t in tokens[1:]]) - return None - -class GetOutputModeCommand(Command): - def __init__(self, model): - super(GetOutputModeCommand, self).__init__(model, "OUTMODE?") - - def execute_and_reply(self, data): - output = self._get_target_output(data) - return "%d,%d,%d" % (output.output_mode, output.control_input, self._bool_to_int(output.powerup_enabled)) - -class SetOutputModeCommand(Command): - def __init__(self, model): - super(SetOutputModeCommand, self).__init__(model, "OUTMODE") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - output = self._get_target_output(index) - (mode, ctr_input, powerup) = [int(t) for t in tokens[1:]] - output.output_mode = mode - output.control_input = ctr_input - output.powerup_enabled = self._int_to_bool(powerup) - return None - -class GetHeaterOutputCommand(Command): - def __init__(self, model): - super(GetHeaterOutputCommand, self).__init__(model, "HTR?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).get_heater_output()) - -class GetHeaterStatusCommand(Command): - def __init__(self, model): - super(GetHeaterStatusCommand, self).__init__(model, "HTRST?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).heater_status) - -class StartAutotuneCommand(Command): - def __init__(self, model): - super(StartAutotuneCommand, self).__init__(model, "ATUNE") - - -class Lakeshore336Emulator(object): - def __init__(self): - self._model = LakeshoreModel() - self._populate_commands() - - def _populate_commands(self): - self.commands = list() - self.commands.append(GetIdCommand(self._model)) - self.commands.append(GetInputNameCommand(self._model)) - self.commands.append(SetInputNameCommand(self._model)) - self.commands.append(GetInputTempCommand(self._model)) - self.commands.append(GetInputVoltageCommand(self._model)) - self.commands.append(GetAlarmStatusCommand(self._model)) - self.commands.append(GetAlarmSettingsCommand(self._model)) - self.commands.append(GetReadingStatusCommand(self._model)) - self.commands.append(GetInputCurveNumberCommand(self._model)) - self.commands.append(GetCurveHeaderCommand(self._model)) - self.commands.append(GetInputTypeCommand(self._model)) - self.commands.append(GetSetpointCommand(self._model)) - self.commands.append(SetSetpointCommand(self._model)) - self.commands.append(GetOutputRampCommand(self._model)) - self.commands.append(SetOutputRampCommand(self._model)) - self.commands.append(GetHeaterRangeCommand(self._model)) - self.commands.append(SetHeaterRangeCommand(self._model)) - self.commands.append(GetManualOutputCommand(self._model)) - self.commands.append(SetManualOutputCommand(self._model)) - self.commands.append(GetPIDCommand(self._model)) - self.commands.append(SetPIDCommand(self._model)) - self.commands.append(GetOutputModeCommand(self._model)) - self.commands.append(SetOutputModeCommand(self._model)) - self.commands.append(GetHeaterOutputCommand(self._model)) - self.commands.append(GetHeaterStatusCommand(self._model)) - self.commands.append(StartAutotuneCommand(self._model)) - - def process(self, data): - msg = self._reply(data) - if msg is not None: - return msg + "\r\n" - - return None - - def _reply(self, data): - command_id = data.split()[0] - try: - (command,) = [cmd for cmd in self.commands if cmd.id == command_id] - return command.execute_and_reply(data) - except ValueError: - print "***\n*** Command \"%s\" not supported! ***\n***" % (data) - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = Lakeshore336Emulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/linkam95_emulator/linkam95_emulator.py b/linkam95_emulator/linkam95_emulator.py deleted file mode 100644 index 964699f0..00000000 --- a/linkam95_emulator/linkam95_emulator.py +++ /dev/null @@ -1,226 +0,0 @@ -import sys, random -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -PUMP_MODE_AUTO = "AUTO" -PUMP_MODE_MANUAL = "MANUAL" - -T95_STATE_STOP = "stopped" -T95_STATE_HEAT = "heat" -T95_STATE_COOL = "cool" -T95_STATE_HOLD = "hold" - -class LinkamModel(object): - def __init__(self): - self.id = "EmulatedDevice" - self.ramp_state = T95_STATE_STOP - self.temperature_sp = 0.0 - self.ramp_rate = 1.0 - self.pump_mode = PUMP_MODE_AUTO - self.temperature = 0.0 - self.pump_speed = 0 - self.command_hold = False - - def get_status_bytes(self): - Tarray = [0x80] * 10 - - # Status byte (SB1) - Tarray[0] = { - T95_STATE_STOP: 0x01, - T95_STATE_HEAT: 0x10, - T95_STATE_COOL: 0x20, - T95_STATE_HOLD: 0x30, - }.get(self.ramp_state, 0x01) - if Tarray[0] == 0x30 and self.command_hold: - Tarray[0] = 0x50 - - # Pump status byte (PB1) - Tarray[2] = 0x80 + self.pump_speed - - # Temperature - self._update_temperature() - Tarray[6:10] = [ord(x) for x in "%04x" % (int(self.temperature * 10) & 0xFFFF)] - - return ''.join(chr(c) for c in Tarray) - - def start(self): - if self.ramp_state == T95_STATE_STOP: - self.ramp_state = T95_STATE_HOLD - self._update_state() - - def stop(self): - self.ramp_state = T95_STATE_STOP - - def heat(self): - self.unhold() - - def cool(self): - self.unhold() - - def hold(self): - self.command_hold = True - self._update_state() - - def unhold(self): - self.command_hold = False - self._update_state() - - def _update_state(self): - if self.ramp_state == T95_STATE_STOP: - return - elif self.command_hold: - self.ramp_state = T95_STATE_HOLD - elif self.temperature_sp < self.temperature: - self.ramp_state = T95_STATE_COOL - elif self.temperature_sp > self.temperature: - self.ramp_state = T95_STATE_HEAT - else: - self.ramp_state = T95_STATE_HOLD - - def _update_temperature(self): - import math - if self.ramp_state!=T95_STATE_HOLD and self.ramp_state!=T95_STATE_STOP: - self.temperature += math.copysign(1, self.temperature_sp-self.temperature)*self.ramp_rate/60.0 - if self.ramp_state == T95_STATE_COOL: - self.pump_speed = min(int(self.ramp_rate/3),30) - else: - self.pump_speed = 0 - self._update_state() - -class Command(object): - def __init__(self, model, id): - self._model = model - self.id = id - - def execute_and_reply(self, data): - return None - - def _get_set_token(self, data): - return data[2:] - - -class GetStatusCommand(Command): - def __init__(self, model): - super(GetStatusCommand, self).__init__(model, "T") - - def execute_and_reply(self, data): - return self._model.get_status_bytes() - -class SetRateCommand(Command): - def __init__(self, model): - super(SetRateCommand, self).__init__(model, "R") - - def execute_and_reply(self, data): - self._model.ramp_rate = int(self._get_set_token(data))/100.0 - return None - -class SetLimitCommand(Command): - def __init__(self, model): - super(SetLimitCommand, self).__init__(model, "L") - - def execute_and_reply(self, data): - self._model.temperature_sp = float(self._get_set_token(data))/10.0 - return None - -class StartRampControlCommand(Command): - def __init__(self, model): - super(StartRampControlCommand, self).__init__(model, "S") - - def execute_and_reply(self, data): - self._model.start() - return None - -class StopRampControlCommand(Command): - def __init__(self, model): - super(StopRampControlCommand, self).__init__(model, "E") - - def execute_and_reply(self, data): - self._model.stop() - return None - -class ForceHeatControlCommand(Command): - def __init__(self, model): - super(ForceHeatControlCommand, self).__init__(model, "H") - - def execute_and_reply(self, data): - self._model.heat() - return None - -class ForceCoolControlCommand(Command): - def __init__(self, model): - super(ForceCoolControlCommand, self).__init__(model, "C") - - def execute_and_reply(self, data): - self._model.cool() - return None - -class HoldControlCommand(Command): - def __init__(self, model): - super(HoldControlCommand, self).__init__(model, "O") - - def execute_and_reply(self, data): - self._model.hold() - return None - -class SetPumpStateCommand(Command): - def __init__(self, model): - super(SetPumpStateCommand, self).__init__(model, "P") - - def execute_and_reply(self, data): - token = self._get_set_token(data) - if token == "a": - self._model.pump_mode = PUMP_MODE_AUTO - elif token == "m": - self._model.pump_mode = PUMP_MODE_MANUAL - else: - try: - speed = ord(token) - if token in range(0,31): - self._model.pump_speed = token - except: - pass - return None - -class Linkam95Emulator(object): - def __init__(self): - self._model = LinkamModel() - self._populate_commands() - - def _populate_commands(self): - self.commands = list() - self.commands.append(SetPumpStateCommand(self._model)) - self.commands.append(HoldControlCommand(self._model)) - self.commands.append(ForceCoolControlCommand(self._model)) - self.commands.append(ForceHeatControlCommand(self._model)) - self.commands.append(StopRampControlCommand(self._model)) - self.commands.append(StartRampControlCommand(self._model)) - self.commands.append(SetLimitCommand(self._model)) - self.commands.append(SetRateCommand(self._model)) - self.commands.append(GetStatusCommand(self._model)) - - def process(self, data): - msg = self._reply(data) - if msg is None: - msg = "" - return msg + "\r" - - return None - - def _reply(self, data): - command_id = data[0] - try: - (command,) = [cmd for cmd in self.commands if cmd.id == command_id] - return command.execute_and_reply(data) - except ValueError: - print "***\n*** Command \"%s\" not supported! ***\n***" % (data) - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = Linkam95Emulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/mkspdr2000_emulator/mkspdr2000_emulator.py b/mkspdr2000_emulator/mkspdr2000_emulator.py deleted file mode 100644 index 18ffc2fd..00000000 --- a/mkspdr2000_emulator/mkspdr2000_emulator.py +++ /dev/null @@ -1,32 +0,0 @@ -import sys, random -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -class TemplateEmulator: - def __init__(self): - self.state = 0 - - def process(self, data): - if data == 'p': - return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) - - if data == 'f': - return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) - - if data == 'v': - return "Emulated device v1.0" - - if data == 'u': - return 'mBar' - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = TemplateEmulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py deleted file mode 100644 index d6e07ff2..00000000 --- a/tpg26x/Tpg26xEmulator.py +++ /dev/null @@ -1,109 +0,0 @@ -import sys -sys.path.append("..") - -from test_framework.telnet_engine import ENQ_SIGNAL, ACK_SIGNAL, NAK_SIGNAL, TelnetEngine - -"""Prefix for command sent to emulator to change the state of the emulator""" -EMULATOR_COMMAND_PREFIX = "emulator:" - - -class Tpg26xEmulator: - """ - Emulator for the TPG26x pressure reading device - """ - - def __init__(self): - """ - Constructor - :return: - """ - self.pressure2 = 0.08 - self.pressure1 = 0.3 - self.error2 = 1 - self.error1 = 0 - self.params = [] - self.enquire = None - self.units = 1 - - def process(self, data): - """ - process the incoming data and return the answer - :param data: data sent - :type data: str - :return: response - """ - try: - if data.startswith(EMULATOR_COMMAND_PREFIX): - return self._do_emulator_command(data[len(EMULATOR_COMMAND_PREFIX):]) - elif data == ENQ_SIGNAL: - return self._make_enquiry() - else: - self.enquire = None - self.params = None - tokens = data.split(',') - if len(tokens) == 0: - return None - if len(tokens) > 0: - self.enquire = tokens[0] - if len(tokens) > 1: - return self._set_value(tokens[1:]) - else: - return ACK_SIGNAL - - except (ValueError, TypeError) as ex: - print "Exception thrown during emulation: {0}".format(ex) - return NAK_SIGNAL - - def _do_emulator_command(self, command): - """ - Perform the emulator state change based on the command - :param command: command that was sent (without the prfix) - :return: response - """ - parameters = command.split() - if parameters[0] == "set:pressure1": - self.pressure1 = float(parameters[1]) - elif parameters[0] == "set:pressure2": - self.pressure2 = float(parameters[1]) - elif parameters[0] == "set:error1": - self.error1 = int(parameters[1]) - elif parameters[0] == "set:error2": - self.error2 = int(parameters[1]) - elif parameters[0] == "set:units": - self.units = int(parameters[1]) - return None - - def _make_enquiry(self): - """ - Most commands sent are in two parts a command which either sets or asks for a value and then - an enquirey that gets the response. This function deals with the response to an enquiry - :return: response - """ - if self.enquire == "PRX": - #x,sx.xxxxEsxx,y,sy.yyyyEsyy - return "{error1:1d},{pressure1:+011.4E},{error2:1d},{pressure2:011.4E}".format( - error1=self.error1, - pressure1=self.pressure1, - error2=self.error2, - pressure2=self.pressure2 - ) - elif self.enquire == "UNI": - return "{0:1d}".format(self.units) - - def _set_value(self, values): - """ - Set a value based on an instruction from the IOC - :param values: value to set - :return: response - """ - if self.enquire == "UNI": - self.units = int(values[0]) - return ACK_SIGNAL - else: - print "Error trying to set {0} which the simulator can not".format(self.enquire) - return NAK_SIGNAL - -if __name__ == "__main__": - port_file = __file__ - dev = Tpg26xEmulator() - TelnetEngine().start(dev, port_file) From 0546c332e8dedb0b60da58447d1b8991e7f2e5d0 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 20 Oct 2016 10:24:36 +0100 Subject: [PATCH 0027/1466] Ticket 1524: undone previous commit, as it was done on master instead of the branch! --- example_emulator/example_emulator.py | 26 ++ .../iris_cryo_valve_emulator.py | 36 ++ .../lakeshore336_emulator.py | 406 ++++++++++++++++++ linkam95_emulator/linkam95_emulator.py | 226 ++++++++++ mkspdr2000_emulator/mkspdr2000_emulator.py | 32 ++ tpg26x/Tpg26xEmulator.py | 109 +++++ 6 files changed, 835 insertions(+) create mode 100644 example_emulator/example_emulator.py create mode 100644 iris_cryo_valve_emulator/iris_cryo_valve_emulator.py create mode 100644 lakeshore336_emulator/lakeshore336_emulator.py create mode 100644 linkam95_emulator/linkam95_emulator.py create mode 100644 mkspdr2000_emulator/mkspdr2000_emulator.py create mode 100644 tpg26x/Tpg26xEmulator.py diff --git a/example_emulator/example_emulator.py b/example_emulator/example_emulator.py new file mode 100644 index 00000000..4fb92c1a --- /dev/null +++ b/example_emulator/example_emulator.py @@ -0,0 +1,26 @@ +import sys + +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + + +class TemplateEmulator: + def __init__(self): + self.state = 0 + + def process(self, data): + if data == 'r_state': + return 'state ' + str(int(self.state)) + + if data.startswith('w_state'): + self.state = data.split()[-1] + return 'w_state 1' + + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = TemplateEmulator() + TelnetEngine().start(dev, port_file) diff --git a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py new file mode 100644 index 00000000..27e73580 --- /dev/null +++ b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py @@ -0,0 +1,36 @@ +import sys +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class CryValveEmulator: + + CLOSED = "CLOSED" + OPEN = "OPEN" + TERMINATOR = "\r" + + def __init__(self): + self.state = CryValveEmulator.CLOSED + + def process(self, data): + if data == 'CLOSE': + self.state = CryValveEmulator.CLOSED + return CryValveEmulator.TERMINATOR + + if data == 'OPEN': + self.state = CryValveEmulator.OPEN + return CryValveEmulator.TERMINATOR + + if data == '?': + return "SOLENOID " + self.state + CryValveEmulator.TERMINATOR + + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = CryValveEmulator() + TelnetEngine().start(dev, port_file) + + + diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py new file mode 100644 index 00000000..905635b0 --- /dev/null +++ b/lakeshore336_emulator/lakeshore336_emulator.py @@ -0,0 +1,406 @@ +import sys, random +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class LakeshoreInput(object): + def __init__(self, start_temp=0.0): + self.name = "Default Name" + self._temperature = start_temp + self.raw_voltage = random.uniform(0, 1000) + self.has_high_alarm = False + self.has_low_alarm = False + self.alarm_enabled = True + self.alarm_high_setpoint = 1000.0 + self.alarm_low_setpoint = 10.0 + self.alarm_deadband = 0.5 + self.alarm_latching = False + self.alarm_audible = True + self.alarm_visible = True + self.reading_status = 0 + self.curve_number = 15 + self.sensor_type = 3 + self.autorange_on = True + self.range = 5 + self.compensation_on = True + self.units = 1 + + + def get_temperature(self): + return_value = self._temperature; + self._temperature += 0.1 + return return_value + + +class LakeshoreOutput(object): + def __init__(self, start_heater_output=0.0): + self.temp_setpoint = 100.0 + self.ramp_on = True + self.ramp_rate = 2.0 + self.heater_range = 2 + self.manual_output = 20.0 + self.pid = (1.0, 2.0, 3.0) + self.output_mode = 1 + self.control_input = 1 + self.powerup_enabled = True + self._heater_output = start_heater_output + self._heater_increment = 10.0 + self.heater_status = 2 + + def get_heater_output(self): + return_value = self._heater_output + self._heater_output += self._heater_increment + self._heater_increment *= -1 + return return_value + + +class CalibrationCurve(object): + def __init__(self): + self.name = "Emulator curve " # Must be 15 chars + self.serial = "0123456789" # Must be 10 chars + self.format = 1 + self.limit = 1000.0 + self.coeff = 2 + +class LakeshoreModel(object): + def __init__(self): + self.id = "EmulatedDevice" + self._populate_inputs() + self._populate_curves() + self._populate_outputs() + + def _populate_inputs(self): + self._inputs = dict() + self._inputs["A"] = LakeshoreInput(0.0) + self._inputs["B"] = LakeshoreInput(5.0) + self._inputs["C"] = LakeshoreInput(10.0) + self._inputs["D"] = LakeshoreInput(20.0) + + def _populate_outputs(self): + self._outputs = dict() + self._outputs["1"] = LakeshoreOutput(50.0) + self._outputs["2"] = LakeshoreOutput(80.0) + + def _populate_curves(self): + self._curves = dict() + self._curves["15"] = CalibrationCurve() + + def get_input(self, input_key): + return self._inputs[input_key] + + def get_output(self, output_key): + return self._outputs[output_key] + + def get_curve(self, curve_key): + return self._curves[curve_key] + + +class Command(object): + def __init__(self, model, id): + self._model = model + self.id = id + + def execute_and_reply(self, data): + return None + + def _get_target_input(self, data): + index = data[-1] + return self._model.get_input(index) + + def _get_target_output(self, data): + index = data[-1] + return self._model.get_output(index) + + def _get_target_curve(self, data): + index = data.split()[-1] + return self._model.get_curve(index) + + def _get_set_tokens(self, data): + return data[len(self.id + " "):].split(",") + + def _bool_to_int(self, bool): + return 1 if bool else 0 + + def _int_to_bool(self, value): + return value == 1 + + +class GetIdCommand(Command): + def __init__(self, model): + super(GetIdCommand, self).__init__(model, "*IDN?") + + def execute_and_reply(self, data): + return "LSCI,%s" % (self._model.id) + +class GetInputNameCommand(Command): + def __init__(self, model): + super(GetInputNameCommand, self).__init__(model, "INNAME?") + + def execute_and_reply(self, data): + return self._get_target_input(data).name + +class SetInputNameCommand(Command): + def __init__(self, model): + super(SetInputNameCommand, self).__init__(model, "INNAME") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + new_name = tokens[1].strip("\"") + self._model.get_input(index).name = new_name + return None + +class GetInputTempCommand(Command): + def __init__(self, model): + super(GetInputTempCommand, self).__init__(model, "KRDG?") + + def execute_and_reply(self, data): + return str(self._get_target_input(data).get_temperature()) + +class GetInputVoltageCommand(Command): + def __init__(self, model): + super(GetInputVoltageCommand, self).__init__(model, "SRDG?") + + def execute_and_reply(self, data): + return str(self._get_target_input(data).raw_voltage) + +class GetAlarmStatusCommand(Command): + def __init__(self, model): + super(GetAlarmStatusCommand, self).__init__(model, "ALARMST?") + + def execute_and_reply(self, data): + input = self._get_target_input(data) + return "%d,%d" % (self._bool_to_int(input.has_high_alarm), self._bool_to_int(input.has_low_alarm)) + +class GetAlarmSettingsCommand(Command): + def __init__(self, model): + super(GetAlarmSettingsCommand, self).__init__(model, "ALARM?") + + def execute_and_reply(self, data): + input = self._get_target_input(data) + return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ + input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ + input.alarm_visible) + +class GetReadingStatusCommand(Command): + def __init__(self, model): + super(GetReadingStatusCommand, self).__init__(model, "RDGST?") + + def execute_and_reply(self, data): + return str(self._get_target_input(data).reading_status) + +class GetInputCurveNumberCommand(Command): + def __init__(self, model): + super(GetInputCurveNumberCommand, self).__init__(model, "INCRV?") + + def execute_and_reply(self, data): + return str(self._get_target_input(data).curve_number) + +class GetCurveHeaderCommand(Command): + def __init__(self, model): + super(GetCurveHeaderCommand, self).__init__(model, "CRVHDR?") + + def execute_and_reply(self, data): + curve = self._get_target_curve(data) + return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) + +class GetInputTypeCommand(Command): + def __init__(self, model): + super(GetInputTypeCommand, self).__init__(model, "INTYPE?") + + def execute_and_reply(self, data): + input = self._get_target_input(data) + return "%d,%d,%d,%d,%d" % (input.sensor_type, self._bool_to_int(input.autorange_on), input.range, \ + self._bool_to_int(input.compensation_on), input.units) + +class GetSetpointCommand(Command): + def __init__(self, model): + super(GetSetpointCommand, self).__init__(model, "SETP?") + + def execute_and_reply(self, data): + return str(self._get_target_output(data).temp_setpoint) + +class SetSetpointCommand(Command): + def __init__(self, model): + super(SetSetpointCommand, self).__init__(model, "SETP") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + new_temp = float(tokens[1]) + self._model.get_output(index).temp_setpoint = new_temp + return None + +class GetOutputRampCommand(Command): + def __init__(self, model): + super(GetOutputRampCommand, self).__init__(model, "RAMP?") + + def execute_and_reply(self, data): + output = self._get_target_output(data) + return "%d,%f" % (output.ramp_on, output.ramp_rate) + +class SetOutputRampCommand(Command): + def __init__(self, model): + super(SetOutputRampCommand, self).__init__(model, "RAMP") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + ramp_on = self._int_to_bool(int(tokens[1])) + ramp_rate = float(tokens[2]) + self._model.get_output(index).ramp_on = ramp_on + self._model.get_output(index).ramp_rate = ramp_rate + return None + +class GetHeaterRangeCommand(Command): + def __init__(self, model): + super(GetHeaterRangeCommand, self).__init__(model, "RANGE?") + + def execute_and_reply(self, data): + return str(self._get_target_output(data).heater_range) + +class SetHeaterRangeCommand(Command): + def __init__(self, model): + super(SetHeaterRangeCommand, self).__init__(model, "RANGE") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + range = int(tokens[1]) + self._model.get_output(index).heater_range = range + return None + +class GetManualOutputCommand(Command): + def __init__(self, model): + super(GetManualOutputCommand, self).__init__(model, "MOUT?") + + def execute_and_reply(self, data): + return str(self._get_target_output(data).manual_output) + +class SetManualOutputCommand(Command): + def __init__(self, model): + super(SetManualOutputCommand, self).__init__(model, "MOUT") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + man_output = float(tokens[1]) + self._model.get_output(index).manual_output = man_output + return None + +class GetPIDCommand(Command): + def __init__(self, model): + super(GetPIDCommand, self).__init__(model, "PID?") + + def execute_and_reply(self, data): + return "%f,%f,%f" % self._get_target_output(data).pid + +class SetPIDCommand(Command): + def __init__(self, model): + super(SetPIDCommand, self).__init__(model, "PID") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + self._model.get_output(index).pid = tuple([float(t) for t in tokens[1:]]) + return None + +class GetOutputModeCommand(Command): + def __init__(self, model): + super(GetOutputModeCommand, self).__init__(model, "OUTMODE?") + + def execute_and_reply(self, data): + output = self._get_target_output(data) + return "%d,%d,%d" % (output.output_mode, output.control_input, self._bool_to_int(output.powerup_enabled)) + +class SetOutputModeCommand(Command): + def __init__(self, model): + super(SetOutputModeCommand, self).__init__(model, "OUTMODE") + + def execute_and_reply(self, data): + tokens = self._get_set_tokens(data) + index = tokens[0] + output = self._get_target_output(index) + (mode, ctr_input, powerup) = [int(t) for t in tokens[1:]] + output.output_mode = mode + output.control_input = ctr_input + output.powerup_enabled = self._int_to_bool(powerup) + return None + +class GetHeaterOutputCommand(Command): + def __init__(self, model): + super(GetHeaterOutputCommand, self).__init__(model, "HTR?") + + def execute_and_reply(self, data): + return str(self._get_target_output(data).get_heater_output()) + +class GetHeaterStatusCommand(Command): + def __init__(self, model): + super(GetHeaterStatusCommand, self).__init__(model, "HTRST?") + + def execute_and_reply(self, data): + return str(self._get_target_output(data).heater_status) + +class StartAutotuneCommand(Command): + def __init__(self, model): + super(StartAutotuneCommand, self).__init__(model, "ATUNE") + + +class Lakeshore336Emulator(object): + def __init__(self): + self._model = LakeshoreModel() + self._populate_commands() + + def _populate_commands(self): + self.commands = list() + self.commands.append(GetIdCommand(self._model)) + self.commands.append(GetInputNameCommand(self._model)) + self.commands.append(SetInputNameCommand(self._model)) + self.commands.append(GetInputTempCommand(self._model)) + self.commands.append(GetInputVoltageCommand(self._model)) + self.commands.append(GetAlarmStatusCommand(self._model)) + self.commands.append(GetAlarmSettingsCommand(self._model)) + self.commands.append(GetReadingStatusCommand(self._model)) + self.commands.append(GetInputCurveNumberCommand(self._model)) + self.commands.append(GetCurveHeaderCommand(self._model)) + self.commands.append(GetInputTypeCommand(self._model)) + self.commands.append(GetSetpointCommand(self._model)) + self.commands.append(SetSetpointCommand(self._model)) + self.commands.append(GetOutputRampCommand(self._model)) + self.commands.append(SetOutputRampCommand(self._model)) + self.commands.append(GetHeaterRangeCommand(self._model)) + self.commands.append(SetHeaterRangeCommand(self._model)) + self.commands.append(GetManualOutputCommand(self._model)) + self.commands.append(SetManualOutputCommand(self._model)) + self.commands.append(GetPIDCommand(self._model)) + self.commands.append(SetPIDCommand(self._model)) + self.commands.append(GetOutputModeCommand(self._model)) + self.commands.append(SetOutputModeCommand(self._model)) + self.commands.append(GetHeaterOutputCommand(self._model)) + self.commands.append(GetHeaterStatusCommand(self._model)) + self.commands.append(StartAutotuneCommand(self._model)) + + def process(self, data): + msg = self._reply(data) + if msg is not None: + return msg + "\r\n" + + return None + + def _reply(self, data): + command_id = data.split()[0] + try: + (command,) = [cmd for cmd in self.commands if cmd.id == command_id] + return command.execute_and_reply(data) + except ValueError: + print "***\n*** Command \"%s\" not supported! ***\n***" % (data) + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = Lakeshore336Emulator() + TelnetEngine().start(dev, port_file) + + + diff --git a/linkam95_emulator/linkam95_emulator.py b/linkam95_emulator/linkam95_emulator.py new file mode 100644 index 00000000..964699f0 --- /dev/null +++ b/linkam95_emulator/linkam95_emulator.py @@ -0,0 +1,226 @@ +import sys, random +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +PUMP_MODE_AUTO = "AUTO" +PUMP_MODE_MANUAL = "MANUAL" + +T95_STATE_STOP = "stopped" +T95_STATE_HEAT = "heat" +T95_STATE_COOL = "cool" +T95_STATE_HOLD = "hold" + +class LinkamModel(object): + def __init__(self): + self.id = "EmulatedDevice" + self.ramp_state = T95_STATE_STOP + self.temperature_sp = 0.0 + self.ramp_rate = 1.0 + self.pump_mode = PUMP_MODE_AUTO + self.temperature = 0.0 + self.pump_speed = 0 + self.command_hold = False + + def get_status_bytes(self): + Tarray = [0x80] * 10 + + # Status byte (SB1) + Tarray[0] = { + T95_STATE_STOP: 0x01, + T95_STATE_HEAT: 0x10, + T95_STATE_COOL: 0x20, + T95_STATE_HOLD: 0x30, + }.get(self.ramp_state, 0x01) + if Tarray[0] == 0x30 and self.command_hold: + Tarray[0] = 0x50 + + # Pump status byte (PB1) + Tarray[2] = 0x80 + self.pump_speed + + # Temperature + self._update_temperature() + Tarray[6:10] = [ord(x) for x in "%04x" % (int(self.temperature * 10) & 0xFFFF)] + + return ''.join(chr(c) for c in Tarray) + + def start(self): + if self.ramp_state == T95_STATE_STOP: + self.ramp_state = T95_STATE_HOLD + self._update_state() + + def stop(self): + self.ramp_state = T95_STATE_STOP + + def heat(self): + self.unhold() + + def cool(self): + self.unhold() + + def hold(self): + self.command_hold = True + self._update_state() + + def unhold(self): + self.command_hold = False + self._update_state() + + def _update_state(self): + if self.ramp_state == T95_STATE_STOP: + return + elif self.command_hold: + self.ramp_state = T95_STATE_HOLD + elif self.temperature_sp < self.temperature: + self.ramp_state = T95_STATE_COOL + elif self.temperature_sp > self.temperature: + self.ramp_state = T95_STATE_HEAT + else: + self.ramp_state = T95_STATE_HOLD + + def _update_temperature(self): + import math + if self.ramp_state!=T95_STATE_HOLD and self.ramp_state!=T95_STATE_STOP: + self.temperature += math.copysign(1, self.temperature_sp-self.temperature)*self.ramp_rate/60.0 + if self.ramp_state == T95_STATE_COOL: + self.pump_speed = min(int(self.ramp_rate/3),30) + else: + self.pump_speed = 0 + self._update_state() + +class Command(object): + def __init__(self, model, id): + self._model = model + self.id = id + + def execute_and_reply(self, data): + return None + + def _get_set_token(self, data): + return data[2:] + + +class GetStatusCommand(Command): + def __init__(self, model): + super(GetStatusCommand, self).__init__(model, "T") + + def execute_and_reply(self, data): + return self._model.get_status_bytes() + +class SetRateCommand(Command): + def __init__(self, model): + super(SetRateCommand, self).__init__(model, "R") + + def execute_and_reply(self, data): + self._model.ramp_rate = int(self._get_set_token(data))/100.0 + return None + +class SetLimitCommand(Command): + def __init__(self, model): + super(SetLimitCommand, self).__init__(model, "L") + + def execute_and_reply(self, data): + self._model.temperature_sp = float(self._get_set_token(data))/10.0 + return None + +class StartRampControlCommand(Command): + def __init__(self, model): + super(StartRampControlCommand, self).__init__(model, "S") + + def execute_and_reply(self, data): + self._model.start() + return None + +class StopRampControlCommand(Command): + def __init__(self, model): + super(StopRampControlCommand, self).__init__(model, "E") + + def execute_and_reply(self, data): + self._model.stop() + return None + +class ForceHeatControlCommand(Command): + def __init__(self, model): + super(ForceHeatControlCommand, self).__init__(model, "H") + + def execute_and_reply(self, data): + self._model.heat() + return None + +class ForceCoolControlCommand(Command): + def __init__(self, model): + super(ForceCoolControlCommand, self).__init__(model, "C") + + def execute_and_reply(self, data): + self._model.cool() + return None + +class HoldControlCommand(Command): + def __init__(self, model): + super(HoldControlCommand, self).__init__(model, "O") + + def execute_and_reply(self, data): + self._model.hold() + return None + +class SetPumpStateCommand(Command): + def __init__(self, model): + super(SetPumpStateCommand, self).__init__(model, "P") + + def execute_and_reply(self, data): + token = self._get_set_token(data) + if token == "a": + self._model.pump_mode = PUMP_MODE_AUTO + elif token == "m": + self._model.pump_mode = PUMP_MODE_MANUAL + else: + try: + speed = ord(token) + if token in range(0,31): + self._model.pump_speed = token + except: + pass + return None + +class Linkam95Emulator(object): + def __init__(self): + self._model = LinkamModel() + self._populate_commands() + + def _populate_commands(self): + self.commands = list() + self.commands.append(SetPumpStateCommand(self._model)) + self.commands.append(HoldControlCommand(self._model)) + self.commands.append(ForceCoolControlCommand(self._model)) + self.commands.append(ForceHeatControlCommand(self._model)) + self.commands.append(StopRampControlCommand(self._model)) + self.commands.append(StartRampControlCommand(self._model)) + self.commands.append(SetLimitCommand(self._model)) + self.commands.append(SetRateCommand(self._model)) + self.commands.append(GetStatusCommand(self._model)) + + def process(self, data): + msg = self._reply(data) + if msg is None: + msg = "" + return msg + "\r" + + return None + + def _reply(self, data): + command_id = data[0] + try: + (command,) = [cmd for cmd in self.commands if cmd.id == command_id] + return command.execute_and_reply(data) + except ValueError: + print "***\n*** Command \"%s\" not supported! ***\n***" % (data) + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = Linkam95Emulator() + TelnetEngine().start(dev, port_file) + + + diff --git a/mkspdr2000_emulator/mkspdr2000_emulator.py b/mkspdr2000_emulator/mkspdr2000_emulator.py new file mode 100644 index 00000000..18ffc2fd --- /dev/null +++ b/mkspdr2000_emulator/mkspdr2000_emulator.py @@ -0,0 +1,32 @@ +import sys, random +sys.path.append("../test_framework") + +from telnet_engine import TelnetEngine + +class TemplateEmulator: + def __init__(self): + self.state = 0 + + def process(self, data): + if data == 'p': + return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) + + if data == 'f': + return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) + + if data == 'v': + return "Emulated device v1.0" + + if data == 'u': + return 'mBar' + + return None + + +if __name__ == "__main__": + port_file = __file__ + dev = TemplateEmulator() + TelnetEngine().start(dev, port_file) + + + diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py new file mode 100644 index 00000000..d6e07ff2 --- /dev/null +++ b/tpg26x/Tpg26xEmulator.py @@ -0,0 +1,109 @@ +import sys +sys.path.append("..") + +from test_framework.telnet_engine import ENQ_SIGNAL, ACK_SIGNAL, NAK_SIGNAL, TelnetEngine + +"""Prefix for command sent to emulator to change the state of the emulator""" +EMULATOR_COMMAND_PREFIX = "emulator:" + + +class Tpg26xEmulator: + """ + Emulator for the TPG26x pressure reading device + """ + + def __init__(self): + """ + Constructor + :return: + """ + self.pressure2 = 0.08 + self.pressure1 = 0.3 + self.error2 = 1 + self.error1 = 0 + self.params = [] + self.enquire = None + self.units = 1 + + def process(self, data): + """ + process the incoming data and return the answer + :param data: data sent + :type data: str + :return: response + """ + try: + if data.startswith(EMULATOR_COMMAND_PREFIX): + return self._do_emulator_command(data[len(EMULATOR_COMMAND_PREFIX):]) + elif data == ENQ_SIGNAL: + return self._make_enquiry() + else: + self.enquire = None + self.params = None + tokens = data.split(',') + if len(tokens) == 0: + return None + if len(tokens) > 0: + self.enquire = tokens[0] + if len(tokens) > 1: + return self._set_value(tokens[1:]) + else: + return ACK_SIGNAL + + except (ValueError, TypeError) as ex: + print "Exception thrown during emulation: {0}".format(ex) + return NAK_SIGNAL + + def _do_emulator_command(self, command): + """ + Perform the emulator state change based on the command + :param command: command that was sent (without the prfix) + :return: response + """ + parameters = command.split() + if parameters[0] == "set:pressure1": + self.pressure1 = float(parameters[1]) + elif parameters[0] == "set:pressure2": + self.pressure2 = float(parameters[1]) + elif parameters[0] == "set:error1": + self.error1 = int(parameters[1]) + elif parameters[0] == "set:error2": + self.error2 = int(parameters[1]) + elif parameters[0] == "set:units": + self.units = int(parameters[1]) + return None + + def _make_enquiry(self): + """ + Most commands sent are in two parts a command which either sets or asks for a value and then + an enquirey that gets the response. This function deals with the response to an enquiry + :return: response + """ + if self.enquire == "PRX": + #x,sx.xxxxEsxx,y,sy.yyyyEsyy + return "{error1:1d},{pressure1:+011.4E},{error2:1d},{pressure2:011.4E}".format( + error1=self.error1, + pressure1=self.pressure1, + error2=self.error2, + pressure2=self.pressure2 + ) + elif self.enquire == "UNI": + return "{0:1d}".format(self.units) + + def _set_value(self, values): + """ + Set a value based on an instruction from the IOC + :param values: value to set + :return: response + """ + if self.enquire == "UNI": + self.units = int(values[0]) + return ACK_SIGNAL + else: + print "Error trying to set {0} which the simulator can not".format(self.enquire) + return NAK_SIGNAL + +if __name__ == "__main__": + port_file = __file__ + dev = Tpg26xEmulator() + TelnetEngine().start(dev, port_file) From 732ea8719bb0b710a9cc308c721b4cb248e139e3 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 20 Oct 2016 10:26:34 +0100 Subject: [PATCH 0028/1466] Ticket 1524: moved clf-type emulators into own folder - this time in the correct branch! --- example_emulator/example_emulator.py | 26 -- .../iris_cryo_valve_emulator.py | 36 -- .../lakeshore336_emulator.py | 406 ------------------ linkam95_emulator/linkam95_emulator.py | 226 ---------- mkspdr2000_emulator/mkspdr2000_emulator.py | 32 -- tpg26x/Tpg26xEmulator.py | 109 ----- 6 files changed, 835 deletions(-) delete mode 100644 example_emulator/example_emulator.py delete mode 100644 iris_cryo_valve_emulator/iris_cryo_valve_emulator.py delete mode 100644 lakeshore336_emulator/lakeshore336_emulator.py delete mode 100644 linkam95_emulator/linkam95_emulator.py delete mode 100644 mkspdr2000_emulator/mkspdr2000_emulator.py delete mode 100644 tpg26x/Tpg26xEmulator.py diff --git a/example_emulator/example_emulator.py b/example_emulator/example_emulator.py deleted file mode 100644 index 4fb92c1a..00000000 --- a/example_emulator/example_emulator.py +++ /dev/null @@ -1,26 +0,0 @@ -import sys - -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - - -class TemplateEmulator: - def __init__(self): - self.state = 0 - - def process(self, data): - if data == 'r_state': - return 'state ' + str(int(self.state)) - - if data.startswith('w_state'): - self.state = data.split()[-1] - return 'w_state 1' - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = TemplateEmulator() - TelnetEngine().start(dev, port_file) diff --git a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py b/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py deleted file mode 100644 index 27e73580..00000000 --- a/iris_cryo_valve_emulator/iris_cryo_valve_emulator.py +++ /dev/null @@ -1,36 +0,0 @@ -import sys -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -class CryValveEmulator: - - CLOSED = "CLOSED" - OPEN = "OPEN" - TERMINATOR = "\r" - - def __init__(self): - self.state = CryValveEmulator.CLOSED - - def process(self, data): - if data == 'CLOSE': - self.state = CryValveEmulator.CLOSED - return CryValveEmulator.TERMINATOR - - if data == 'OPEN': - self.state = CryValveEmulator.OPEN - return CryValveEmulator.TERMINATOR - - if data == '?': - return "SOLENOID " + self.state + CryValveEmulator.TERMINATOR - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = CryValveEmulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/lakeshore336_emulator/lakeshore336_emulator.py b/lakeshore336_emulator/lakeshore336_emulator.py deleted file mode 100644 index 905635b0..00000000 --- a/lakeshore336_emulator/lakeshore336_emulator.py +++ /dev/null @@ -1,406 +0,0 @@ -import sys, random -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -class LakeshoreInput(object): - def __init__(self, start_temp=0.0): - self.name = "Default Name" - self._temperature = start_temp - self.raw_voltage = random.uniform(0, 1000) - self.has_high_alarm = False - self.has_low_alarm = False - self.alarm_enabled = True - self.alarm_high_setpoint = 1000.0 - self.alarm_low_setpoint = 10.0 - self.alarm_deadband = 0.5 - self.alarm_latching = False - self.alarm_audible = True - self.alarm_visible = True - self.reading_status = 0 - self.curve_number = 15 - self.sensor_type = 3 - self.autorange_on = True - self.range = 5 - self.compensation_on = True - self.units = 1 - - - def get_temperature(self): - return_value = self._temperature; - self._temperature += 0.1 - return return_value - - -class LakeshoreOutput(object): - def __init__(self, start_heater_output=0.0): - self.temp_setpoint = 100.0 - self.ramp_on = True - self.ramp_rate = 2.0 - self.heater_range = 2 - self.manual_output = 20.0 - self.pid = (1.0, 2.0, 3.0) - self.output_mode = 1 - self.control_input = 1 - self.powerup_enabled = True - self._heater_output = start_heater_output - self._heater_increment = 10.0 - self.heater_status = 2 - - def get_heater_output(self): - return_value = self._heater_output - self._heater_output += self._heater_increment - self._heater_increment *= -1 - return return_value - - -class CalibrationCurve(object): - def __init__(self): - self.name = "Emulator curve " # Must be 15 chars - self.serial = "0123456789" # Must be 10 chars - self.format = 1 - self.limit = 1000.0 - self.coeff = 2 - -class LakeshoreModel(object): - def __init__(self): - self.id = "EmulatedDevice" - self._populate_inputs() - self._populate_curves() - self._populate_outputs() - - def _populate_inputs(self): - self._inputs = dict() - self._inputs["A"] = LakeshoreInput(0.0) - self._inputs["B"] = LakeshoreInput(5.0) - self._inputs["C"] = LakeshoreInput(10.0) - self._inputs["D"] = LakeshoreInput(20.0) - - def _populate_outputs(self): - self._outputs = dict() - self._outputs["1"] = LakeshoreOutput(50.0) - self._outputs["2"] = LakeshoreOutput(80.0) - - def _populate_curves(self): - self._curves = dict() - self._curves["15"] = CalibrationCurve() - - def get_input(self, input_key): - return self._inputs[input_key] - - def get_output(self, output_key): - return self._outputs[output_key] - - def get_curve(self, curve_key): - return self._curves[curve_key] - - -class Command(object): - def __init__(self, model, id): - self._model = model - self.id = id - - def execute_and_reply(self, data): - return None - - def _get_target_input(self, data): - index = data[-1] - return self._model.get_input(index) - - def _get_target_output(self, data): - index = data[-1] - return self._model.get_output(index) - - def _get_target_curve(self, data): - index = data.split()[-1] - return self._model.get_curve(index) - - def _get_set_tokens(self, data): - return data[len(self.id + " "):].split(",") - - def _bool_to_int(self, bool): - return 1 if bool else 0 - - def _int_to_bool(self, value): - return value == 1 - - -class GetIdCommand(Command): - def __init__(self, model): - super(GetIdCommand, self).__init__(model, "*IDN?") - - def execute_and_reply(self, data): - return "LSCI,%s" % (self._model.id) - -class GetInputNameCommand(Command): - def __init__(self, model): - super(GetInputNameCommand, self).__init__(model, "INNAME?") - - def execute_and_reply(self, data): - return self._get_target_input(data).name - -class SetInputNameCommand(Command): - def __init__(self, model): - super(SetInputNameCommand, self).__init__(model, "INNAME") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - new_name = tokens[1].strip("\"") - self._model.get_input(index).name = new_name - return None - -class GetInputTempCommand(Command): - def __init__(self, model): - super(GetInputTempCommand, self).__init__(model, "KRDG?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).get_temperature()) - -class GetInputVoltageCommand(Command): - def __init__(self, model): - super(GetInputVoltageCommand, self).__init__(model, "SRDG?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).raw_voltage) - -class GetAlarmStatusCommand(Command): - def __init__(self, model): - super(GetAlarmStatusCommand, self).__init__(model, "ALARMST?") - - def execute_and_reply(self, data): - input = self._get_target_input(data) - return "%d,%d" % (self._bool_to_int(input.has_high_alarm), self._bool_to_int(input.has_low_alarm)) - -class GetAlarmSettingsCommand(Command): - def __init__(self, model): - super(GetAlarmSettingsCommand, self).__init__(model, "ALARM?") - - def execute_and_reply(self, data): - input = self._get_target_input(data) - return "%d,%f,%f,%f,%d,%d,%d" % (input.alarm_enabled, input.alarm_high_setpoint, input.alarm_low_setpoint, \ - input.alarm_deadband, input.alarm_latching, input.alarm_audible, \ - input.alarm_visible) - -class GetReadingStatusCommand(Command): - def __init__(self, model): - super(GetReadingStatusCommand, self).__init__(model, "RDGST?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).reading_status) - -class GetInputCurveNumberCommand(Command): - def __init__(self, model): - super(GetInputCurveNumberCommand, self).__init__(model, "INCRV?") - - def execute_and_reply(self, data): - return str(self._get_target_input(data).curve_number) - -class GetCurveHeaderCommand(Command): - def __init__(self, model): - super(GetCurveHeaderCommand, self).__init__(model, "CRVHDR?") - - def execute_and_reply(self, data): - curve = self._get_target_curve(data) - return "%s,%s,%d,%f,%d" % (curve.name, curve.serial, curve.format, curve.limit, curve.coeff) - -class GetInputTypeCommand(Command): - def __init__(self, model): - super(GetInputTypeCommand, self).__init__(model, "INTYPE?") - - def execute_and_reply(self, data): - input = self._get_target_input(data) - return "%d,%d,%d,%d,%d" % (input.sensor_type, self._bool_to_int(input.autorange_on), input.range, \ - self._bool_to_int(input.compensation_on), input.units) - -class GetSetpointCommand(Command): - def __init__(self, model): - super(GetSetpointCommand, self).__init__(model, "SETP?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).temp_setpoint) - -class SetSetpointCommand(Command): - def __init__(self, model): - super(SetSetpointCommand, self).__init__(model, "SETP") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - new_temp = float(tokens[1]) - self._model.get_output(index).temp_setpoint = new_temp - return None - -class GetOutputRampCommand(Command): - def __init__(self, model): - super(GetOutputRampCommand, self).__init__(model, "RAMP?") - - def execute_and_reply(self, data): - output = self._get_target_output(data) - return "%d,%f" % (output.ramp_on, output.ramp_rate) - -class SetOutputRampCommand(Command): - def __init__(self, model): - super(SetOutputRampCommand, self).__init__(model, "RAMP") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - ramp_on = self._int_to_bool(int(tokens[1])) - ramp_rate = float(tokens[2]) - self._model.get_output(index).ramp_on = ramp_on - self._model.get_output(index).ramp_rate = ramp_rate - return None - -class GetHeaterRangeCommand(Command): - def __init__(self, model): - super(GetHeaterRangeCommand, self).__init__(model, "RANGE?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).heater_range) - -class SetHeaterRangeCommand(Command): - def __init__(self, model): - super(SetHeaterRangeCommand, self).__init__(model, "RANGE") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - range = int(tokens[1]) - self._model.get_output(index).heater_range = range - return None - -class GetManualOutputCommand(Command): - def __init__(self, model): - super(GetManualOutputCommand, self).__init__(model, "MOUT?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).manual_output) - -class SetManualOutputCommand(Command): - def __init__(self, model): - super(SetManualOutputCommand, self).__init__(model, "MOUT") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - man_output = float(tokens[1]) - self._model.get_output(index).manual_output = man_output - return None - -class GetPIDCommand(Command): - def __init__(self, model): - super(GetPIDCommand, self).__init__(model, "PID?") - - def execute_and_reply(self, data): - return "%f,%f,%f" % self._get_target_output(data).pid - -class SetPIDCommand(Command): - def __init__(self, model): - super(SetPIDCommand, self).__init__(model, "PID") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - self._model.get_output(index).pid = tuple([float(t) for t in tokens[1:]]) - return None - -class GetOutputModeCommand(Command): - def __init__(self, model): - super(GetOutputModeCommand, self).__init__(model, "OUTMODE?") - - def execute_and_reply(self, data): - output = self._get_target_output(data) - return "%d,%d,%d" % (output.output_mode, output.control_input, self._bool_to_int(output.powerup_enabled)) - -class SetOutputModeCommand(Command): - def __init__(self, model): - super(SetOutputModeCommand, self).__init__(model, "OUTMODE") - - def execute_and_reply(self, data): - tokens = self._get_set_tokens(data) - index = tokens[0] - output = self._get_target_output(index) - (mode, ctr_input, powerup) = [int(t) for t in tokens[1:]] - output.output_mode = mode - output.control_input = ctr_input - output.powerup_enabled = self._int_to_bool(powerup) - return None - -class GetHeaterOutputCommand(Command): - def __init__(self, model): - super(GetHeaterOutputCommand, self).__init__(model, "HTR?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).get_heater_output()) - -class GetHeaterStatusCommand(Command): - def __init__(self, model): - super(GetHeaterStatusCommand, self).__init__(model, "HTRST?") - - def execute_and_reply(self, data): - return str(self._get_target_output(data).heater_status) - -class StartAutotuneCommand(Command): - def __init__(self, model): - super(StartAutotuneCommand, self).__init__(model, "ATUNE") - - -class Lakeshore336Emulator(object): - def __init__(self): - self._model = LakeshoreModel() - self._populate_commands() - - def _populate_commands(self): - self.commands = list() - self.commands.append(GetIdCommand(self._model)) - self.commands.append(GetInputNameCommand(self._model)) - self.commands.append(SetInputNameCommand(self._model)) - self.commands.append(GetInputTempCommand(self._model)) - self.commands.append(GetInputVoltageCommand(self._model)) - self.commands.append(GetAlarmStatusCommand(self._model)) - self.commands.append(GetAlarmSettingsCommand(self._model)) - self.commands.append(GetReadingStatusCommand(self._model)) - self.commands.append(GetInputCurveNumberCommand(self._model)) - self.commands.append(GetCurveHeaderCommand(self._model)) - self.commands.append(GetInputTypeCommand(self._model)) - self.commands.append(GetSetpointCommand(self._model)) - self.commands.append(SetSetpointCommand(self._model)) - self.commands.append(GetOutputRampCommand(self._model)) - self.commands.append(SetOutputRampCommand(self._model)) - self.commands.append(GetHeaterRangeCommand(self._model)) - self.commands.append(SetHeaterRangeCommand(self._model)) - self.commands.append(GetManualOutputCommand(self._model)) - self.commands.append(SetManualOutputCommand(self._model)) - self.commands.append(GetPIDCommand(self._model)) - self.commands.append(SetPIDCommand(self._model)) - self.commands.append(GetOutputModeCommand(self._model)) - self.commands.append(SetOutputModeCommand(self._model)) - self.commands.append(GetHeaterOutputCommand(self._model)) - self.commands.append(GetHeaterStatusCommand(self._model)) - self.commands.append(StartAutotuneCommand(self._model)) - - def process(self, data): - msg = self._reply(data) - if msg is not None: - return msg + "\r\n" - - return None - - def _reply(self, data): - command_id = data.split()[0] - try: - (command,) = [cmd for cmd in self.commands if cmd.id == command_id] - return command.execute_and_reply(data) - except ValueError: - print "***\n*** Command \"%s\" not supported! ***\n***" % (data) - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = Lakeshore336Emulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/linkam95_emulator/linkam95_emulator.py b/linkam95_emulator/linkam95_emulator.py deleted file mode 100644 index 964699f0..00000000 --- a/linkam95_emulator/linkam95_emulator.py +++ /dev/null @@ -1,226 +0,0 @@ -import sys, random -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -PUMP_MODE_AUTO = "AUTO" -PUMP_MODE_MANUAL = "MANUAL" - -T95_STATE_STOP = "stopped" -T95_STATE_HEAT = "heat" -T95_STATE_COOL = "cool" -T95_STATE_HOLD = "hold" - -class LinkamModel(object): - def __init__(self): - self.id = "EmulatedDevice" - self.ramp_state = T95_STATE_STOP - self.temperature_sp = 0.0 - self.ramp_rate = 1.0 - self.pump_mode = PUMP_MODE_AUTO - self.temperature = 0.0 - self.pump_speed = 0 - self.command_hold = False - - def get_status_bytes(self): - Tarray = [0x80] * 10 - - # Status byte (SB1) - Tarray[0] = { - T95_STATE_STOP: 0x01, - T95_STATE_HEAT: 0x10, - T95_STATE_COOL: 0x20, - T95_STATE_HOLD: 0x30, - }.get(self.ramp_state, 0x01) - if Tarray[0] == 0x30 and self.command_hold: - Tarray[0] = 0x50 - - # Pump status byte (PB1) - Tarray[2] = 0x80 + self.pump_speed - - # Temperature - self._update_temperature() - Tarray[6:10] = [ord(x) for x in "%04x" % (int(self.temperature * 10) & 0xFFFF)] - - return ''.join(chr(c) for c in Tarray) - - def start(self): - if self.ramp_state == T95_STATE_STOP: - self.ramp_state = T95_STATE_HOLD - self._update_state() - - def stop(self): - self.ramp_state = T95_STATE_STOP - - def heat(self): - self.unhold() - - def cool(self): - self.unhold() - - def hold(self): - self.command_hold = True - self._update_state() - - def unhold(self): - self.command_hold = False - self._update_state() - - def _update_state(self): - if self.ramp_state == T95_STATE_STOP: - return - elif self.command_hold: - self.ramp_state = T95_STATE_HOLD - elif self.temperature_sp < self.temperature: - self.ramp_state = T95_STATE_COOL - elif self.temperature_sp > self.temperature: - self.ramp_state = T95_STATE_HEAT - else: - self.ramp_state = T95_STATE_HOLD - - def _update_temperature(self): - import math - if self.ramp_state!=T95_STATE_HOLD and self.ramp_state!=T95_STATE_STOP: - self.temperature += math.copysign(1, self.temperature_sp-self.temperature)*self.ramp_rate/60.0 - if self.ramp_state == T95_STATE_COOL: - self.pump_speed = min(int(self.ramp_rate/3),30) - else: - self.pump_speed = 0 - self._update_state() - -class Command(object): - def __init__(self, model, id): - self._model = model - self.id = id - - def execute_and_reply(self, data): - return None - - def _get_set_token(self, data): - return data[2:] - - -class GetStatusCommand(Command): - def __init__(self, model): - super(GetStatusCommand, self).__init__(model, "T") - - def execute_and_reply(self, data): - return self._model.get_status_bytes() - -class SetRateCommand(Command): - def __init__(self, model): - super(SetRateCommand, self).__init__(model, "R") - - def execute_and_reply(self, data): - self._model.ramp_rate = int(self._get_set_token(data))/100.0 - return None - -class SetLimitCommand(Command): - def __init__(self, model): - super(SetLimitCommand, self).__init__(model, "L") - - def execute_and_reply(self, data): - self._model.temperature_sp = float(self._get_set_token(data))/10.0 - return None - -class StartRampControlCommand(Command): - def __init__(self, model): - super(StartRampControlCommand, self).__init__(model, "S") - - def execute_and_reply(self, data): - self._model.start() - return None - -class StopRampControlCommand(Command): - def __init__(self, model): - super(StopRampControlCommand, self).__init__(model, "E") - - def execute_and_reply(self, data): - self._model.stop() - return None - -class ForceHeatControlCommand(Command): - def __init__(self, model): - super(ForceHeatControlCommand, self).__init__(model, "H") - - def execute_and_reply(self, data): - self._model.heat() - return None - -class ForceCoolControlCommand(Command): - def __init__(self, model): - super(ForceCoolControlCommand, self).__init__(model, "C") - - def execute_and_reply(self, data): - self._model.cool() - return None - -class HoldControlCommand(Command): - def __init__(self, model): - super(HoldControlCommand, self).__init__(model, "O") - - def execute_and_reply(self, data): - self._model.hold() - return None - -class SetPumpStateCommand(Command): - def __init__(self, model): - super(SetPumpStateCommand, self).__init__(model, "P") - - def execute_and_reply(self, data): - token = self._get_set_token(data) - if token == "a": - self._model.pump_mode = PUMP_MODE_AUTO - elif token == "m": - self._model.pump_mode = PUMP_MODE_MANUAL - else: - try: - speed = ord(token) - if token in range(0,31): - self._model.pump_speed = token - except: - pass - return None - -class Linkam95Emulator(object): - def __init__(self): - self._model = LinkamModel() - self._populate_commands() - - def _populate_commands(self): - self.commands = list() - self.commands.append(SetPumpStateCommand(self._model)) - self.commands.append(HoldControlCommand(self._model)) - self.commands.append(ForceCoolControlCommand(self._model)) - self.commands.append(ForceHeatControlCommand(self._model)) - self.commands.append(StopRampControlCommand(self._model)) - self.commands.append(StartRampControlCommand(self._model)) - self.commands.append(SetLimitCommand(self._model)) - self.commands.append(SetRateCommand(self._model)) - self.commands.append(GetStatusCommand(self._model)) - - def process(self, data): - msg = self._reply(data) - if msg is None: - msg = "" - return msg + "\r" - - return None - - def _reply(self, data): - command_id = data[0] - try: - (command,) = [cmd for cmd in self.commands if cmd.id == command_id] - return command.execute_and_reply(data) - except ValueError: - print "***\n*** Command \"%s\" not supported! ***\n***" % (data) - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = Linkam95Emulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/mkspdr2000_emulator/mkspdr2000_emulator.py b/mkspdr2000_emulator/mkspdr2000_emulator.py deleted file mode 100644 index 18ffc2fd..00000000 --- a/mkspdr2000_emulator/mkspdr2000_emulator.py +++ /dev/null @@ -1,32 +0,0 @@ -import sys, random -sys.path.append("../test_framework") - -from telnet_engine import TelnetEngine - -class TemplateEmulator: - def __init__(self): - self.state = 0 - - def process(self, data): - if data == 'p': - return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) - - if data == 'f': - return "%e %e" % (random.uniform(0,1000),random.uniform(0,1000)) - - if data == 'v': - return "Emulated device v1.0" - - if data == 'u': - return 'mBar' - - return None - - -if __name__ == "__main__": - port_file = __file__ - dev = TemplateEmulator() - TelnetEngine().start(dev, port_file) - - - diff --git a/tpg26x/Tpg26xEmulator.py b/tpg26x/Tpg26xEmulator.py deleted file mode 100644 index d6e07ff2..00000000 --- a/tpg26x/Tpg26xEmulator.py +++ /dev/null @@ -1,109 +0,0 @@ -import sys -sys.path.append("..") - -from test_framework.telnet_engine import ENQ_SIGNAL, ACK_SIGNAL, NAK_SIGNAL, TelnetEngine - -"""Prefix for command sent to emulator to change the state of the emulator""" -EMULATOR_COMMAND_PREFIX = "emulator:" - - -class Tpg26xEmulator: - """ - Emulator for the TPG26x pressure reading device - """ - - def __init__(self): - """ - Constructor - :return: - """ - self.pressure2 = 0.08 - self.pressure1 = 0.3 - self.error2 = 1 - self.error1 = 0 - self.params = [] - self.enquire = None - self.units = 1 - - def process(self, data): - """ - process the incoming data and return the answer - :param data: data sent - :type data: str - :return: response - """ - try: - if data.startswith(EMULATOR_COMMAND_PREFIX): - return self._do_emulator_command(data[len(EMULATOR_COMMAND_PREFIX):]) - elif data == ENQ_SIGNAL: - return self._make_enquiry() - else: - self.enquire = None - self.params = None - tokens = data.split(',') - if len(tokens) == 0: - return None - if len(tokens) > 0: - self.enquire = tokens[0] - if len(tokens) > 1: - return self._set_value(tokens[1:]) - else: - return ACK_SIGNAL - - except (ValueError, TypeError) as ex: - print "Exception thrown during emulation: {0}".format(ex) - return NAK_SIGNAL - - def _do_emulator_command(self, command): - """ - Perform the emulator state change based on the command - :param command: command that was sent (without the prfix) - :return: response - """ - parameters = command.split() - if parameters[0] == "set:pressure1": - self.pressure1 = float(parameters[1]) - elif parameters[0] == "set:pressure2": - self.pressure2 = float(parameters[1]) - elif parameters[0] == "set:error1": - self.error1 = int(parameters[1]) - elif parameters[0] == "set:error2": - self.error2 = int(parameters[1]) - elif parameters[0] == "set:units": - self.units = int(parameters[1]) - return None - - def _make_enquiry(self): - """ - Most commands sent are in two parts a command which either sets or asks for a value and then - an enquirey that gets the response. This function deals with the response to an enquiry - :return: response - """ - if self.enquire == "PRX": - #x,sx.xxxxEsxx,y,sy.yyyyEsyy - return "{error1:1d},{pressure1:+011.4E},{error2:1d},{pressure2:011.4E}".format( - error1=self.error1, - pressure1=self.pressure1, - error2=self.error2, - pressure2=self.pressure2 - ) - elif self.enquire == "UNI": - return "{0:1d}".format(self.units) - - def _set_value(self, values): - """ - Set a value based on an instruction from the IOC - :param values: value to set - :return: response - """ - if self.enquire == "UNI": - self.units = int(values[0]) - return ACK_SIGNAL - else: - print "Error trying to set {0} which the simulator can not".format(self.enquire) - return NAK_SIGNAL - -if __name__ == "__main__": - port_file = __file__ - dev = Tpg26xEmulator() - TelnetEngine().start(dev, port_file) From e9fbdf37529b571676e3e63a3e9f6ec2bc358163 Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Thu, 20 Oct 2016 12:20:36 +0100 Subject: [PATCH 0029/1466] Ticket 1524: added plankton emulator for iris cryo valve --- .idea/isis_emulators.iml | 12 ++++++++ plankton_emulators/__init__.py | 1 + .../iris_cryo_valve/__init__.py | 6 ++++ plankton_emulators/iris_cryo_valve/device.py | 7 +++++ .../iris_cryo_valve/interfaces/__init__.py | 1 + .../interfaces/stream_interface.py | 28 +++++++++++++++++++ 6 files changed, 55 insertions(+) create mode 100644 .idea/isis_emulators.iml create mode 100644 plankton_emulators/__init__.py create mode 100644 plankton_emulators/iris_cryo_valve/__init__.py create mode 100644 plankton_emulators/iris_cryo_valve/device.py create mode 100644 plankton_emulators/iris_cryo_valve/interfaces/__init__.py create mode 100644 plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py diff --git a/.idea/isis_emulators.iml b/.idea/isis_emulators.iml new file mode 100644 index 00000000..6f63a63c --- /dev/null +++ b/.idea/isis_emulators.iml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/plankton_emulators/__init__.py b/plankton_emulators/__init__.py new file mode 100644 index 00000000..3be6bd29 --- /dev/null +++ b/plankton_emulators/__init__.py @@ -0,0 +1 @@ +from __future__ import absolute_import \ No newline at end of file diff --git a/plankton_emulators/iris_cryo_valve/__init__.py b/plankton_emulators/iris_cryo_valve/__init__.py new file mode 100644 index 00000000..36a28162 --- /dev/null +++ b/plankton_emulators/iris_cryo_valve/__init__.py @@ -0,0 +1,6 @@ +from .device import SimulatedIrisCryoValve + + + + + diff --git a/plankton_emulators/iris_cryo_valve/device.py b/plankton_emulators/iris_cryo_valve/device.py new file mode 100644 index 00000000..8c1c0223 --- /dev/null +++ b/plankton_emulators/iris_cryo_valve/device.py @@ -0,0 +1,7 @@ +from devices import Device + +class SimulatedIrisCryoValve(Device): + is_open = False + + + diff --git a/plankton_emulators/iris_cryo_valve/interfaces/__init__.py b/plankton_emulators/iris_cryo_valve/interfaces/__init__.py new file mode 100644 index 00000000..bed47b7f --- /dev/null +++ b/plankton_emulators/iris_cryo_valve/interfaces/__init__.py @@ -0,0 +1 @@ +from .stream_interface import IrisCryoValveStreamInterface \ No newline at end of file diff --git a/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py b/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py new file mode 100644 index 00000000..32b3f3f1 --- /dev/null +++ b/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py @@ -0,0 +1,28 @@ +from adapters.stream import StreamAdapter, Cmd + +class IrisCryoValveStreamInterface(StreamAdapter): + + commands = { + Cmd("get_status", "^\?$"), + Cmd("set_open", "^OPEN$"), + Cmd("set_closed", "^CLOSE$"), + } + + in_terminator = "\r" + out_terminator = "\r" + + def get_status(self): + status = "OPEN" if self._device.is_open else "CLOSED" + return "SOLENOID " + status + + def set_open(self): + self._device.is_open = True + return "" + + def set_closed(self): + self._device.is_open = False + return "" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + From 0fb18c34a4584acecbfb56cc8b771f80d581a1af Mon Sep 17 00:00:00 2001 From: IsabellaRey Date: Fri, 21 Oct 2016 12:25:57 +0100 Subject: [PATCH 0030/1466] Removed pycharm file --- .idea/isis_emulators.iml | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .idea/isis_emulators.iml diff --git a/.idea/isis_emulators.iml b/.idea/isis_emulators.iml deleted file mode 100644 index 6f63a63c..00000000 --- a/.idea/isis_emulators.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file From 71860664bd76a8d450fa1a2b15a3b19fcf885dd7 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 24 Oct 2016 10:21:47 +0100 Subject: [PATCH 0031/1466] "Adding .project files to git ignore" --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c0ee5449..700c2cc0 100644 --- a/.gitignore +++ b/.gitignore @@ -91,3 +91,4 @@ ENV/ # Port files *.pid *.port +.project From 01e9944040aa402e48b7fad3c62deb58655c5e57 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 3 Jan 2017 14:51:52 +0000 Subject: [PATCH 0032/1466] Make cryo valve work with lewis package --- plankton_emulators/iris_cryo_valve/__init__.py | 2 ++ plankton_emulators/iris_cryo_valve/device.py | 3 ++- plankton_emulators/iris_cryo_valve/interfaces/__init__.py | 4 +++- .../iris_cryo_valve/interfaces/stream_interface.py | 3 ++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/plankton_emulators/iris_cryo_valve/__init__.py b/plankton_emulators/iris_cryo_valve/__init__.py index 36a28162..6a83a462 100644 --- a/plankton_emulators/iris_cryo_valve/__init__.py +++ b/plankton_emulators/iris_cryo_valve/__init__.py @@ -1,5 +1,7 @@ from .device import SimulatedIrisCryoValve +__all__ = ['SimulatedIrisCryoValve'] + diff --git a/plankton_emulators/iris_cryo_valve/device.py b/plankton_emulators/iris_cryo_valve/device.py index 8c1c0223..d37bd213 100644 --- a/plankton_emulators/iris_cryo_valve/device.py +++ b/plankton_emulators/iris_cryo_valve/device.py @@ -1,4 +1,5 @@ -from devices import Device +from lewis.devices import Device + class SimulatedIrisCryoValve(Device): is_open = False diff --git a/plankton_emulators/iris_cryo_valve/interfaces/__init__.py b/plankton_emulators/iris_cryo_valve/interfaces/__init__.py index bed47b7f..71f9193c 100644 --- a/plankton_emulators/iris_cryo_valve/interfaces/__init__.py +++ b/plankton_emulators/iris_cryo_valve/interfaces/__init__.py @@ -1 +1,3 @@ -from .stream_interface import IrisCryoValveStreamInterface \ No newline at end of file +from .stream_interface import IrisCryoValveStreamInterface + +__all__ = ['IrisCryoValveStreamInterface'] \ No newline at end of file diff --git a/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py b/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py index 32b3f3f1..02137798 100644 --- a/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py +++ b/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py @@ -1,4 +1,5 @@ -from adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamAdapter, Cmd + class IrisCryoValveStreamInterface(StreamAdapter): From c73d2de8cc45449d33996c614da835600a39c05d Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 3 Jan 2017 15:32:12 +0000 Subject: [PATCH 0033/1466] Add basic rig interface --- plankton_emulators/volumetric_rig/__init__.py | 8 + plankton_emulators/volumetric_rig/device.py | 8 + .../volumetric_rig/interfaces/__init__.py | 3 + .../interfaces/stream_interface.py | 152 ++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 plankton_emulators/volumetric_rig/__init__.py create mode 100644 plankton_emulators/volumetric_rig/device.py create mode 100644 plankton_emulators/volumetric_rig/interfaces/__init__.py create mode 100644 plankton_emulators/volumetric_rig/interfaces/stream_interface.py diff --git a/plankton_emulators/volumetric_rig/__init__.py b/plankton_emulators/volumetric_rig/__init__.py new file mode 100644 index 00000000..992e8767 --- /dev/null +++ b/plankton_emulators/volumetric_rig/__init__.py @@ -0,0 +1,8 @@ +from .device import SimulatedVolumetricRig + +__all__ = ['SimulatedVolumetricRig'] + + + + + diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py new file mode 100644 index 00000000..79a34300 --- /dev/null +++ b/plankton_emulators/volumetric_rig/device.py @@ -0,0 +1,8 @@ +from lewis.devices import Device + + +class SimulatedVolumetricRig(Device): + pass + + + diff --git a/plankton_emulators/volumetric_rig/interfaces/__init__.py b/plankton_emulators/volumetric_rig/interfaces/__init__.py new file mode 100644 index 00000000..db4c0f08 --- /dev/null +++ b/plankton_emulators/volumetric_rig/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import VolumetricRigStreamInterface + +__all__ = ['VolumetricRigStreamInterface'] \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py new file mode 100644 index 00000000..225c398f --- /dev/null +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -0,0 +1,152 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + +gases = ["UNKNOWN", "EMPTY", "VACUUM EXTRACT", "ARGON", "NITROGEN", "NEON", "CARBON DIOXIDE", "CARBON MONOXIDE", + "HELIUM", "GRAVY", "LIVER", "HYDROGEN", "OXYGEN", "CURRIED RAT", "FRESH COFFEE", "BACON", "ONION", "CHIPS", + "GARLIC", "BROWN SAUCE"] + + +def is_mixable(g1, g2): + if "LIVER" in {g1, g2}: + return False + elif "NITROGEN" in {g1, g2} and not {g1, g2}.isdisjoint({"EMPTY", "VACUUM EXTRACT", "ARGON", "NEON"}): + return False + else: + return True + + +class VolumetricRigStreamInterface(StreamAdapter): + + commands = { + Cmd("get_identity", "^IDN$"), + Cmd("get_identity", "^\?$"), + Cmd("get_buffer_control_and_status", "^BCS 2$"), + Cmd("get_ethernet_and_hmi_status", "^ETN$"), + Cmd("get_gas_control_and_status", "^GCS$"), + Cmd("get_gas_mix_matrix", "^GMM$"), + Cmd("gas_mix_check", "^GMC 02 05$"), + Cmd("get_gas_number_available", "^GNA$"), + Cmd("get_hmi_status", "^HMI$"), + Cmd("get_hmi_count_cycles", "^HMC$"), + Cmd("get_memory_location", "^RDM 200$"), + Cmd("get_pressure_and_temperature_status", "^PTS$"), + Cmd("get_pressures", "^PMV$"), + Cmd("get_temperatures", "^TMV$"), + Cmd("get_ports_and_relays_hex", "^PTR$"), + Cmd("get_ports_output", "^POT$"), + Cmd("get_ports_input", "^PIN$"), + Cmd("get_ports_relays", "^PRY$"), + Cmd("get_system_status", "^STS$"), + Cmd("get_com_activity", "^COM$"), + Cmd("get_valve_status", "^VST$"), + Cmd("set_valve_open", "^OPV 1$"), + Cmd("set_valve_closed", "^CLV 1$"), + Cmd("halt", "^HLT$"), + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def get_identity(self): + return "IDN,00,ISIS Volumetric Gas Handing Panel" + + def get_buffer_control_and_status(self,buffer): + return "BCS "+buffer+"2 04 NITROGEN d c 01 EMPTY" + + def get_ethernet_and_hmi_status(self): + return "ETN:PLC 192.168.100.156,HMI OK ,192.168.100.208" + + def get_gas_control_and_status(self): + lines = list() + lines.append("No No Buffer E O No System") + lines.append(" 1 03 ARGON E O 03 ARGON") + lines.append(" 2 04 NITROGEN d c 01 EMPTY") + lines.append(" 3 05 NEON E c 01 EMPTY") + lines.append(" 4 06 CARBON DIOXIDE E c 01 EMPTY") + lines.append(" 5 08 HELIUM E c 08 HELIUM") + lines.append(" 6 11 HYDROGEN E c 01 EMPTY") + lines.append("GCS") + return '\n'.join(lines) + + def get_gas_mix_matrix(self): + length_limit = 20 + padded_gases = [(g + (length_limit-len(g))*"-") for g in gases] + lines = list() + + # Column headers + for i in range(length_limit): + words = list() + words.append(' '*(length_limit+3)) + for j in range(length_limit): + words.append(padded_gases[j][i]) + lines.append(''.join(words)) + + # Rows + for i in range(length_limit): + words = list() + words.append(str(i).zfill(2)) + words.append(padded_gases[i]) + for j in range(length_limit): + words.append("<" if is_mixable(gases[i], gases[j]) else ".") + lines.append(' '.join(words)) + + # Footer + lines.append("GMM allowance limit: " + str(length_limit)) + + return '\n'.join(lines) + + def gas_mix_check(self,gas1_index,gas2_index): + return "GMC " + gas1_index + " VACUUM EXTRACT...... " + gas2_index + " NEON................ 05 " + \ + ("ok" if is_mixable(gases[gas1_index],gases[gas2_index]) else "NO") + + def get_gas_number_available(self): + return "20" + + def get_hmi_status(self): + return "HMI OK ,192.168.100.208,B,034,S,041" + + def get_hmi_count_cycles(self): + return "HMC 999 006 002 002 002 002 002 001 001 310" + + def get_memory_location(self,location): + return "RDM " + location.zfill(4) + " 000000" + + def get_pressure_and_temperature_status(self): + return "PTS ODDDDDDDDDDDDD" + + def get_pressures(self): + return "PMV 00.00 01.00 00.00 00.00 01.02 00.00" + + def get_temperatures(self): + return "TMV 25.00 00.00 00.00 00.00 00.00 00.00 00.00 00.00 00.00" + + def get_ports_and_relays_hex(self): + return "PTR I:00 0000 0000 R:0000 0200 0000 O:00 0000 4400" + + def get_ports_output(self): + return "POT qwertyus vsbbbbbbzyxwvuts aBhecSssvsbbbbbb" + + def get_ports_input(self): + return "PIN qwertyui zyxwvutsrqponmlk abcdefghijklmneb" + + def get_ports_relays(self): + return "PRY qwertyuiopasdfgh zyxwhmLsrqponmlk abcdefghihlbhace" + + def get_system_status(self): + return "STS 09 STOP hmi guages comms hlt E-STOP" + + def get_com_activity(self): + return "COM ok 0113/0000" + + def get_valve_status(self): + return "VST Valve Status DDDDDDDDD" + + def set_valve_closed(self,valve_number): + #return "CLV Rejected only allowed when running" + return "CLV Valve Buffer " + valve_number + " closed was open" + + def set_valve_open(self,valve_number): + #return "OLV Rejected only allowed when running" + return "OPV Valve Buffer " + valve_number + " opened was closed " + + def halt(self): + return "HLT *** SYSTEM NOW HALTED ***" \ No newline at end of file From fb00180b1366eec58f430ee8054f7383a9860d06 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 3 Jan 2017 16:21:58 +0000 Subject: [PATCH 0034/1466] Set up protocol with input parameters --- .../interfaces/stream_interface.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 225c398f..ae9fa63a 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -19,15 +19,15 @@ class VolumetricRigStreamInterface(StreamAdapter): commands = { Cmd("get_identity", "^IDN$"), Cmd("get_identity", "^\?$"), - Cmd("get_buffer_control_and_status", "^BCS 2$"), + Cmd("get_buffer_control_and_status", "^BCS\s([0-9]+)$"), Cmd("get_ethernet_and_hmi_status", "^ETN$"), Cmd("get_gas_control_and_status", "^GCS$"), Cmd("get_gas_mix_matrix", "^GMM$"), - Cmd("gas_mix_check", "^GMC 02 05$"), + Cmd("gas_mix_check", "^GMC\s([0-9]+)\s([0-9]+)$"), Cmd("get_gas_number_available", "^GNA$"), Cmd("get_hmi_status", "^HMI$"), Cmd("get_hmi_count_cycles", "^HMC$"), - Cmd("get_memory_location", "^RDM 200$"), + Cmd("get_memory_location", "^RDM\s([0-9]+)$"), Cmd("get_pressure_and_temperature_status", "^PTS$"), Cmd("get_pressures", "^PMV$"), Cmd("get_temperatures", "^TMV$"), @@ -38,8 +38,8 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("get_system_status", "^STS$"), Cmd("get_com_activity", "^COM$"), Cmd("get_valve_status", "^VST$"), - Cmd("set_valve_open", "^OPV 1$"), - Cmd("set_valve_closed", "^CLV 1$"), + Cmd("set_valve_open", "^OPV\s([0-9]+)$"), + Cmd("set_valve_closed", "^CLV\s([0-9]+)$"), Cmd("halt", "^HLT$"), } @@ -50,7 +50,7 @@ def get_identity(self): return "IDN,00,ISIS Volumetric Gas Handing Panel" def get_buffer_control_and_status(self,buffer): - return "BCS "+buffer+"2 04 NITROGEN d c 01 EMPTY" + return "BCS "+buffer+" 04 NITROGEN d c 01 EMPTY" def get_ethernet_and_hmi_status(self): return "ETN:PLC 192.168.100.156,HMI OK ,192.168.100.208" @@ -95,8 +95,12 @@ def get_gas_mix_matrix(self): return '\n'.join(lines) def gas_mix_check(self,gas1_index,gas2_index): - return "GMC " + gas1_index + " VACUUM EXTRACT...... " + gas2_index + " NEON................ 05 " + \ - ("ok" if is_mixable(gases[gas1_index],gases[gas2_index]) else "NO") + gas1 = gases[int(gas1_index)] + gas2 = gases[int(gas2_index)] + gas1_padded = gas1 + "."*(20-len(gas1)) + gas2_padded = gas2 + "."*(20-len(gas2)) + return "GMC " + gas1_index.zfill(2) + " " + gas1_padded + " " + gas2_index.zfill(2) + " " + \ + gas2_padded + " " + ("ok" if is_mixable(gas1,gas2) else "NO") def get_gas_number_available(self): return "20" @@ -142,11 +146,11 @@ def get_valve_status(self): def set_valve_closed(self,valve_number): #return "CLV Rejected only allowed when running" - return "CLV Valve Buffer " + valve_number + " closed was open" + return "CLV Valve Buffer " + valve_number.lstrip("0") + " closed was open" def set_valve_open(self,valve_number): #return "OLV Rejected only allowed when running" - return "OPV Valve Buffer " + valve_number + " opened was closed " + return "OPV Valve Buffer " + valve_number.lstrip("0") + " opened was closed " def halt(self): return "HLT *** SYSTEM NOW HALTED ***" \ No newline at end of file From 6d676b6520563d0c7c9b2e7a683b22e24666b9af Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 3 Jan 2017 17:04:53 +0000 Subject: [PATCH 0035/1466] Start adding in device logic --- plankton_emulators/volumetric_rig/device.py | 52 ++++++++++++++++++- plankton_emulators/volumetric_rig/gas.py | 27 ++++++++++ .../volumetric_rig/system_gases.py | 46 ++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 plankton_emulators/volumetric_rig/gas.py create mode 100644 plankton_emulators/volumetric_rig/system_gases.py diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 79a34300..8077b07d 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -1,8 +1,58 @@ from lewis.devices import Device +from system_gases import SystemGases + +# Define string literals for all gas names to avoid typos +UNKNOWN = "UNKNOWN" +EMPTY = "EMPTY" +VACUUM_EXTRACT = "VACUUM EXTRACT" +ARGON = "ARGON" +NITROGEN = "NITROGEN" +NEON = "NEON" +CARBON_DIOXIDE = "CARBON DIOXIDE" +CARBON_MONOXIDE = "CARBON MONOXIDE" +HELIUM = "HELIUM" +GRAVY = "GRAVY" +LIVER = "LIVER" +HYDROGEN = "HYDROGEN" +OXYGEN = "OXYGEN" +CURRIED_RAT = "CURRIED RAT" +FRESH_COFFEE = "FRESH COFFEE" +BACON = "BACON" +ONION = "ONION" +CHIPS = "CHIPS" +GARLIC = "GARLIC" +BROWN_SAUCE = "BROWN SAUCE" class SimulatedVolumetricRig(Device): - pass + def __init__(self): + self.system_gases = SystemGases() + # Populate system gases + gas_names = [UNKNOWN, EMPTY, VACUUM_EXTRACT, ARGON, NITROGEN, NEON, CARBON_DIOXIDE, CARBON_MONOXIDE, HELIUM, + GRAVY, LIVER, HYDROGEN, OXYGEN, CURRIED_RAT, FRESH_COFFEE, BACON, ONION, CHIPS, GARLIC, + BROWN_SAUCE] + index = 0 + for gas_name in gas_names: + self.system_gases.add_gas(index,gas_name) + index += 1 + # Set unmixable gases + for g in gas_names: + self.system_gases.set_unmixable_by_name(UNKNOWN, g) + self.system_gases.set_unmixable_by_name(LIVER, g) + if g not in {ARGON, NITROGEN, VACUUM_EXTRACT, EMPTY}: + self.system_gases.set_unmixable_by_name(NITROGEN, g) + if g not in {NEON, CARBON_DIOXIDE, CARBON_MONOXIDE}: + self.system_gases.set_unmixable_by_name(CARBON_MONOXIDE, g) + if g in {CURRIED_RAT, FRESH_COFFEE, BACON, CHIPS}: + self.system_gases.set_unmixable_by_name(GRAVY, g) + if g in {OXYGEN, ONION, BROWN_SAUCE}: + self.system_gases.set_unmixable_by_name(HYDROGEN, g) + self.system_gases.set_buffer_gas(1, ARGON) + self.system_gases.set_buffer_gas(2, NITROGEN) + self.system_gases.set_buffer_gas(3, NEON) + self.system_gases.set_buffer_gas(4, CARBON_DIOXIDE) + self.system_gases.set_buffer_gas(5, HELIUM) + self.system_gases.set_buffer_gas(6, HYDROGEN) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/gas.py b/plankton_emulators/volumetric_rig/gas.py new file mode 100644 index 00000000..2d6a42bf --- /dev/null +++ b/plankton_emulators/volumetric_rig/gas.py @@ -0,0 +1,27 @@ +from types import * + + +class Gas(object): + def __init__(self,index,name): + assert type(index) is IntType + assert type(name) is StringType + self.index = index + self.name = name + + def get_name(self): + return self.name + + def get_index(self): + return self.index + + def get_zero_padded_index(self,length=2): + return str(self.index).zfill(length) + + def get_dash_padded_name(self,length): + return self.get_padded_name(length,"-") + + def get_dot_padded_name(self,length): + return self.get_padded_name(length,".") + + def get_padded_name(self,length,char): + return self.name + char*(length-len(self.name)) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/plankton_emulators/volumetric_rig/system_gases.py new file mode 100644 index 00000000..a856af59 --- /dev/null +++ b/plankton_emulators/volumetric_rig/system_gases.py @@ -0,0 +1,46 @@ +from gas import Gas + +class SystemGases(object): + def __init__(self): + self.gases = dict() + self.unmixable_pairs = set() + self.buffer_gases = dict() + + def add_gas(self,gas): + self.gases[gas.get_index()] = gas + + def get_gas_by_index(self,index): + if index in self.gases.keys(): + return self.gases[index] + else: + return None + + def get_gas_by_name(self, name): + try: + return next(g for g in self.gases if g.get_name()==name) + except StopIteration: + return None + + def set_unmixable(self, gas1, gas2): + self.set_unmixable(gas1.get_index(), gas2.get_index()) + + def set_unmixable_by_name(self, name1, name2): + self.set_unmixable(self.get_gas_by_name(name1), self.get_gas_by_name(name2)) + + def set_unmixable_by_index(self, gas1_index, gas2_index): + self.unmixable_pairs.add({gas1_index,gas2_index}) + + def are_mixable(self, gas1, gas2): + return self.are_mixable_by_index(gas1.get_index(), gas2.get_index()) + + def are_mixable_by_index(self, gas1_index, gas2_index): + return not {gas1_index,gas2_index} in self.unmixable_pairs + + def are_mixable_by_name(self, gas1_name, gas2_name): + return self.are_mixable(self.get_gas_by_name(gas1_name), self.get_gas_by_name(gas2_name)) + + def set_buffer_gas(self,key,gas_name): + self.buffer_gases[key] = self.get_gas_by_name(gas_name) + + def add_gas(self,index,name): + self.gases[index] = Gas(index,name) \ No newline at end of file From ff806eaac681f7a1fa2039c17e3efa218bb54e8d Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 4 Jan 2017 12:13:26 +0000 Subject: [PATCH 0036/1466] First pass connecting interface and device --- plankton_emulators/volumetric_rig/device.py | 14 +- .../interfaces/stream_interface.py | 248 ++++++++++++------ 2 files changed, 187 insertions(+), 75 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 8077b07d..8a07033a 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -55,4 +55,16 @@ def __init__(self): self.system_gases.set_buffer_gas(3, NEON) self.system_gases.set_buffer_gas(4, CARBON_DIOXIDE) self.system_gases.set_buffer_gas(5, HELIUM) - self.system_gases.set_buffer_gas(6, HYDROGEN) \ No newline at end of file + self.system_gases.set_buffer_gas(6, HYDROGEN) + + def get_identity(self): + return "ISIS Volumetric Gas Handing Panel" + + def get_plc_ip(self): + return "192.168.1.100" + + def get_hmi_ip(self): + return "192.168.1.101" + + def get_hmi_status(self): + return "OK" \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index ae9fa63a..d5164ac1 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,17 +1,5 @@ from lewis.adapters.stream import StreamAdapter, Cmd - -gases = ["UNKNOWN", "EMPTY", "VACUUM EXTRACT", "ARGON", "NITROGEN", "NEON", "CARBON DIOXIDE", "CARBON MONOXIDE", - "HELIUM", "GRAVY", "LIVER", "HYDROGEN", "OXYGEN", "CURRIED RAT", "FRESH COFFEE", "BACON", "ONION", "CHIPS", - "GARLIC", "BROWN SAUCE"] - - -def is_mixable(g1, g2): - if "LIVER" in {g1, g2}: - return False - elif "NITROGEN" in {g1, g2} and not {g1, g2}.isdisjoint({"EMPTY", "VACUUM EXTRACT", "ARGON", "NEON"}): - return False - else: - return True +from ..device import SimulatedVolumetricRig class VolumetricRigStreamInterface(StreamAdapter): @@ -46,83 +34,212 @@ class VolumetricRigStreamInterface(StreamAdapter): in_terminator = "\r\n" out_terminator = "\r\n" + def __init__(self): + self.simulated_rig = SimulatedVolumetricRig() + self.gas_output_length = 20 + super(VolumetricRigStreamInterface, self).__init__() + def get_identity(self): - return "IDN,00,ISIS Volumetric Gas Handing Panel" + return "IDN,00," + self.simulated_rig.get_identity() + + def get_buffer_control_and_status(self,buffer_number_string): + number_print_length = 3 + message_prefix = "BCS Buffer " + buffer_number_string.zfill(number_print_length)[:number_print_length] + " " + buffer_too_low = message_prefix + "Too Low" + buffer_too_high = message_prefix + "Too High" + try: + buffer_number = int(buffer_number_string) + except TypeError: + # This is how the volumetric rig currently responds + return buffer_too_low + + if buffer_number <= 0: + return buffer_too_low + elif buffer_number > self.simulated_rig.number_of_buffers(): + return buffer_too_high + + buffer = self.simulated_rig.get_buffer(buffer_number) + return " ".join([ + message_prefix[:-1], + buffer.get_buffer_gas().get_index_string(), + buffer.get_buffer_gas().get_name(length=self.gas_output_length, padding_character=" "), + "E" if buffer.get_valve().enabled() else "d", + "O" if buffer.get_valve().is_open() else "c", + buffer.get_system_gas().get_index_string(), + buffer.get_system_gas().get_name() + ]) - def get_buffer_control_and_status(self,buffer): - return "BCS "+buffer+" 04 NITROGEN d c 01 EMPTY" def get_ethernet_and_hmi_status(self): - return "ETN:PLC 192.168.100.156,HMI OK ,192.168.100.208" + return "ETN:PLC " + self.simulated_rig.get_plc_ip() + ",HMI " + self.simulated_rig.get_hmi_status() + " ," + \ + self.simulated_rig.get_hmi_ip() def get_gas_control_and_status(self): lines = list() lines.append("No No Buffer E O No System") - lines.append(" 1 03 ARGON E O 03 ARGON") - lines.append(" 2 04 NITROGEN d c 01 EMPTY") - lines.append(" 3 05 NEON E c 01 EMPTY") - lines.append(" 4 06 CARBON DIOXIDE E c 01 EMPTY") - lines.append(" 5 08 HELIUM E c 08 HELIUM") - lines.append(" 6 11 HYDROGEN E c 01 EMPTY") + for buffer in self.simulated_rig.get_buffers(): + words = list() + words.append(buffer.get_number()) + words.append(buffer.get_buffer_gas().get_index_string()) + words.append(buffer.get_buffer_gas().get_name(length=self.gas_output_length, padding_character=' ')) + words.append("E" if buffer.valve_available() else "d") + words.append("O" if buffer.valve_open() else "c") + words.append(buffer.get_system_gas().get_index_string()) + words.append(buffer.get_buffer_gas().get_name()) + lines.append(' '.join(words)) lines.append("GCS") return '\n'.join(lines) def get_gas_mix_matrix(self): - length_limit = 20 - padded_gases = [(g + (length_limit-len(g))*"-") for g in gases] - lines = list() - # Column headers - for i in range(length_limit): - words = list() - words.append(' '*(length_limit+3)) - for j in range(length_limit): - words.append(padded_gases[j][i]) - lines.append(''.join(words)) - - # Rows - for i in range(length_limit): + # Gather data + system_gases = self.simulated_rig.get_system_gases().get_gases() + column_headers = [gas.get_name(length=self.gas_output_length, padding_character='-') + for gas in system_gases] + column_header_length = self.gas_output_length + row_titles = [gas.get_index_string() + " " + gas.get_name(length=self.gas_output_length, padding_character=' ') + for gas in system_gases] + row_title_length = max(row_titles, key=len) + mixable_chars = [["<" if self.simulated_rig.mixable(g1,g2) else "." for g1 in system_gases] + for g2 in system_gases] + + # Put data in output format + lines = list() + # Add column headers + for i in range(column_header_length): words = list() - words.append(str(i).zfill(2)) - words.append(padded_gases[i]) - for j in range(length_limit): - words.append("<" if is_mixable(gases[i], gases[j]) else ".") + words.append((row_title_length-1)*" ") + for j in range(len(column_headers)): + words.append(column_headers[j][i]) lines.append(' '.join(words)) - - # Footer - lines.append("GMM allowance limit: " + str(length_limit)) + # Add rows + assert len(row_titles)==len(mixable_chars) + for i in range(len(row_titles)): + words = list() + words.append(row_titles[i]) + words.append(' '.join(mixable_chars[i])) + lines.append(''.join(words)) + # Add footer + lines.append("GMM allowance limit: " + str(len(system_gases))) return '\n'.join(lines) def gas_mix_check(self,gas1_index,gas2_index): - gas1 = gases[int(gas1_index)] - gas2 = gases[int(gas2_index)] - gas1_padded = gas1 + "."*(20-len(gas1)) - gas2_padded = gas2 + "."*(20-len(gas2)) - return "GMC " + gas1_index.zfill(2) + " " + gas1_padded + " " + gas2_index.zfill(2) + " " + \ - gas2_padded + " " + ("ok" if is_mixable(gas1,gas2) else "NO") + gas1 = self.simulated_rig.get_system_gases().get_gas_by_index(gas1_index) + gas2 = self.simulated_rig.get_system_gases().get_gas_by_index(gas2_index) + return ' '.join(["GMC", + gas1.get_index_string(), gas1.get_name(length=self.gas_output_length, padding_character='.'), + gas2.get_index_string(), gas2.get_name(length=self.gas_output_length, padding_character='.'), + "ok" if self.simulated_rig.mixable(gas1, gas2) else "NO"]) + def get_gas_number_available(self): - return "20" + return str(self.simulated_rig.system_gases.count()) def get_hmi_status(self): - return "HMI OK ,192.168.100.208,B,034,S,041" + hmi = self.simulated_rig.get_hmi() + return " ".join(["HMI " + hmi.get_status() + " ", + hmi.get_ip(), "B", hmi.get_base_page(), "S", hmi.get_sub_page()]) def get_hmi_count_cycles(self): - return "HMC 999 006 002 002 002 002 002 001 001 310" + return " ".join(["HMC"] + self.simulated_rig.get_hmi().get_count_cycles()) def get_memory_location(self,location): - return "RDM " + location.zfill(4) + " 000000" + location_length = 4 + return " ".join(["RDM", location.zfill(location_length), self.simulated_rig.get_memory_location(location)]) def get_pressure_and_temperature_status(self): - return "PTS ODDDDDDDDDDDDD" + + def get_status_code(s): + if s==Sensor.DISABLED: + return "D" + elif s==Sensor.NO_REPLY: + return "X" + elif s==Sensor.VALUE_IN_RANGE: + return "O" + elif s==Sensor.VALUE_TOO_LOW: + return "L" + elif s==Sensor.VALUE_TOO_HIGH: + return "H" + else: + return "?" + + return "PTS " + \ + "".join([get_status_code(self.simulated_rig.get_temperature_sensor(i).get_status()) + for i in reversed(range(self.simulated_rig.number_of_temperature_sensors()))]) + \ + "".join([get_status_code(self.simulated_rig.get_pressure_sensor(i).get_status()) + for i in reversed(range(self.simulated_rig.number_of_pressure_sensors()))]) def get_pressures(self): - return "PMV 00.00 01.00 00.00 00.00 01.02 00.00" + return " ".join(["PMV"] + [self.simulated_rig.get_pressure_sensor(i).get_pressure() + for i in reversed(range(self.simulated_rig.number_of_pressure_sensors()))]) def get_temperatures(self): - return "TMV 25.00 00.00 00.00 00.00 00.00 00.00 00.00 00.00 00.00" + return " ".join(["TMV"] + [self.simulated_rig.get_temperature_sensor(i).get_temperature() + for i in reversed(range(self.simulated_rig.number_of_temperature_sensors()))]) + + def get_valve_status(self): + valves = [self.simulated_rig.get_supply_valve(), + self.get_vacuum_extract_valve(), + self.get_cell_valve()] + \ + [b.get_valve() for b in self.simulated_rig.get_buffers().reverse()] + + def derive_status(valve): + if valve.enabled() and valve.is_open(): + return "O" + elif valve.enabled() and not valve.is_open(): + return "E" + elif not valve.enabled() and valve.is_open(): + return "!" + elif not valve.enabled() and not valve.is_open(): + return "D" + else: + assert False + + return "VST Valve Status " + ''.join([derive_status(v) for v in valves]) + + def set_valve_status(self,valve_number,set_to_open): + if self.simulated_rig.halted(): + return "CLV Rejected only allowed when running" + else: + original_status = self.simulated_rig.is_valve_open(valve_number) + self.simulated_rig.set_valve_open(set_to_open) + new_status = self.simulated_rig.is_valve_open(valve_number) + + def derive_status(is_open): + return "open" if is_open else "closed" + + return " ".join(["OPV" if set_to_open else "CLV", "Valve Buffer", valve_number.lstrip("0"), + derive_status(original_status), "was", derive_status(new_status)]) + + def set_valve_closed(self, valve_number): + self.set_valve_status(valve_number, False) + + def set_valve_open(self, valve_number): + self.set_valve_status(valve_number, True) + def halt(self): + if self.simulated_rig.halted(): + message = "SYSTEM ALREADY HALTED" + else: + self.simulated_rig.halt() + assert self.simulated_rig.halted() + message = "SYSTEM NOW HALTED" + return "HLT *** " + message + " ***" + + def get_system_status(self): + return " ".join([ + "STS", + str(self.simulated_rig.get_status_code()).zfill(2), + "STOP" if self.simulated_rig.errors().run else "run", + "HMI" if self.simulated_rig.errors().hmi else "hmi", + "GUAGES" if self.simulated_rig.errors().guages else "guages", + "COMMS" if self.simulated_rig.errors().guages else "comms", + "HLT" if self.simulated_rig.halted() else "halted", + "E-STOP" if self.simulated_rig.errors().estop else "estop" + ]) + + # Information about ports, relays and com traffic are currently returned statically def get_ports_and_relays_hex(self): return "PTR I:00 0000 0000 R:0000 0200 0000 O:00 0000 4400" @@ -135,22 +252,5 @@ def get_ports_input(self): def get_ports_relays(self): return "PRY qwertyuiopasdfgh zyxwhmLsrqponmlk abcdefghihlbhace" - def get_system_status(self): - return "STS 09 STOP hmi guages comms hlt E-STOP" - def get_com_activity(self): - return "COM ok 0113/0000" - - def get_valve_status(self): - return "VST Valve Status DDDDDDDDD" - - def set_valve_closed(self,valve_number): - #return "CLV Rejected only allowed when running" - return "CLV Valve Buffer " + valve_number.lstrip("0") + " closed was open" - - def set_valve_open(self,valve_number): - #return "OLV Rejected only allowed when running" - return "OPV Valve Buffer " + valve_number.lstrip("0") + " opened was closed " - - def halt(self): - return "HLT *** SYSTEM NOW HALTED ***" \ No newline at end of file + return "COM ok 0113/0000" \ No newline at end of file From e1b3c14ced427be7cd2f9bfd6ce387d4cd86427c Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 4 Jan 2017 15:52:29 +0000 Subject: [PATCH 0037/1466] Add underlying device logic --- plankton_emulators/volumetric_rig/buffer.py | 12 ++ plankton_emulators/volumetric_rig/device.py | 118 +++++------ .../volumetric_rig/error_states.py | 7 + .../volumetric_rig/ethernet_device.py | 7 + plankton_emulators/volumetric_rig/gas.py | 26 +-- .../volumetric_rig/hmi_device.py | 19 ++ .../interfaces/stream_interface.py | 198 ++++++++++-------- .../volumetric_rig/pressure_sensor.py | 13 ++ .../volumetric_rig/seed_gas_data.py | 67 ++++++ plankton_emulators/volumetric_rig/sensor.py | 12 ++ .../volumetric_rig/sensor_status.py | 2 + .../volumetric_rig/system_gases.py | 48 ++--- .../volumetric_rig/temperature_sensor.py | 13 ++ .../volumetric_rig/two_gas_mixer.py | 9 + plankton_emulators/volumetric_rig/valve.py | 12 ++ 15 files changed, 356 insertions(+), 207 deletions(-) create mode 100644 plankton_emulators/volumetric_rig/buffer.py create mode 100644 plankton_emulators/volumetric_rig/error_states.py create mode 100644 plankton_emulators/volumetric_rig/ethernet_device.py create mode 100644 plankton_emulators/volumetric_rig/hmi_device.py create mode 100644 plankton_emulators/volumetric_rig/pressure_sensor.py create mode 100644 plankton_emulators/volumetric_rig/seed_gas_data.py create mode 100644 plankton_emulators/volumetric_rig/sensor.py create mode 100644 plankton_emulators/volumetric_rig/sensor_status.py create mode 100644 plankton_emulators/volumetric_rig/temperature_sensor.py create mode 100644 plankton_emulators/volumetric_rig/two_gas_mixer.py create mode 100644 plankton_emulators/volumetric_rig/valve.py diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py new file mode 100644 index 00000000..7ab264b2 --- /dev/null +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -0,0 +1,12 @@ +from valve import Valve + + +class Buffer(object): + def __init__(self, index, buffer_gas, system_gas): + self.buffer_gas = buffer_gas + self.system_gas = system_gas + self.index = index + self.valve = Valve() + + def index_string(self, length=1): + return str(self.index).zfill(length) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 8a07033a..e2dae457 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -1,70 +1,68 @@ from lewis.devices import Device +from two_gas_mixer import TwoGasMixer +from buffer import Buffer +from gas import Gas from system_gases import SystemGases - -# Define string literals for all gas names to avoid typos -UNKNOWN = "UNKNOWN" -EMPTY = "EMPTY" -VACUUM_EXTRACT = "VACUUM EXTRACT" -ARGON = "ARGON" -NITROGEN = "NITROGEN" -NEON = "NEON" -CARBON_DIOXIDE = "CARBON DIOXIDE" -CARBON_MONOXIDE = "CARBON MONOXIDE" -HELIUM = "HELIUM" -GRAVY = "GRAVY" -LIVER = "LIVER" -HYDROGEN = "HYDROGEN" -OXYGEN = "OXYGEN" -CURRIED_RAT = "CURRIED RAT" -FRESH_COFFEE = "FRESH COFFEE" -BACON = "BACON" -ONION = "ONION" -CHIPS = "CHIPS" -GARLIC = "GARLIC" -BROWN_SAUCE = "BROWN SAUCE" +from seed_gas_data import SeedGasData +from ethernet_device import EthernetDevice +from hmi_device import HmiDevice +from temperature_sensor import TemperatureSensor +from valve import Valve class SimulatedVolumetricRig(Device): def __init__(self): - self.system_gases = SystemGases() - - # Populate system gases - gas_names = [UNKNOWN, EMPTY, VACUUM_EXTRACT, ARGON, NITROGEN, NEON, CARBON_DIOXIDE, CARBON_MONOXIDE, HELIUM, - GRAVY, LIVER, HYDROGEN, OXYGEN, CURRIED_RAT, FRESH_COFFEE, BACON, ONION, CHIPS, GARLIC, - BROWN_SAUCE] - index = 0 - for gas_name in gas_names: - self.system_gases.add_gas(index,gas_name) - index += 1 - - # Set unmixable gases - for g in gas_names: - self.system_gases.set_unmixable_by_name(UNKNOWN, g) - self.system_gases.set_unmixable_by_name(LIVER, g) - if g not in {ARGON, NITROGEN, VACUUM_EXTRACT, EMPTY}: - self.system_gases.set_unmixable_by_name(NITROGEN, g) - if g not in {NEON, CARBON_DIOXIDE, CARBON_MONOXIDE}: - self.system_gases.set_unmixable_by_name(CARBON_MONOXIDE, g) - if g in {CURRIED_RAT, FRESH_COFFEE, BACON, CHIPS}: - self.system_gases.set_unmixable_by_name(GRAVY, g) - if g in {OXYGEN, ONION, BROWN_SAUCE}: - self.system_gases.set_unmixable_by_name(HYDROGEN, g) - - self.system_gases.set_buffer_gas(1, ARGON) - self.system_gases.set_buffer_gas(2, NITROGEN) - self.system_gases.set_buffer_gas(3, NEON) - self.system_gases.set_buffer_gas(4, CARBON_DIOXIDE) - self.system_gases.set_buffer_gas(5, HELIUM) - self.system_gases.set_buffer_gas(6, HYDROGEN) - - def get_identity(self): + + names = SeedGasData.names() + self.system_gases = SystemGases([Gas(i, names[i]) for i in range(len(names))]) + + self.mixer = TwoGasMixer() + for pair in SeedGasData.mixable_gas_names(): + self.mixer.add_mixable(self.system_gases.gas_by_name(pair.pop()), self.system_gases.gas_by_name(pair.pop())) + + # Set buffers + buffer_gases = [(self.system_gases.gas_by_name(pair[0]), + self.system_gases.gas_by_name(pair[1])) + for pair in SeedGasData.buffer_gas_names()] + self.buffers = [Buffer(i+1, buffer_gases[i][0], buffer_gases[i][1]) + for i in range(len(buffer_gases))] + + # Set ethernet devices + self.plc = EthernetDevice() + self.hmi = HmiDevice() + + # Set up sensors + self.temperature_sensors = [TemperatureSensor() for i in range(9)] + self.pressure_sensors = [TemperatureSensor() for i in range(5)] + + # Target pressure: We can't set this via serial + self.target_pressure = 12.34 + + # Set up special valves + self.supply_valve = Valve() + self.vacuum_extract_valve = Valve() + self.cell_valve = Valve() + + self.halted = False + self.status_code = 2 + self.errors = ErrorStates() + + super(SimulatedVolumetricRig, self).__init__() + + def identify(self): return "ISIS Volumetric Gas Handing Panel" - def get_plc_ip(self): - return "192.168.1.100" + def count_buffers(self): + return len(self.buffers) + + def buffer(self,i): + try: + return next(b for b in self.buffers if b.index == i) + except StopIteration: + return None - def get_hmi_ip(self): - return "192.168.1.101" + def memory_location(self, location): + return str(location).zfill(6) - def get_hmi_status(self): - return "OK" \ No newline at end of file + def halt(self): + self.halted = True \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/error_states.py b/plankton_emulators/volumetric_rig/error_states.py new file mode 100644 index 00000000..b671c473 --- /dev/null +++ b/plankton_emulators/volumetric_rig/error_states.py @@ -0,0 +1,7 @@ +class ErrorStates(object): + def __init__(self): + self.run = False + self.hmi = False + self.gauges = False + self.comms = False + self.estop = False \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/ethernet_device.py b/plankton_emulators/volumetric_rig/ethernet_device.py new file mode 100644 index 00000000..31978c57 --- /dev/null +++ b/plankton_emulators/volumetric_rig/ethernet_device.py @@ -0,0 +1,7 @@ +from types import StringType + + +class EthernetDevice(object): + def __init__(self, ip): + assert type(ip) is StringType + self.ip = ip diff --git a/plankton_emulators/volumetric_rig/gas.py b/plankton_emulators/volumetric_rig/gas.py index 2d6a42bf..e5d4ee90 100644 --- a/plankton_emulators/volumetric_rig/gas.py +++ b/plankton_emulators/volumetric_rig/gas.py @@ -1,27 +1,15 @@ -from types import * +from types import StringType, IntType class Gas(object): def __init__(self,index,name): - assert type(index) is IntType - assert type(name) is StringType + assert type(index) is IntType and type(name) is StringType self.index = index self.name = name - def get_name(self): - return self.name + def name(self, length=None, padding_character=" "): + return self.name if length is None \ + else self.name[:length] + (length-len(self.name))*padding_character - def get_index(self): - return self.index - - def get_zero_padded_index(self,length=2): - return str(self.index).zfill(length) - - def get_dash_padded_name(self,length): - return self.get_padded_name(length,"-") - - def get_dot_padded_name(self,length): - return self.get_padded_name(length,".") - - def get_padded_name(self,length,char): - return self.name + char*(length-len(self.name)) \ No newline at end of file + def index_string(self, length=2): + return str(self.index).zfill(length) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/hmi_device.py b/plankton_emulators/volumetric_rig/hmi_device.py new file mode 100644 index 00000000..875e8bb8 --- /dev/null +++ b/plankton_emulators/volumetric_rig/hmi_device.py @@ -0,0 +1,19 @@ +from ethernet_device import EthernetDevice + + +class HmiDevice(EthernetDevice): + + OK_STATUS = "OK" + + def __init__(self, ip): + self.status = HmiDevice.OK_STATUS + self.base_page = 34 + self.sub_page = 2 + self.count_cycles = ["999", "006", "002", "002", "002", "002", "002", "002", "001", "001", "310"] + super(HmiDevice, self).__init__(ip) + + def base_page_string(self): + return str(self.base_page).zfill(3) + + def sub_page_string(self): + return str(self.sub_page).zfill(3) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index d5164ac1..11f72241 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,5 +1,6 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..device import SimulatedVolumetricRig +from ..sensor_status import SensorStatus class VolumetricRigStreamInterface(StreamAdapter): @@ -34,19 +35,21 @@ class VolumetricRigStreamInterface(StreamAdapter): in_terminator = "\r\n" out_terminator = "\r\n" - def __init__(self): - self.simulated_rig = SimulatedVolumetricRig() + def __init__(self, device, arguments=None): + self.rig = SimulatedVolumetricRig() + # Lots of formatted output is based on fixed length strings self.gas_output_length = 20 - super(VolumetricRigStreamInterface, self).__init__() + super(VolumetricRigStreamInterface, self).__init__(device, arguments) def get_identity(self): - return "IDN,00," + self.simulated_rig.get_identity() + return "IDN,00," + self.rig.identify() - def get_buffer_control_and_status(self,buffer_number_string): - number_print_length = 3 - message_prefix = "BCS Buffer " + buffer_number_string.zfill(number_print_length)[:number_print_length] + " " + def get_buffer_control_and_status(self, buffer_number_string): + index_print_length = 3 + message_prefix = "BCS Buffer " + buffer_number_string.zfill(index_print_length)[:index_print_length] + " " buffer_too_low = message_prefix + "Too Low" buffer_too_high = message_prefix + "Too High" + try: buffer_number = int(buffer_number_string) except TypeError: @@ -55,65 +58,69 @@ def get_buffer_control_and_status(self,buffer_number_string): if buffer_number <= 0: return buffer_too_low - elif buffer_number > self.simulated_rig.number_of_buffers(): + elif buffer_number > len(self.rig.buffers()): return buffer_too_high - buffer = self.simulated_rig.get_buffer(buffer_number) + buff = self.rig.buffer(buffer_number) + assert buff is not None return " ".join([ message_prefix[:-1], - buffer.get_buffer_gas().get_index_string(), - buffer.get_buffer_gas().get_name(length=self.gas_output_length, padding_character=" "), - "E" if buffer.get_valve().enabled() else "d", - "O" if buffer.get_valve().is_open() else "c", - buffer.get_system_gas().get_index_string(), - buffer.get_system_gas().get_name() + buff.buffer_gas.index_string(), + buff.buffer_gas.name(self.gas_output_length, " "), + "E" if buff.valve.is_enabled else "d", + "O" if buff.valve.is_open else "c", + buff.system_gas.index_string(), + buff.system_gas.name ]) - def get_ethernet_and_hmi_status(self): - return "ETN:PLC " + self.simulated_rig.get_plc_ip() + ",HMI " + self.simulated_rig.get_hmi_status() + " ," + \ - self.simulated_rig.get_hmi_ip() + # The syntax of the return string is odd: the separators are not consistent + return " ".join([ + "ETN:PLC", + self.rig.plc.ip + ",HMI", + self.rig.hmi.status, + "," + self.rig.hmi.ip + ]) def get_gas_control_and_status(self): lines = list() lines.append("No No Buffer E O No System") - for buffer in self.simulated_rig.get_buffers(): + for buff in self.rig.buffers: words = list() - words.append(buffer.get_number()) - words.append(buffer.get_buffer_gas().get_index_string()) - words.append(buffer.get_buffer_gas().get_name(length=self.gas_output_length, padding_character=' ')) - words.append("E" if buffer.valve_available() else "d") - words.append("O" if buffer.valve_open() else "c") - words.append(buffer.get_system_gas().get_index_string()) - words.append(buffer.get_buffer_gas().get_name()) - lines.append(' '.join(words)) + words.append(buff.index_string()) + words.append(buff.buffer_gas.index_string()) + words.append(buff.buffer_gas.name(self.gas_output_length, ' ')) + words.append("E" if buff.valve.is_enabled else "d") + words.append("O" if buff.valve.is_open else "c") + words.append(buff.system_gas.index_string()) + words.append(buff.system_gas().name) + lines.append(" ".join(words)) lines.append("GCS") return '\n'.join(lines) def get_gas_mix_matrix(self): # Gather data - system_gases = self.simulated_rig.get_system_gases().get_gases() - column_headers = [gas.get_name(length=self.gas_output_length, padding_character='-') - for gas in system_gases] - column_header_length = self.gas_output_length - row_titles = [gas.get_index_string() + " " + gas.get_name(length=self.gas_output_length, padding_character=' ') - for gas in system_gases] - row_title_length = max(row_titles, key=len) - mixable_chars = [["<" if self.simulated_rig.mixable(g1,g2) else "." for g1 in system_gases] + system_gases = self.rig.system_gases.gases + + column_headers = [gas.name(self.gas_output_length, '-') for gas in system_gases] + row_titles = [" ".join([gas.index_string(), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] + mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] # Put data in output format lines = list() # Add column headers - for i in range(column_header_length): + for i in range(max(column_headers, key=len)): words = list() - words.append((row_title_length-1)*" ") + # For the top-left block of white space + words.append((max(row_titles, key=len)-1)*" ") + # Vertically aligned gas names for j in range(len(column_headers)): words.append(column_headers[j][i]) lines.append(' '.join(words)) # Add rows - assert len(row_titles)==len(mixable_chars) + assert len(row_titles) == len(mixable_chars) for i in range(len(row_titles)): words = list() words.append(row_titles[i]) @@ -124,65 +131,64 @@ def get_gas_mix_matrix(self): return '\n'.join(lines) - def gas_mix_check(self,gas1_index,gas2_index): - gas1 = self.simulated_rig.get_system_gases().get_gas_by_index(gas1_index) - gas2 = self.simulated_rig.get_system_gases().get_gas_by_index(gas2_index) + def gas_mix_check(self, gas1_index, gas2_index): + gas1 = self.rig.system_gases.gas_by_index(gas1_index) + gas2 = self.rig.system_gases.gas_by_index(gas2_index) return ' '.join(["GMC", - gas1.get_index_string(), gas1.get_name(length=self.gas_output_length, padding_character='.'), - gas2.get_index_string(), gas2.get_name(length=self.gas_output_length, padding_character='.'), - "ok" if self.simulated_rig.mixable(gas1, gas2) else "NO"]) - + gas1.index_string(), gas1.name(self.gas_output_length, '.'), + gas2.index_string(), gas2.name(self.gas_output_length, '.'), + "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): - return str(self.simulated_rig.system_gases.count()) + return len(self.rig.system_gases) def get_hmi_status(self): - hmi = self.simulated_rig.get_hmi() - return " ".join(["HMI " + hmi.get_status() + " ", - hmi.get_ip(), "B", hmi.get_base_page(), "S", hmi.get_sub_page()]) + hmi = self.rig.hmi + return " ".join(["HMI " + hmi.status + " ", + hmi.ip, "B", hmi.base_page_string(), "S", hmi.sub_page_string()]) def get_hmi_count_cycles(self): - return " ".join(["HMC"] + self.simulated_rig.get_hmi().get_count_cycles()) + return " ".join(["HMC"] + self.rig.hmi.count_cycles) - def get_memory_location(self,location): - location_length = 4 - return " ".join(["RDM", location.zfill(location_length), self.simulated_rig.get_memory_location(location)]) + def get_memory_location(self, location): + return " ".join(["RDM", location.zfill(4), self.rig.memory_location(location)]) def get_pressure_and_temperature_status(self): def get_status_code(s): - if s==Sensor.DISABLED: + if s == SensorStatus.DISABLED: return "D" - elif s==Sensor.NO_REPLY: + elif s == SensorStatus.NO_REPLY: return "X" - elif s==Sensor.VALUE_IN_RANGE: + elif s == SensorStatus.VALUE_IN_RANGE: return "O" - elif s==Sensor.VALUE_TOO_LOW: + elif s == SensorStatus.VALUE_TOO_LOW: return "L" - elif s==Sensor.VALUE_TOO_HIGH: + elif s == SensorStatus.VALUE_TOO_HIGH: return "H" else: return "?" return "PTS " + \ - "".join([get_status_code(self.simulated_rig.get_temperature_sensor(i).get_status()) - for i in reversed(range(self.simulated_rig.number_of_temperature_sensors()))]) + \ - "".join([get_status_code(self.simulated_rig.get_pressure_sensor(i).get_status()) - for i in reversed(range(self.simulated_rig.number_of_pressure_sensors()))]) + "".join([get_status_code(self.rig.temperature_sensors[i].status) + for i in reversed(range(len(self.rig.temperature_sensors)))]) + \ + "".join([get_status_code(self.rig.pressure_sensors[i].status) + for i in reversed(range(len(self.rig.pressure_sensors)))]) def get_pressures(self): - return " ".join(["PMV"] + [self.simulated_rig.get_pressure_sensor(i).get_pressure() - for i in reversed(range(self.simulated_rig.number_of_pressure_sensors()))]) + return " ".join(["PMV"] + + [self.rig.pressure_sensors[i].pressure + for i in reversed(range(len(self.rig.pressure_sensors)))] + + ["T", self.rig.target_pressure]) def get_temperatures(self): - return " ".join(["TMV"] + [self.simulated_rig.get_temperature_sensor(i).get_temperature() - for i in reversed(range(self.simulated_rig.number_of_temperature_sensors()))]) + return " ".join(["TMV"] + + [self.rig.temperature_sensors[i].temperature + for i in reversed(range(len(self.rig.temperature_sensors)))]) def get_valve_status(self): - valves = [self.simulated_rig.get_supply_valve(), - self.get_vacuum_extract_valve(), - self.get_cell_valve()] + \ - [b.get_valve() for b in self.simulated_rig.get_buffers().reverse()] + valves = [self.rig.supply_valve, self.rig.vacuum_extract_valve, self.rig.cell_valve] + \ + [b.valve for b in self.rig.buffers.reverse()] def derive_status(valve): if valve.enabled() and valve.is_open(): @@ -196,47 +202,55 @@ def derive_status(valve): else: assert False - return "VST Valve Status " + ''.join([derive_status(v) for v in valves]) + return "VST Valve Status " + "".join([derive_status(v) for v in valves]) - def set_valve_status(self,valve_number,set_to_open): - if self.simulated_rig.halted(): + def _set_valve_status(self, buffer_number, set_to_open): + if self.rig.halted: return "CLV Rejected only allowed when running" else: - original_status = self.simulated_rig.is_valve_open(valve_number) - self.simulated_rig.set_valve_open(set_to_open) - new_status = self.simulated_rig.is_valve_open(valve_number) + valve = self.rig.buffer(buffer_number).valve + original_status = valve.is_open + valve.open() if set_to_open else valve.close() + new_status = self.rig.buffer(buffer_number).valve.is_open def derive_status(is_open): return "open" if is_open else "closed" - return " ".join(["OPV" if set_to_open else "CLV", "Valve Buffer", valve_number.lstrip("0"), - derive_status(original_status), "was", derive_status(new_status)]) + # e.g. CLV Valve Buffer 1 closed was open + return " ".join([ + "OPV" if set_to_open else "CLV", + "Valve Buffer", + buffer_number.lstrip("0"), + derive_status(original_status), + "was", + derive_status(new_status)]) - def set_valve_closed(self, valve_number): - self.set_valve_status(valve_number, False) + def set_valve_closed(self, buffer_number): + self._set_valve_status(buffer_number, False) - def set_valve_open(self, valve_number): - self.set_valve_status(valve_number, True) + def set_valve_open(self, buffer_number): + self._set_valve_status(buffer_number, True) def halt(self): - if self.simulated_rig.halted(): + if self.rig.halted: message = "SYSTEM ALREADY HALTED" else: - self.simulated_rig.halt() - assert self.simulated_rig.halted() + self.rig.halt() + assert self.rig.halted message = "SYSTEM NOW HALTED" return "HLT *** " + message + " ***" def get_system_status(self): return " ".join([ "STS", - str(self.simulated_rig.get_status_code()).zfill(2), - "STOP" if self.simulated_rig.errors().run else "run", - "HMI" if self.simulated_rig.errors().hmi else "hmi", - "GUAGES" if self.simulated_rig.errors().guages else "guages", - "COMMS" if self.simulated_rig.errors().guages else "comms", - "HLT" if self.simulated_rig.halted() else "halted", - "E-STOP" if self.simulated_rig.errors().estop else "estop" + str(self.rig.status_code).zfill(2), + "STOP" if self.rig.errors.run else "run", + "HMI" if self.rig.errors.hmi else "hmi", + # Spelling error duplicated as on device + "GUAGES" if self.rig.errors.gauges else "guages", + "COMMS" if self.rig.errors.comms else "comms", + "HLT" if self.rig.halted else "halted", + "E-STOP" if self.rig.errors.estop else "estop" ]) # Information about ports, relays and com traffic are currently returned statically diff --git a/plankton_emulators/volumetric_rig/pressure_sensor.py b/plankton_emulators/volumetric_rig/pressure_sensor.py new file mode 100644 index 00000000..5ec46b4d --- /dev/null +++ b/plankton_emulators/volumetric_rig/pressure_sensor.py @@ -0,0 +1,13 @@ +from sensor import Sensor + + +class PressureSensor(Sensor): + def __init__(self): + self.pressure = 0.0 + super(PressureSensor,self).__init__() + + def set_pressure(self,pressure): + self.pressure = pressure + + def get_pressure(self): + return self.pressure \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/seed_gas_data.py b/plankton_emulators/volumetric_rig/seed_gas_data.py new file mode 100644 index 00000000..b78abbea --- /dev/null +++ b/plankton_emulators/volumetric_rig/seed_gas_data.py @@ -0,0 +1,67 @@ +from gas import Gas + + +class SeedGasData(object): + unknown = "UNKNOWN" + empty = "EMPTY" + vacuum_extract = "VACUUM EXTRACT" + argon = "ARGON" + nitrogen = "NITROGEN" + neon = "NEON" + carbon_dioxide = "CARBON DIOXIDE" + carbon_monoxide = "CARBON MONOXIDE" + helium = "HELIUM" + gravy = "GRAVY" + liver = "LIVER" + hydrogen = "HYDROGEN" + oxygen = "OXYGEN" + curried_rat = "CURRIED RAT" + fresh_coffee = "FRESH COFFEE" + bacon = "BACON" + onion = "ONION" + chips = "CHIPS" + garlic = "GARLIC" + brown_sauce = "BROWN SAUCE" + + names = [unknown, empty, vacuum_extract, argon, nitrogen, neon, carbon_dioxide, carbon_monoxide, + helium, gravy, liver, hydrogen, oxygen, curried_rat, fresh_coffee, bacon, onion, chips, garlic, + brown_sauce] + + @staticmethod + def names(): + return SeedGasData.names + + @ staticmethod + def mixable_gas_names(): + sgd = SeedGasData + mixable_names = set() + for g in sgd.names: + if g not in {sgd.unknown, sgd.liver}: + mixable_names.add({g, g}) + mixable_names.add({sgd.empty, g}) + mixable_names.add({sgd.vacuum_extract, g}) + mixable_names.add({sgd.argon, g}) + if g != sgd.nitrogen: + mixable_names.add({sgd.neon, g}) + mixable_names.add({sgd.carbon_dioxide, g}) + if g != sgd.carbon_monoxide: + mixable_names.add({sgd.helium, g}) + for g in {sgd.hydrogen, sgd.oxygen, sgd.onion, sgd.garlic, sgd.brown_sauce}: + mixable_names.add({sgd.gravy, g}) + import itertools + for pair in list(itertools.combinations( + {sgd.oxygen, sgd.curried_rat, sgd.fresh_coffee, sgd.bacon, sgd.onion, sgd.chips, sgd.garlic, sgd.brown_sauce},2)): + mixable_names.add({pair[0], pair[1]}) + return mixable_names + + @staticmethod + def buffer_gas_names(): + sgd = SeedGasData + return [ + (sgd.argon, sgd.argon), + (sgd.nitrogen, sgd.empty), + (sgd.neon, sgd.empty), + (sgd.carbon_dioxide, sgd.empty), + (sgd.helium, sgd.helium), + (sgd.hydrogen, sgd.empty), + ] diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py new file mode 100644 index 00000000..c466f709 --- /dev/null +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -0,0 +1,12 @@ +from sensor_status import SensorStatus + + +class Sensor(object): + def __init__(self): + self.status = SensorStatus.UNKNOWN + + def set_status(self, status): + self.status = status + + def get_status(self): + return self.status \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/sensor_status.py b/plankton_emulators/volumetric_rig/sensor_status.py new file mode 100644 index 00000000..f3263a79 --- /dev/null +++ b/plankton_emulators/volumetric_rig/sensor_status.py @@ -0,0 +1,2 @@ +class SensorStatus(object): + UNKNOWN, DISABLED, NO_REPLY, VALUE_IN_RANGE, VALUE_TOO_LOW, VALUE_TOO_HIGH = (i for i in range(6)) diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/plankton_emulators/volumetric_rig/system_gases.py index a856af59..485b37f4 100644 --- a/plankton_emulators/volumetric_rig/system_gases.py +++ b/plankton_emulators/volumetric_rig/system_gases.py @@ -1,46 +1,22 @@ from gas import Gas + class SystemGases(object): - def __init__(self): - self.gases = dict() - self.unmixable_pairs = set() - self.buffer_gases = dict() + def __init__(self, gases=list()): + self.gases = set() + self._add_gases(gases) - def add_gas(self,gas): - self.gases[gas.get_index()] = gas + def gas_by_index(self, index): + return self._get_by_method(index, "index") - def get_gas_by_index(self,index): - if index in self.gases.keys(): - return self.gases[index] - else: - return None + def gas_by_name(self, name): + return self._get_by_method(name, "name") - def get_gas_by_name(self, name): + def _get_by_method(self, value, method): try: - return next(g for g in self.gases if g.get_name()==name) + return next(g for g in self.gases if getattr(Gas, method) == value) except StopIteration: return None - def set_unmixable(self, gas1, gas2): - self.set_unmixable(gas1.get_index(), gas2.get_index()) - - def set_unmixable_by_name(self, name1, name2): - self.set_unmixable(self.get_gas_by_name(name1), self.get_gas_by_name(name2)) - - def set_unmixable_by_index(self, gas1_index, gas2_index): - self.unmixable_pairs.add({gas1_index,gas2_index}) - - def are_mixable(self, gas1, gas2): - return self.are_mixable_by_index(gas1.get_index(), gas2.get_index()) - - def are_mixable_by_index(self, gas1_index, gas2_index): - return not {gas1_index,gas2_index} in self.unmixable_pairs - - def are_mixable_by_name(self, gas1_name, gas2_name): - return self.are_mixable(self.get_gas_by_name(gas1_name), self.get_gas_by_name(gas2_name)) - - def set_buffer_gas(self,key,gas_name): - self.buffer_gases[key] = self.get_gas_by_name(gas_name) - - def add_gas(self,index,name): - self.gases[index] = Gas(index,name) \ No newline at end of file + def _add_gases(self, iterable): + self.gases += {g for g in iterable if isinstance(g, Gas)} diff --git a/plankton_emulators/volumetric_rig/temperature_sensor.py b/plankton_emulators/volumetric_rig/temperature_sensor.py new file mode 100644 index 00000000..af7ba6da --- /dev/null +++ b/plankton_emulators/volumetric_rig/temperature_sensor.py @@ -0,0 +1,13 @@ +from sensor import Sensor + + +class TemperatureSensor(Sensor): + def __init__(self): + self.temperature = 0.0 + super(TemperatureSensor,self).__init__() + + def set_temperature(self,temperature): + self.temperature = temperature + + def get_temperature(self): + return self.temperature \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/two_gas_mixer.py b/plankton_emulators/volumetric_rig/two_gas_mixer.py new file mode 100644 index 00000000..09b9f2f7 --- /dev/null +++ b/plankton_emulators/volumetric_rig/two_gas_mixer.py @@ -0,0 +1,9 @@ +class TwoGasMixer(object): + def __init__(self): + self.mixable = {} + + def add_mixable(self, gas1, gas2): + self.mixable.add({gas1, gas2}) + + def can_mix(self, gas1, gas2): + return {gas1, gas2} in self.mixable diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py new file mode 100644 index 00000000..c981f102 --- /dev/null +++ b/plankton_emulators/volumetric_rig/valve.py @@ -0,0 +1,12 @@ +class Valve(object): + def __init__(self): + self.is_enabled = True + self.is_open = False + + def open(self): + if self.is_enabled: + self.is_open = True + + def close(self): + if self.is_enabled: + self.is_open = False \ No newline at end of file From 07938498ecba823a43d84d9bff1049b9a3a2842e Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 4 Jan 2017 16:48:17 +0000 Subject: [PATCH 0038/1466] Fix up errors on run --- plankton_emulators/volumetric_rig/device.py | 23 ++++++++----- plankton_emulators/volumetric_rig/gas.py | 2 +- .../interfaces/stream_interface.py | 30 ++++++++-------- .../volumetric_rig/seed_gas_data.py | 34 ++++++++----------- .../volumetric_rig/system_gases.py | 6 ++-- .../volumetric_rig/two_gas_mixer.py | 6 ++-- 6 files changed, 52 insertions(+), 49 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index e2dae457..4e78db7c 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -8,28 +8,33 @@ from hmi_device import HmiDevice from temperature_sensor import TemperatureSensor from valve import Valve +from error_states import ErrorStates +from time import sleep class SimulatedVolumetricRig(Device): def __init__(self): + # Set up all available gases + self.system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) - names = SeedGasData.names() - self.system_gases = SystemGases([Gas(i, names[i]) for i in range(len(names))]) - + # Set mixable gases self.mixer = TwoGasMixer() for pair in SeedGasData.mixable_gas_names(): - self.mixer.add_mixable(self.system_gases.gas_by_name(pair.pop()), self.system_gases.gas_by_name(pair.pop())) + name1, name2 = pair + self.mixer.add_mixable( + self.system_gases.gas_by_name(name1), + self.system_gases.gas_by_name(name2)) # Set buffers - buffer_gases = [(self.system_gases.gas_by_name(pair[0]), - self.system_gases.gas_by_name(pair[1])) + buffer_gases = [(self.system_gases.gas_by_name(next(iter(pair))), + self.system_gases.gas_by_name(next(iter(pair)))) for pair in SeedGasData.buffer_gas_names()] self.buffers = [Buffer(i+1, buffer_gases[i][0], buffer_gases[i][1]) for i in range(len(buffer_gases))] # Set ethernet devices - self.plc = EthernetDevice() - self.hmi = HmiDevice() + self.plc = EthernetDevice("192.168.0.1") + self.hmi = HmiDevice("192.168.0.2") # Set up sensors self.temperature_sensors = [TemperatureSensor() for i in range(9)] @@ -43,10 +48,12 @@ def __init__(self): self.vacuum_extract_valve = Valve() self.cell_valve = Valve() + # Misc system state variables self.halted = False self.status_code = 2 self.errors = ErrorStates() + # Parent constructor super(SimulatedVolumetricRig, self).__init__() def identify(self): diff --git a/plankton_emulators/volumetric_rig/gas.py b/plankton_emulators/volumetric_rig/gas.py index e5d4ee90..0d4ecf36 100644 --- a/plankton_emulators/volumetric_rig/gas.py +++ b/plankton_emulators/volumetric_rig/gas.py @@ -7,7 +7,7 @@ def __init__(self,index,name): self.index = index self.name = name - def name(self, length=None, padding_character=" "): + def pad_name(self, length=None, padding_character=" "): return self.name if length is None \ else self.name[:length] + (length-len(self.name))*padding_character diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 11f72241..2d1a2f31 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -8,7 +8,7 @@ class VolumetricRigStreamInterface(StreamAdapter): commands = { Cmd("get_identity", "^IDN$"), Cmd("get_identity", "^\?$"), - Cmd("get_buffer_control_and_status", "^BCS\s([0-9]+)$"), + Cmd("get_buffer_control_and_status", "^BCS\s([a-zA-Z0-9_.-]+)$"), Cmd("get_ethernet_and_hmi_status", "^ETN$"), Cmd("get_gas_control_and_status", "^GCS$"), Cmd("get_gas_mix_matrix", "^GMM$"), @@ -45,28 +45,30 @@ def get_identity(self): return "IDN,00," + self.rig.identify() def get_buffer_control_and_status(self, buffer_number_string): - index_print_length = 3 - message_prefix = "BCS Buffer " + buffer_number_string.zfill(index_print_length)[:index_print_length] + " " - buffer_too_low = message_prefix + "Too Low" - buffer_too_high = message_prefix + "Too High" - try: buffer_number = int(buffer_number_string) - except TypeError: + except ValueError: # This is how the volumetric rig currently responds - return buffer_too_low + buffer_number = 0 + + message_prefix = "BCS" + num_length = 3 + error_message_prefix = " ".join(["Buffer",message_prefix,str(buffer_number)[:num_length].zfill(num_length)]) + buffer_too_low = " ".join([error_message_prefix,"Too Low"]) + buffer_too_high = " ".join([error_message_prefix + "Too High"]) if buffer_number <= 0: return buffer_too_low - elif buffer_number > len(self.rig.buffers()): + elif buffer_number > len(self.rig.buffers): return buffer_too_high buff = self.rig.buffer(buffer_number) assert buff is not None return " ".join([ - message_prefix[:-1], + message_prefix, + buff.index_string(), buff.buffer_gas.index_string(), - buff.buffer_gas.name(self.gas_output_length, " "), + buff.buffer_gas.pad_name(self.gas_output_length, " "), "E" if buff.valve.is_enabled else "d", "O" if buff.valve.is_open else "c", buff.system_gas.index_string(), @@ -89,7 +91,7 @@ def get_gas_control_and_status(self): words = list() words.append(buff.index_string()) words.append(buff.buffer_gas.index_string()) - words.append(buff.buffer_gas.name(self.gas_output_length, ' ')) + words.append(buff.buffer_gas.pad_name(self.gas_output_length, ' ')) words.append("E" if buff.valve.is_enabled else "d") words.append("O" if buff.valve.is_open else "c") words.append(buff.system_gas.index_string()) @@ -103,8 +105,8 @@ def get_gas_mix_matrix(self): # Gather data system_gases = self.rig.system_gases.gases - column_headers = [gas.name(self.gas_output_length, '-') for gas in system_gases] - row_titles = [" ".join([gas.index_string(), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] + column_headers = [gas.pad_name(self.gas_output_length, '-') for gas in system_gases] + row_titles = [" ".join([gas.index_string(), gas.pad_name(self.gas_output_length, ' ')]) for gas in system_gases] mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] diff --git a/plankton_emulators/volumetric_rig/seed_gas_data.py b/plankton_emulators/volumetric_rig/seed_gas_data.py index b78abbea..57d04ac9 100644 --- a/plankton_emulators/volumetric_rig/seed_gas_data.py +++ b/plankton_emulators/volumetric_rig/seed_gas_data.py @@ -1,6 +1,3 @@ -from gas import Gas - - class SeedGasData(object): unknown = "UNKNOWN" empty = "EMPTY" @@ -23,35 +20,32 @@ class SeedGasData(object): garlic = "GARLIC" brown_sauce = "BROWN SAUCE" - names = [unknown, empty, vacuum_extract, argon, nitrogen, neon, carbon_dioxide, carbon_monoxide, - helium, gravy, liver, hydrogen, oxygen, curried_rat, fresh_coffee, bacon, onion, chips, garlic, - brown_sauce] + names = [unknown, empty, vacuum_extract, argon, nitrogen, neon, carbon_dioxide, carbon_monoxide, helium, gravy, + liver, hydrogen, oxygen, curried_rat, fresh_coffee, bacon, onion, chips, garlic, brown_sauce] @staticmethod - def names(): - return SeedGasData.names - - @ staticmethod def mixable_gas_names(): sgd = SeedGasData mixable_names = set() for g in sgd.names: if g not in {sgd.unknown, sgd.liver}: - mixable_names.add({g, g}) - mixable_names.add({sgd.empty, g}) - mixable_names.add({sgd.vacuum_extract, g}) - mixable_names.add({sgd.argon, g}) + mixable_names.add((g, g)) + mixable_names.add((sgd.empty, g)) + mixable_names.add((sgd.vacuum_extract, g)) + mixable_names.add((sgd.argon, g)) if g != sgd.nitrogen: - mixable_names.add({sgd.neon, g}) - mixable_names.add({sgd.carbon_dioxide, g}) + mixable_names.add((sgd.neon, g)) + mixable_names.add((sgd.carbon_dioxide, g)) if g != sgd.carbon_monoxide: - mixable_names.add({sgd.helium, g}) + mixable_names.add((sgd.helium, g)) for g in {sgd.hydrogen, sgd.oxygen, sgd.onion, sgd.garlic, sgd.brown_sauce}: - mixable_names.add({sgd.gravy, g}) + mixable_names.add((sgd.gravy, g)) import itertools for pair in list(itertools.combinations( - {sgd.oxygen, sgd.curried_rat, sgd.fresh_coffee, sgd.bacon, sgd.onion, sgd.chips, sgd.garlic, sgd.brown_sauce},2)): - mixable_names.add({pair[0], pair[1]}) + {sgd.oxygen, sgd.curried_rat, sgd.fresh_coffee, sgd.bacon, + sgd.onion, sgd.chips, sgd.garlic, sgd.brown_sauce}, + 2)): + mixable_names.add((pair[0], pair[1])) return mixable_names @staticmethod diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/plankton_emulators/volumetric_rig/system_gases.py index 485b37f4..a97dfa98 100644 --- a/plankton_emulators/volumetric_rig/system_gases.py +++ b/plankton_emulators/volumetric_rig/system_gases.py @@ -12,11 +12,11 @@ def gas_by_index(self, index): def gas_by_name(self, name): return self._get_by_method(name, "name") - def _get_by_method(self, value, method): + def _get_by_method(self, value, attr): try: - return next(g for g in self.gases if getattr(Gas, method) == value) + return next(g for g in self.gases if getattr(g, attr) == value) except StopIteration: return None def _add_gases(self, iterable): - self.gases += {g for g in iterable if isinstance(g, Gas)} + self.gases = set.union(self.gases, {g for g in iterable if isinstance(g, Gas)}) diff --git a/plankton_emulators/volumetric_rig/two_gas_mixer.py b/plankton_emulators/volumetric_rig/two_gas_mixer.py index 09b9f2f7..f8cb2373 100644 --- a/plankton_emulators/volumetric_rig/two_gas_mixer.py +++ b/plankton_emulators/volumetric_rig/two_gas_mixer.py @@ -1,9 +1,9 @@ class TwoGasMixer(object): def __init__(self): - self.mixable = {} + self.mixable = set() def add_mixable(self, gas1, gas2): - self.mixable.add({gas1, gas2}) + self.mixable.add(frozenset([gas1, gas2])) def can_mix(self, gas1, gas2): - return {gas1, gas2} in self.mixable + return frozenset([gas1, gas2]) in self.mixable From 17594193aa244e46f5a7a1b745f81395e3fd1f78 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 09:54:47 +0000 Subject: [PATCH 0039/1466] Handle non-normal input formats --- .../interfaces/stream_interface.py | 163 +++++++++++------- 1 file changed, 104 insertions(+), 59 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 2d1a2f31..e8d733b8 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -5,31 +5,42 @@ class VolumetricRigStreamInterface(StreamAdapter): + # The rig typically splits a command by whitespace and then uses the arguments it needs and then ignores the rest + # so "IDN" will respond as "IDN BLAH BLAH BLAH" and "BCS 01" would be the same has "BCS 01 02 03". + # Some commands that take input will respond with default (often invalid) parameters if not present. For example + # "BCS" is the same as "BCS 00" and also "BCS AA". commands = { - Cmd("get_identity", "^IDN$"), - Cmd("get_identity", "^\?$"), - Cmd("get_buffer_control_and_status", "^BCS\s([a-zA-Z0-9_.-]+)$"), - Cmd("get_ethernet_and_hmi_status", "^ETN$"), - Cmd("get_gas_control_and_status", "^GCS$"), - Cmd("get_gas_mix_matrix", "^GMM$"), - Cmd("gas_mix_check", "^GMC\s([0-9]+)\s([0-9]+)$"), - Cmd("get_gas_number_available", "^GNA$"), - Cmd("get_hmi_status", "^HMI$"), - Cmd("get_hmi_count_cycles", "^HMC$"), - Cmd("get_memory_location", "^RDM\s([0-9]+)$"), - Cmd("get_pressure_and_temperature_status", "^PTS$"), - Cmd("get_pressures", "^PMV$"), - Cmd("get_temperatures", "^TMV$"), - Cmd("get_ports_and_relays_hex", "^PTR$"), - Cmd("get_ports_output", "^POT$"), - Cmd("get_ports_input", "^PIN$"), - Cmd("get_ports_relays", "^PRY$"), - Cmd("get_system_status", "^STS$"), - Cmd("get_com_activity", "^COM$"), - Cmd("get_valve_status", "^VST$"), - Cmd("set_valve_open", "^OPV\s([0-9]+)$"), - Cmd("set_valve_closed", "^CLV\s([0-9]+)$"), - Cmd("halt", "^HLT$"), + #Cmd("get_identity", "^IDN$"), + Cmd("get_identity", "^IDN(\s.*)?$"), + Cmd("get_identity", "^\?(\s.*)?$"), + Cmd("get_buffer_control_and_status", "^BCS$"), + Cmd("get_buffer_control_and_status", "^BCS\s(\S*).*$"), + Cmd("get_ethernet_and_hmi_status", "^ETN(\s.*)?$"), + Cmd("get_gas_control_and_status", "^GCS(\s.*)?$"), + Cmd("get_gas_mix_matrix", "^GMM(\s.*)?$"), + Cmd("gas_mix_check", "^GMC$"), + Cmd("gas_mix_check", "^GMC\s(\S*)$"), + Cmd("gas_mix_check", "^GMC\s(\S*)\s(\S*).*$"), + Cmd("get_gas_number_available", "^GNA(\s.*)?$"), + Cmd("get_hmi_status", "^HMI(\s.*)?$"), + Cmd("get_hmi_count_cycles", "^HMC(\s.*)?$"), + Cmd("get_memory_location", "^RDM"), + Cmd("get_memory_location", "^RDM\s(\S*).*$"), + Cmd("get_pressure_and_temperature_status", "^PTS(\s.*)?$"), + Cmd("get_pressures", "^PMV(\s.*)?$"), + Cmd("get_temperatures", "^TMV(\s.*)?$"), + Cmd("get_ports_and_relays_hex", "^PTR(\s.*)?$"), + Cmd("get_ports_output", "^POT(\s.*)?$"), + Cmd("get_ports_input", "^PIN(\s.*)?$"), + Cmd("get_ports_relays", "^PRY(\s.*)?$"), + Cmd("get_system_status", "^STS(\s.*)?$"), + Cmd("get_com_activity", "^COM(\s.*)?$"), + Cmd("get_valve_status", "^VST(\s.*)?$"), + Cmd("set_valve_open", "^OPV$"), + Cmd("set_valve_open", "^OPV\s(\S*).*$"), + Cmd("set_valve_closed", "^CLV.*$"), + Cmd("set_valve_closed", "^CLV\s(\S*).*$"), + Cmd("halt", "^HLT(\s.*)?$"), } in_terminator = "\r\n" @@ -41,10 +52,10 @@ def __init__(self, device, arguments=None): self.gas_output_length = 20 super(VolumetricRigStreamInterface, self).__init__(device, arguments) - def get_identity(self): + def get_identity(self, dummy): return "IDN,00," + self.rig.identify() - def get_buffer_control_and_status(self, buffer_number_string): + def get_buffer_control_and_status(self, buffer_number_string="0"): try: buffer_number = int(buffer_number_string) except ValueError: @@ -53,7 +64,7 @@ def get_buffer_control_and_status(self, buffer_number_string): message_prefix = "BCS" num_length = 3 - error_message_prefix = " ".join(["Buffer",message_prefix,str(buffer_number)[:num_length].zfill(num_length)]) + error_message_prefix = " ".join([message_prefix, "Buffer", str(buffer_number)[:num_length].zfill(num_length)]) buffer_too_low = " ".join([error_message_prefix,"Too Low"]) buffer_too_high = " ".join([error_message_prefix + "Too High"]) @@ -75,7 +86,7 @@ def get_buffer_control_and_status(self, buffer_number_string): buff.system_gas.name ]) - def get_ethernet_and_hmi_status(self): + def get_ethernet_and_hmi_status(self, dummy): # The syntax of the return string is odd: the separators are not consistent return " ".join([ "ETN:PLC", @@ -84,7 +95,7 @@ def get_ethernet_and_hmi_status(self): "," + self.rig.hmi.ip ]) - def get_gas_control_and_status(self): + def get_gas_control_and_status(self, dummy): lines = list() lines.append("No No Buffer E O No System") for buff in self.rig.buffers: @@ -95,12 +106,12 @@ def get_gas_control_and_status(self): words.append("E" if buff.valve.is_enabled else "d") words.append("O" if buff.valve.is_open else "c") words.append(buff.system_gas.index_string()) - words.append(buff.system_gas().name) + words.append(buff.system_gas.name) lines.append(" ".join(words)) lines.append("GCS") - return '\n'.join(lines) + return '\r\n'.join(lines) - def get_gas_mix_matrix(self): + def get_gas_mix_matrix(self, dummy): # Gather data system_gases = self.rig.system_gases.gases @@ -113,10 +124,10 @@ def get_gas_mix_matrix(self): # Put data in output format lines = list() # Add column headers - for i in range(max(column_headers, key=len)): + for i in range(len(max(column_headers, key=len))): words = list() # For the top-left block of white space - words.append((max(row_titles, key=len)-1)*" ") + words.append((len(max(row_titles, key=len))-1)*" ") # Vertically aligned gas names for j in range(len(column_headers)): words.append(column_headers[j][i]) @@ -131,31 +142,44 @@ def get_gas_mix_matrix(self): # Add footer lines.append("GMM allowance limit: " + str(len(system_gases))) - return '\n'.join(lines) + return '\r\n'.join(lines) + + def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): + try: + gas1_index = int(gas1_index_raw) + except ValueError: + gas1_index = 0 + try: + gas2_index = int(gas2_index_raw) + except ValueError: + gas2_index = 0 - def gas_mix_check(self, gas1_index, gas2_index): gas1 = self.rig.system_gases.gas_by_index(gas1_index) gas2 = self.rig.system_gases.gas_by_index(gas2_index) return ' '.join(["GMC", - gas1.index_string(), gas1.name(self.gas_output_length, '.'), - gas2.index_string(), gas2.name(self.gas_output_length, '.'), + gas1.index_string(), gas1.pad_name(self.gas_output_length, '.'), + gas2.index_string(), gas2.pad_name(self.gas_output_length, '.'), "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) - def get_gas_number_available(self): + def get_gas_number_available(self, dummy): return len(self.rig.system_gases) - def get_hmi_status(self): + def get_hmi_status(self, dummy): hmi = self.rig.hmi return " ".join(["HMI " + hmi.status + " ", hmi.ip, "B", hmi.base_page_string(), "S", hmi.sub_page_string()]) - def get_hmi_count_cycles(self): + def get_hmi_count_cycles(self, dummy): return " ".join(["HMC"] + self.rig.hmi.count_cycles) - def get_memory_location(self, location): + def get_memory_location(self, location_raw="0"): + try: + location = int(location_raw) + except ValueError: + location = 0 return " ".join(["RDM", location.zfill(4), self.rig.memory_location(location)]) - def get_pressure_and_temperature_status(self): + def get_pressure_and_temperature_status(self, dummy): def get_status_code(s): if s == SensorStatus.DISABLED: @@ -177,18 +201,18 @@ def get_status_code(s): "".join([get_status_code(self.rig.pressure_sensors[i].status) for i in reversed(range(len(self.rig.pressure_sensors)))]) - def get_pressures(self): + def get_pressures(self, dummy): return " ".join(["PMV"] + [self.rig.pressure_sensors[i].pressure for i in reversed(range(len(self.rig.pressure_sensors)))] + ["T", self.rig.target_pressure]) - def get_temperatures(self): + def get_temperatures(self, dummy): return " ".join(["TMV"] + [self.rig.temperature_sensors[i].temperature for i in reversed(range(len(self.rig.temperature_sensors)))]) - def get_valve_status(self): + def get_valve_status(self, dummy): valves = [self.rig.supply_valve, self.rig.vacuum_extract_valve, self.rig.cell_valve] + \ [b.valve for b in self.rig.buffers.reverse()] @@ -206,7 +230,22 @@ def derive_status(valve): return "VST Valve Status " + "".join([derive_status(v) for v in valves]) - def _set_valve_status(self, buffer_number, set_to_open): + def _set_valve_status(self, buffer_number_raw, set_to_open): + try: + buffer_number = int(buffer_number_raw) + except TypeError: + buffer_number = 0 + + message_prefix = " ".join([ + "OPV" if set_to_open else "CLV", + "Value", + str(buffer_number) + ]) + if buffer_number <= 0: + return message_prefix + " Too Low" + elif buffer_number > len(self.rig.buffers): + return message_prefix + " Too High" + if self.rig.halted: return "CLV Rejected only allowed when running" else: @@ -227,13 +266,13 @@ def derive_status(is_open): "was", derive_status(new_status)]) - def set_valve_closed(self, buffer_number): - self._set_valve_status(buffer_number, False) + def set_valve_closed(self, buffer_number_raw): + self._set_valve_status(buffer_number_raw, False) - def set_valve_open(self, buffer_number): - self._set_valve_status(buffer_number, True) + def set_valve_open(self, buffer_number_raw): + self._set_valve_status(buffer_number_raw, True) - def halt(self): + def halt(self, dummy): if self.rig.halted: message = "SYSTEM ALREADY HALTED" else: @@ -242,7 +281,7 @@ def halt(self): message = "SYSTEM NOW HALTED" return "HLT *** " + message + " ***" - def get_system_status(self): + def get_system_status(self, dummy): return " ".join([ "STS", str(self.rig.status_code).zfill(2), @@ -256,17 +295,23 @@ def get_system_status(self): ]) # Information about ports, relays and com traffic are currently returned statically - def get_ports_and_relays_hex(self): + def get_ports_and_relays_hex(self, dummy): return "PTR I:00 0000 0000 R:0000 0200 0000 O:00 0000 4400" - def get_ports_output(self): + def get_ports_output(self, dummy): return "POT qwertyus vsbbbbbbzyxwvuts aBhecSssvsbbbbbb" - def get_ports_input(self): + def get_ports_input(self, dummy): return "PIN qwertyui zyxwvutsrqponmlk abcdefghijklmneb" - def get_ports_relays(self): + def get_ports_relays(self, dummy): return "PRY qwertyuiopasdfgh zyxwhmLsrqponmlk abcdefghihlbhace" - def get_com_activity(self): - return "COM ok 0113/0000" \ No newline at end of file + def get_com_activity(self, dummy): + return "COM ok 0113/0000" + + def handle_error(self, request, error): + if str(error) == "None of the device's commands matched.": + return "URC,04,Unrecognised Command," + str(request) + else: + print "An error occurred at request " + repr(request) + ": " + repr(error) \ No newline at end of file From faf022640588c442770f2942f122e10894a58476 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 10:31:50 +0000 Subject: [PATCH 0040/1466] Use non-capturing groups to avoid the need for dummy variables --- .../interfaces/stream_interface.py | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index e8d733b8..c849634d 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -11,36 +11,36 @@ class VolumetricRigStreamInterface(StreamAdapter): # "BCS" is the same as "BCS 00" and also "BCS AA". commands = { #Cmd("get_identity", "^IDN$"), - Cmd("get_identity", "^IDN(\s.*)?$"), - Cmd("get_identity", "^\?(\s.*)?$"), + Cmd("get_identity", "^IDN(?: .*)?$"), + Cmd("get_identity", "^\?(?: .*)?$"), Cmd("get_buffer_control_and_status", "^BCS$"), Cmd("get_buffer_control_and_status", "^BCS\s(\S*).*$"), - Cmd("get_ethernet_and_hmi_status", "^ETN(\s.*)?$"), - Cmd("get_gas_control_and_status", "^GCS(\s.*)?$"), - Cmd("get_gas_mix_matrix", "^GMM(\s.*)?$"), + Cmd("get_ethernet_and_hmi_status", "^ETN(?: .*)?$"), + Cmd("get_gas_control_and_status", "^GCS(?: .*)?$"), + Cmd("get_gas_mix_matrix", "^GMM(?: .*)?$"), Cmd("gas_mix_check", "^GMC$"), Cmd("gas_mix_check", "^GMC\s(\S*)$"), Cmd("gas_mix_check", "^GMC\s(\S*)\s(\S*).*$"), - Cmd("get_gas_number_available", "^GNA(\s.*)?$"), - Cmd("get_hmi_status", "^HMI(\s.*)?$"), - Cmd("get_hmi_count_cycles", "^HMC(\s.*)?$"), + Cmd("get_gas_number_available", "^GNA(?: .*)?$"), + Cmd("get_hmi_status", "^HMI(?: .*)?$"), + Cmd("get_hmi_count_cycles", "^HMC(?: .*)?$"), Cmd("get_memory_location", "^RDM"), Cmd("get_memory_location", "^RDM\s(\S*).*$"), - Cmd("get_pressure_and_temperature_status", "^PTS(\s.*)?$"), - Cmd("get_pressures", "^PMV(\s.*)?$"), - Cmd("get_temperatures", "^TMV(\s.*)?$"), - Cmd("get_ports_and_relays_hex", "^PTR(\s.*)?$"), - Cmd("get_ports_output", "^POT(\s.*)?$"), - Cmd("get_ports_input", "^PIN(\s.*)?$"), - Cmd("get_ports_relays", "^PRY(\s.*)?$"), - Cmd("get_system_status", "^STS(\s.*)?$"), - Cmd("get_com_activity", "^COM(\s.*)?$"), - Cmd("get_valve_status", "^VST(\s.*)?$"), + Cmd("get_pressure_and_temperature_status", "^PTS(?: .*)?$"), + Cmd("get_pressures", "^PMV(?: .*)?$"), + Cmd("get_temperatures", "^TMV(?: .*)?$"), + Cmd("get_ports_and_relays_hex", "^PTR(?: .*)?$"), + Cmd("get_ports_output", "^POT(?: .*)?$"), + Cmd("get_ports_input", "^PIN(?: .*)?$"), + Cmd("get_ports_relays", "^PRY(?: .*)?$"), + Cmd("get_system_status", "^STS(?: .*)?$"), + Cmd("get_com_activity", "^COM(?: .*)?$"), + Cmd("get_valve_status", "^VST(?: .*)?$"), Cmd("set_valve_open", "^OPV$"), Cmd("set_valve_open", "^OPV\s(\S*).*$"), Cmd("set_valve_closed", "^CLV.*$"), Cmd("set_valve_closed", "^CLV\s(\S*).*$"), - Cmd("halt", "^HLT(\s.*)?$"), + Cmd("halt", "^HLT(?: .*)?$"), } in_terminator = "\r\n" @@ -52,7 +52,7 @@ def __init__(self, device, arguments=None): self.gas_output_length = 20 super(VolumetricRigStreamInterface, self).__init__(device, arguments) - def get_identity(self, dummy): + def get_identity(self): return "IDN,00," + self.rig.identify() def get_buffer_control_and_status(self, buffer_number_string="0"): @@ -86,7 +86,7 @@ def get_buffer_control_and_status(self, buffer_number_string="0"): buff.system_gas.name ]) - def get_ethernet_and_hmi_status(self, dummy): + def get_ethernet_and_hmi_status(self): # The syntax of the return string is odd: the separators are not consistent return " ".join([ "ETN:PLC", @@ -95,11 +95,12 @@ def get_ethernet_and_hmi_status(self, dummy): "," + self.rig.hmi.ip ]) - def get_gas_control_and_status(self, dummy): + def get_gas_control_and_status(self): lines = list() lines.append("No No Buffer E O No System") for buff in self.rig.buffers: words = list() + words.append("") words.append(buff.index_string()) words.append(buff.buffer_gas.index_string()) words.append(buff.buffer_gas.pad_name(self.gas_output_length, ' ')) @@ -111,7 +112,7 @@ def get_gas_control_and_status(self, dummy): lines.append("GCS") return '\r\n'.join(lines) - def get_gas_mix_matrix(self, dummy): + def get_gas_mix_matrix(self): # Gather data system_gases = self.rig.system_gases.gases @@ -161,15 +162,15 @@ def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): gas2.index_string(), gas2.pad_name(self.gas_output_length, '.'), "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) - def get_gas_number_available(self, dummy): + def get_gas_number_available(self): return len(self.rig.system_gases) - def get_hmi_status(self, dummy): + def get_hmi_status(self): hmi = self.rig.hmi return " ".join(["HMI " + hmi.status + " ", hmi.ip, "B", hmi.base_page_string(), "S", hmi.sub_page_string()]) - def get_hmi_count_cycles(self, dummy): + def get_hmi_count_cycles(self): return " ".join(["HMC"] + self.rig.hmi.count_cycles) def get_memory_location(self, location_raw="0"): @@ -179,7 +180,7 @@ def get_memory_location(self, location_raw="0"): location = 0 return " ".join(["RDM", location.zfill(4), self.rig.memory_location(location)]) - def get_pressure_and_temperature_status(self, dummy): + def get_pressure_and_temperature_status(self): def get_status_code(s): if s == SensorStatus.DISABLED: @@ -201,18 +202,18 @@ def get_status_code(s): "".join([get_status_code(self.rig.pressure_sensors[i].status) for i in reversed(range(len(self.rig.pressure_sensors)))]) - def get_pressures(self, dummy): + def get_pressures(self): return " ".join(["PMV"] + [self.rig.pressure_sensors[i].pressure for i in reversed(range(len(self.rig.pressure_sensors)))] + ["T", self.rig.target_pressure]) - def get_temperatures(self, dummy): + def get_temperatures(self): return " ".join(["TMV"] + [self.rig.temperature_sensors[i].temperature for i in reversed(range(len(self.rig.temperature_sensors)))]) - def get_valve_status(self, dummy): + def get_valve_status(self): valves = [self.rig.supply_valve, self.rig.vacuum_extract_valve, self.rig.cell_valve] + \ [b.valve for b in self.rig.buffers.reverse()] @@ -272,7 +273,7 @@ def set_valve_closed(self, buffer_number_raw): def set_valve_open(self, buffer_number_raw): self._set_valve_status(buffer_number_raw, True) - def halt(self, dummy): + def halt(self): if self.rig.halted: message = "SYSTEM ALREADY HALTED" else: @@ -281,7 +282,7 @@ def halt(self, dummy): message = "SYSTEM NOW HALTED" return "HLT *** " + message + " ***" - def get_system_status(self, dummy): + def get_system_status(self): return " ".join([ "STS", str(self.rig.status_code).zfill(2), @@ -295,19 +296,19 @@ def get_system_status(self, dummy): ]) # Information about ports, relays and com traffic are currently returned statically - def get_ports_and_relays_hex(self, dummy): + def get_ports_and_relays_hex(self): return "PTR I:00 0000 0000 R:0000 0200 0000 O:00 0000 4400" - def get_ports_output(self, dummy): + def get_ports_output(self): return "POT qwertyus vsbbbbbbzyxwvuts aBhecSssvsbbbbbb" - def get_ports_input(self, dummy): + def get_ports_input(self): return "PIN qwertyui zyxwvutsrqponmlk abcdefghijklmneb" - def get_ports_relays(self, dummy): + def get_ports_relays(self): return "PRY qwertyuiopasdfgh zyxwhmLsrqponmlk abcdefghihlbhace" - def get_com_activity(self, dummy): + def get_com_activity(self): return "COM ok 0113/0000" def handle_error(self, request, error): From ca272ac42816278dff196fbf30e75df4a539a95c Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 10:34:08 +0000 Subject: [PATCH 0041/1466] Better way of accessing names from pair --- plankton_emulators/volumetric_rig/device.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 4e78db7c..e6d58d4b 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -19,16 +19,13 @@ def __init__(self): # Set mixable gases self.mixer = TwoGasMixer() - for pair in SeedGasData.mixable_gas_names(): - name1, name2 = pair - self.mixer.add_mixable( - self.system_gases.gas_by_name(name1), - self.system_gases.gas_by_name(name2)) + for name1, name2 in SeedGasData.mixable_gas_names(): + self.mixer.add_mixable(self.system_gases.gas_by_name(name1), self.system_gases.gas_by_name(name2)) # Set buffers - buffer_gases = [(self.system_gases.gas_by_name(next(iter(pair))), - self.system_gases.gas_by_name(next(iter(pair)))) - for pair in SeedGasData.buffer_gas_names()] + buffer_gases = [(self.system_gases.gas_by_name(name1), + self.system_gases.gas_by_name(name2)) + for name1, name2 in SeedGasData.buffer_gas_names()] self.buffers = [Buffer(i+1, buffer_gases[i][0], buffer_gases[i][1]) for i in range(len(buffer_gases))] From 2aab98327d0a729c651ba160271fe9eff7557619 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 10:51:31 +0000 Subject: [PATCH 0042/1466] Start hiding class variables behind method to prevent exposing implementation details between device and interface --- plankton_emulators/volumetric_rig/device.py | 39 ++++++++++++++++--- .../interfaces/stream_interface.py | 31 ++++++++------- 2 files changed, 50 insertions(+), 20 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index e6d58d4b..6a152a9e 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -30,8 +30,8 @@ def __init__(self): for i in range(len(buffer_gases))] # Set ethernet devices - self.plc = EthernetDevice("192.168.0.1") - self.hmi = HmiDevice("192.168.0.2") + self._plc = EthernetDevice("192.168.0.1") + self._hmi = HmiDevice("192.168.0.2") # Set up sensors self.temperature_sensors = [TemperatureSensor() for i in range(9)] @@ -46,7 +46,7 @@ def __init__(self): self.cell_valve = Valve() # Misc system state variables - self.halted = False + self._halted = False self.status_code = 2 self.errors = ErrorStates() @@ -65,8 +65,35 @@ def buffer(self,i): except StopIteration: return None - def memory_location(self, location): - return str(location).zfill(6) + def memory_location(self, location, as_string, length): + return self._optional_int_string_format(location, as_string, length) def halt(self): - self.halted = True \ No newline at end of file + self._halted = True + + def plc_ip(self): + return self._plc.ip + + def hmi_ip(self): + return self._hmi.ip + + def hmi_status(self): + return self._hmi.status + + def hmi_base_page(self, as_string=False, length=None): + return self._optional_int_string_format(self._hmi.base_page, as_string, length) + + def hmi_sub_page(self, as_string=False, length=None): + return self._optional_int_string_format(self._hmi.sub_page, as_string, length) + + def hmi_count_cycles(self): + return self._hmi.count_cycles + + def _optional_int_string_format(self, int, as_string, length): + if as_string: + return str(int) if length is not None else int[:length].zfill(length) + else: + return int + + def halted(self): + return self._halted diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index c849634d..205c64c6 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -24,8 +24,7 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("get_gas_number_available", "^GNA(?: .*)?$"), Cmd("get_hmi_status", "^HMI(?: .*)?$"), Cmd("get_hmi_count_cycles", "^HMC(?: .*)?$"), - Cmd("get_memory_location", "^RDM"), - Cmd("get_memory_location", "^RDM\s(\S*).*$"), + Cmd("get_memory_location", "^RDM(?: .*)?"), Cmd("get_pressure_and_temperature_status", "^PTS(?: .*)?$"), Cmd("get_pressures", "^PMV(?: .*)?$"), Cmd("get_temperatures", "^TMV(?: .*)?$"), @@ -90,9 +89,9 @@ def get_ethernet_and_hmi_status(self): # The syntax of the return string is odd: the separators are not consistent return " ".join([ "ETN:PLC", - self.rig.plc.ip + ",HMI", - self.rig.hmi.status, - "," + self.rig.hmi.ip + self.rig.plc_ip() + ",HMI", + self.rig.hmi_status(), + "," + self.rig.hmi_ip() ]) def get_gas_control_and_status(self): @@ -166,19 +165,23 @@ def get_gas_number_available(self): return len(self.rig.system_gases) def get_hmi_status(self): - hmi = self.rig.hmi - return " ".join(["HMI " + hmi.status + " ", - hmi.ip, "B", hmi.base_page_string(), "S", hmi.sub_page_string()]) + return " ".join(["HMI " + self.rig.hmi_status() + " ", + self.rig.hmi_ip(), + "B", + self.rig.hmi_base_page(as_string=True, length=4), + "S", + self.rig.hmi_sub_page(as_string=True, length=3)]) def get_hmi_count_cycles(self): - return " ".join(["HMC"] + self.rig.hmi.count_cycles) + return " ".join(["HMC"] + self.rig.hmi.count_cycles()) def get_memory_location(self, location_raw="0"): try: location = int(location_raw) except ValueError: location = 0 - return " ".join(["RDM", location.zfill(4), self.rig.memory_location(location)]) + return " ".join(["RDM", location[:4].zfill(4), + self.rig.memory_location(location, as_string=True, length=6)]) def get_pressure_and_temperature_status(self): @@ -247,7 +250,7 @@ def _set_valve_status(self, buffer_number_raw, set_to_open): elif buffer_number > len(self.rig.buffers): return message_prefix + " Too High" - if self.rig.halted: + if self.rig.halted(): return "CLV Rejected only allowed when running" else: valve = self.rig.buffer(buffer_number).valve @@ -274,11 +277,11 @@ def set_valve_open(self, buffer_number_raw): self._set_valve_status(buffer_number_raw, True) def halt(self): - if self.rig.halted: + if self.rig.halted(): message = "SYSTEM ALREADY HALTED" else: self.rig.halt() - assert self.rig.halted + assert self.rig.halted() message = "SYSTEM NOW HALTED" return "HLT *** " + message + " ***" @@ -291,7 +294,7 @@ def get_system_status(self): # Spelling error duplicated as on device "GUAGES" if self.rig.errors.gauges else "guages", "COMMS" if self.rig.errors.comms else "comms", - "HLT" if self.rig.halted else "halted", + "HLT" if self.rig.halted() else "halted", "E-STOP" if self.rig.errors.estop else "estop" ]) From c2163a3bff010ca3060623462541fb47a7551113 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 11:06:38 +0000 Subject: [PATCH 0043/1466] Further abstraction --- plankton_emulators/volumetric_rig/device.py | 32 +++++++++++----- .../interfaces/stream_interface.py | 38 ++++++++----------- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 6a152a9e..d4427a0e 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -34,11 +34,8 @@ def __init__(self): self._hmi = HmiDevice("192.168.0.2") # Set up sensors - self.temperature_sensors = [TemperatureSensor() for i in range(9)] - self.pressure_sensors = [TemperatureSensor() for i in range(5)] - - # Target pressure: We can't set this via serial - self.target_pressure = 12.34 + self._temperature_sensors = [TemperatureSensor() for i in range(9)] + self._pressure_sensors = [TemperatureSensor() for i in range(5)] # Set up special valves self.supply_valve = Valve() @@ -47,9 +44,12 @@ def __init__(self): # Misc system state variables self._halted = False - self.status_code = 2 + self._status_code = 2 self.errors = ErrorStates() + # Target pressure: We can't set this via serial + self._target_pressure = 12.34 + # Parent constructor super(SimulatedVolumetricRig, self).__init__() @@ -89,11 +89,23 @@ def hmi_sub_page(self, as_string=False, length=None): def hmi_count_cycles(self): return self._hmi.count_cycles + def halted(self): + return self._halted + + def pressure_sensors(self, reverse=False): + return self._pressure_sensors if not reverse else self._pressure_sensors.reverse() + + def temperature_sensors(self, reverse=False): + return self._temperature_sensors if not reverse else self._temperature_sensors.reverse() + + def target_pressure(self): + return self._target_pressure + + def status_code(self, as_string=False, length=None): + return self._optional_int_string_format(2, as_string, length) + def _optional_int_string_format(self, int, as_string, length): if as_string: return str(int) if length is not None else int[:length].zfill(length) else: - return int - - def halted(self): - return self._halted + return int \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 205c64c6..daf57df7 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -10,7 +10,6 @@ class VolumetricRigStreamInterface(StreamAdapter): # Some commands that take input will respond with default (often invalid) parameters if not present. For example # "BCS" is the same as "BCS 00" and also "BCS AA". commands = { - #Cmd("get_identity", "^IDN$"), Cmd("get_identity", "^IDN(?: .*)?$"), Cmd("get_identity", "^\?(?: .*)?$"), Cmd("get_buffer_control_and_status", "^BCS$"), @@ -64,8 +63,8 @@ def get_buffer_control_and_status(self, buffer_number_string="0"): message_prefix = "BCS" num_length = 3 error_message_prefix = " ".join([message_prefix, "Buffer", str(buffer_number)[:num_length].zfill(num_length)]) - buffer_too_low = " ".join([error_message_prefix,"Too Low"]) - buffer_too_high = " ".join([error_message_prefix + "Too High"]) + buffer_too_low = " ".join([error_message_prefix, "Too Low"]) + buffer_too_high = " ".join([error_message_prefix, "Too High"]) if buffer_number <= 0: return buffer_too_low @@ -112,7 +111,6 @@ def get_gas_control_and_status(self): return '\r\n'.join(lines) def get_gas_mix_matrix(self): - # Gather data system_gases = self.rig.system_gases.gases @@ -173,7 +171,7 @@ def get_hmi_status(self): self.rig.hmi_sub_page(as_string=True, length=3)]) def get_hmi_count_cycles(self): - return " ".join(["HMC"] + self.rig.hmi.count_cycles()) + return " ".join(["HMC"] + self.rig.hmi_count_cycles()) def get_memory_location(self, location_raw="0"): try: @@ -185,36 +183,32 @@ def get_memory_location(self, location_raw="0"): def get_pressure_and_temperature_status(self): - def get_status_code(s): - if s == SensorStatus.DISABLED: + def get_status_code(status): + if status == SensorStatus.DISABLED: return "D" - elif s == SensorStatus.NO_REPLY: + elif status == SensorStatus.NO_REPLY: return "X" - elif s == SensorStatus.VALUE_IN_RANGE: + elif status == SensorStatus.VALUE_IN_RANGE: return "O" - elif s == SensorStatus.VALUE_TOO_LOW: + elif status == SensorStatus.VALUE_TOO_LOW: return "L" - elif s == SensorStatus.VALUE_TOO_HIGH: + elif status == SensorStatus.VALUE_TOO_HIGH: return "H" else: return "?" return "PTS " + \ - "".join([get_status_code(self.rig.temperature_sensors[i].status) - for i in reversed(range(len(self.rig.temperature_sensors)))]) + \ - "".join([get_status_code(self.rig.pressure_sensors[i].status) - for i in reversed(range(len(self.rig.pressure_sensors)))]) + "".join([get_status_code(s.status) for s in self.rig.pressure_sensors(reverse=True)]) + \ + "".join([get_status_code(s.status) for s in self.rig.temperature_sensors(reverse=True)]) def get_pressures(self): return " ".join(["PMV"] + - [self.rig.pressure_sensors[i].pressure - for i in reversed(range(len(self.rig.pressure_sensors)))] + - ["T", self.rig.target_pressure]) + [p.pressure for p in self.rig.pressure_sensors(reverse=True)] + + ["T", self.rig.target_pressure()]) def get_temperatures(self): return " ".join(["TMV"] + - [self.rig.temperature_sensors[i].temperature - for i in reversed(range(len(self.rig.temperature_sensors)))]) + [t.temperature for t in self.rig.temperature_sensors(reverse=True)]) def get_valve_status(self): valves = [self.rig.supply_valve, self.rig.vacuum_extract_valve, self.rig.cell_valve] + \ @@ -265,7 +259,7 @@ def derive_status(is_open): return " ".join([ "OPV" if set_to_open else "CLV", "Valve Buffer", - buffer_number.lstrip("0"), + str(buffer_number), derive_status(original_status), "was", derive_status(new_status)]) @@ -288,7 +282,7 @@ def halt(self): def get_system_status(self): return " ".join([ "STS", - str(self.rig.status_code).zfill(2), + self.rig.status_code(as_string=True, length=2), "STOP" if self.rig.errors.run else "run", "HMI" if self.rig.errors.hmi else "hmi", # Spelling error duplicated as on device From 112cbde9e69c3fbb4b3bdc5b4df8a6982151ef14 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 11:30:59 +0000 Subject: [PATCH 0044/1466] Further abstraction --- plankton_emulators/volumetric_rig/buffer.py | 16 +-- plankton_emulators/volumetric_rig/device.py | 64 ++++++----- plankton_emulators/volumetric_rig/gas.py | 14 +-- .../interfaces/stream_interface.py | 101 +++++++++--------- .../volumetric_rig/utilities.py | 9 ++ 5 files changed, 117 insertions(+), 87 deletions(-) create mode 100644 plankton_emulators/volumetric_rig/utilities.py diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 7ab264b2..6fa159bf 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -1,12 +1,16 @@ from valve import Valve +from utilities import optional_int_string_format class Buffer(object): def __init__(self, index, buffer_gas, system_gas): - self.buffer_gas = buffer_gas - self.system_gas = system_gas - self.index = index - self.valve = Valve() + self._buffer_gas = buffer_gas + self._system_gas = system_gas + self._index = index + self._valve = Valve() - def index_string(self, length=1): - return str(self.index).zfill(length) + def index(self, as_string=False, length=1): + return optional_int_string_format(self._index, as_string, length) + + def valve(self): + return self._valve \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index d4427a0e..de99bc5f 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -7,9 +7,10 @@ from ethernet_device import EthernetDevice from hmi_device import HmiDevice from temperature_sensor import TemperatureSensor +from pressure_sensor import PressureSensor from valve import Valve from error_states import ErrorStates -from time import sleep +from utilities import optional_int_string_format class SimulatedVolumetricRig(Device): @@ -26,26 +27,26 @@ def __init__(self): buffer_gases = [(self.system_gases.gas_by_name(name1), self.system_gases.gas_by_name(name2)) for name1, name2 in SeedGasData.buffer_gas_names()] - self.buffers = [Buffer(i+1, buffer_gases[i][0], buffer_gases[i][1]) - for i in range(len(buffer_gases))] + self._buffers = [Buffer(i + 1, buffer_gases[i][0], buffer_gases[i][1]) + for i in range(len(buffer_gases))] # Set ethernet devices self._plc = EthernetDevice("192.168.0.1") self._hmi = HmiDevice("192.168.0.2") # Set up sensors - self._temperature_sensors = [TemperatureSensor() for i in range(9)] - self._pressure_sensors = [TemperatureSensor() for i in range(5)] + self._temperature_sensors = [TemperatureSensor() for _ in range(9)] + self._pressure_sensors = [PressureSensor() for _ in range(5)] # Set up special valves - self.supply_valve = Valve() - self.vacuum_extract_valve = Valve() - self.cell_valve = Valve() + self._supply_valve = Valve() + self._vacuum_extract_valve = Valve() + self._cell_valve = Valve() # Misc system state variables self._halted = False self._status_code = 2 - self.errors = ErrorStates() + self._errors = ErrorStates() # Target pressure: We can't set this via serial self._target_pressure = 12.34 @@ -56,20 +57,17 @@ def __init__(self): def identify(self): return "ISIS Volumetric Gas Handing Panel" - def count_buffers(self): - return len(self.buffers) + def buffer_count(self): + return len(self._buffers) - def buffer(self,i): + def buffer(self, i): try: - return next(b for b in self.buffers if b.index == i) + return next(b for b in self._buffers if b.index() == i) except StopIteration: return None def memory_location(self, location, as_string, length): - return self._optional_int_string_format(location, as_string, length) - - def halt(self): - self._halted = True + return optional_int_string_format(location, as_string, length) def plc_ip(self): return self._plc.ip @@ -81,10 +79,10 @@ def hmi_status(self): return self._hmi.status def hmi_base_page(self, as_string=False, length=None): - return self._optional_int_string_format(self._hmi.base_page, as_string, length) + return optional_int_string_format(self._hmi.base_page, as_string, length) def hmi_sub_page(self, as_string=False, length=None): - return self._optional_int_string_format(self._hmi.sub_page, as_string, length) + return optional_int_string_format(self._hmi.sub_page, as_string, length) def hmi_count_cycles(self): return self._hmi.count_cycles @@ -92,6 +90,9 @@ def hmi_count_cycles(self): def halted(self): return self._halted + def halt(self): + self._halted = True + def pressure_sensors(self, reverse=False): return self._pressure_sensors if not reverse else self._pressure_sensors.reverse() @@ -102,10 +103,23 @@ def target_pressure(self): return self._target_pressure def status_code(self, as_string=False, length=None): - return self._optional_int_string_format(2, as_string, length) + return optional_int_string_format(2, as_string, length) + + def errors(self): + return self._errors + + def valve_count(self): + return len(self.valves()) + + def valves(self): + return [self._supply_valve, self._vacuum_extract_valve, self._cell_valve] + \ + [b.valve for b in self._buffers.reverse()] + + def cell_valve(self): + return self._cell_valve + + def vacuum_valve(self): + return self._vacuum_extract_valve - def _optional_int_string_format(self, int, as_string, length): - if as_string: - return str(int) if length is not None else int[:length].zfill(length) - else: - return int \ No newline at end of file + def buffers(self): + return self._buffers diff --git a/plankton_emulators/volumetric_rig/gas.py b/plankton_emulators/volumetric_rig/gas.py index 0d4ecf36..6c12236a 100644 --- a/plankton_emulators/volumetric_rig/gas.py +++ b/plankton_emulators/volumetric_rig/gas.py @@ -1,15 +1,15 @@ from types import StringType, IntType +from utilities import pad_string, optional_int_string_format class Gas(object): def __init__(self,index,name): assert type(index) is IntType and type(name) is StringType - self.index = index - self.name = name + self._index = index + self._name = name - def pad_name(self, length=None, padding_character=" "): - return self.name if length is None \ - else self.name[:length] + (length-len(self.name))*padding_character + def name(self, length=None, padding_character=" "): + return pad_string(self._name, length, padding_character) - def index_string(self, length=2): - return str(self.index).zfill(length) \ No newline at end of file + def index(self, as_string=False, length=2): + return optional_int_string_format(self._index, as_string, length) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index daf57df7..67eac6f7 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -68,20 +68,20 @@ def get_buffer_control_and_status(self, buffer_number_string="0"): if buffer_number <= 0: return buffer_too_low - elif buffer_number > len(self.rig.buffers): + elif buffer_number > len(self.rig.buffers()): return buffer_too_high buff = self.rig.buffer(buffer_number) assert buff is not None return " ".join([ message_prefix, - buff.index_string(), - buff.buffer_gas.index_string(), - buff.buffer_gas.pad_name(self.gas_output_length, " "), - "E" if buff.valve.is_enabled else "d", - "O" if buff.valve.is_open else "c", - buff.system_gas.index_string(), - buff.system_gas.name + buff.index(as_string=True), + buff.buffer_gas.index(as_string=True), + buff.buffer_gas.name(self.gas_output_length, " "), + "E" if buff.valve().is_enabled else "d", + "O" if buff.valve().is_open else "c", + buff.system_gas.index(as_string=True), + buff.system_gas.name() ]) def get_ethernet_and_hmi_status(self): @@ -96,16 +96,16 @@ def get_ethernet_and_hmi_status(self): def get_gas_control_and_status(self): lines = list() lines.append("No No Buffer E O No System") - for buff in self.rig.buffers: + for buff in self.rig.buffers(): words = list() words.append("") - words.append(buff.index_string()) - words.append(buff.buffer_gas.index_string()) - words.append(buff.buffer_gas.pad_name(self.gas_output_length, ' ')) + words.append(buff.index(as_string=True)) + words.append(buff.buffer_gas.index(as_string=True)) + words.append(buff.buffer_gas.pad_name(self.gas_output_length, " ")) words.append("E" if buff.valve.is_enabled else "d") words.append("O" if buff.valve.is_open else "c") - words.append(buff.system_gas.index_string()) - words.append(buff.system_gas.name) + words.append(buff.system_gas.index(as_string=True)) + words.append(buff.system_gas.name()) lines.append(" ".join(words)) lines.append("GCS") return '\r\n'.join(lines) @@ -155,8 +155,8 @@ def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): gas1 = self.rig.system_gases.gas_by_index(gas1_index) gas2 = self.rig.system_gases.gas_by_index(gas2_index) return ' '.join(["GMC", - gas1.index_string(), gas1.pad_name(self.gas_output_length, '.'), - gas2.index_string(), gas2.pad_name(self.gas_output_length, '.'), + gas1.index(as_string=True), gas1.pad_name(self.gas_output_length, '.'), + gas2.index(as_string=True), gas2.pad_name(self.gas_output_length, '.'), "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): @@ -211,8 +211,6 @@ def get_temperatures(self): [t.temperature for t in self.rig.temperature_sensors(reverse=True)]) def get_valve_status(self): - valves = [self.rig.supply_valve, self.rig.vacuum_extract_valve, self.rig.cell_valve] + \ - [b.valve for b in self.rig.buffers.reverse()] def derive_status(valve): if valve.enabled() and valve.is_open(): @@ -226,43 +224,48 @@ def derive_status(valve): else: assert False - return "VST Valve Status " + "".join([derive_status(v) for v in valves]) + return "VST Valve Status " + "".join([derive_status(v) for v in self.rig.valves()]) def _set_valve_status(self, buffer_number_raw, set_to_open): try: - buffer_number = int(buffer_number_raw) + valve_number = int(buffer_number_raw) except TypeError: - buffer_number = 0 + valve_number = 0 + command = "OPV" if set_to_open else "CLV" message_prefix = " ".join([ - "OPV" if set_to_open else "CLV", + command, "Value", - str(buffer_number) + str(valve_number) ]) - if buffer_number <= 0: - return message_prefix + " Too Low" - elif buffer_number > len(self.rig.buffers): - return message_prefix + " Too High" - if self.rig.halted(): return "CLV Rejected only allowed when running" + elif valve_number <= 0: + return message_prefix + " Too Low" + elif valve_number <= self.rig.buffer_count(): + valve = self.rig.buffer(valve_number).valve + elif valve_number == self.rig.buffer_count() + 1: + valve = self.rig.cell_valve() + elif valve_number == self.rig.buffer_count() + 2: + valve = self.rig.vacuum_valve() else: - valve = self.rig.buffer(buffer_number).valve - original_status = valve.is_open - valve.open() if set_to_open else valve.close() - new_status = self.rig.buffer(buffer_number).valve.is_open - - def derive_status(is_open): - return "open" if is_open else "closed" - - # e.g. CLV Valve Buffer 1 closed was open - return " ".join([ - "OPV" if set_to_open else "CLV", - "Valve Buffer", - str(buffer_number), - derive_status(original_status), - "was", - derive_status(new_status)]) + return message_prefix + " Too High" + + original_status = valve.is_open + valve.open() if set_to_open else valve.close() + new_status = self.rig.buffer(valve_number).valve.is_open + + def derive_status(is_open): + return "open" if is_open else "closed" + + # e.g. CLV Valve Buffer 1 closed was open + return " ".join([ + command, + "Valve Buffer", + str(valve_number), + derive_status(original_status), + "was", + derive_status(new_status)]) def set_valve_closed(self, buffer_number_raw): self._set_valve_status(buffer_number_raw, False) @@ -283,13 +286,13 @@ def get_system_status(self): return " ".join([ "STS", self.rig.status_code(as_string=True, length=2), - "STOP" if self.rig.errors.run else "run", - "HMI" if self.rig.errors.hmi else "hmi", + "STOP" if self.rig.errors().run else "run", + "HMI" if self.rig.errors().hmi else "hmi", # Spelling error duplicated as on device - "GUAGES" if self.rig.errors.gauges else "guages", - "COMMS" if self.rig.errors.comms else "comms", + "GUAGES" if self.rig.errors().gauges else "guages", + "COMMS" if self.rig.errors().comms else "comms", "HLT" if self.rig.halted() else "halted", - "E-STOP" if self.rig.errors.estop else "estop" + "E-STOP" if self.rig.errors().estop else "estop" ]) # Information about ports, relays and com traffic are currently returned statically diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py new file mode 100644 index 00000000..82929b75 --- /dev/null +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -0,0 +1,9 @@ +def optional_int_string_format(int, as_string, length): + if as_string: + return str(int) if length is not None else int[:length].zfill(length) + else: + return int + + +def pad_string(s, length, padding_character): + return s if length is None else s[:length] + (length - len(s))*padding_character \ No newline at end of file From b9a2a2d4649c9d720b7f0d5034536d5ee78ddc0a Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 11:46:46 +0000 Subject: [PATCH 0045/1466] Improve valve open/close logic --- plankton_emulators/volumetric_rig/buffer.py | 10 ++++-- plankton_emulators/volumetric_rig/device.py | 34 ++++++++++++++++--- .../interfaces/stream_interface.py | 18 ++++++---- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 6fa159bf..d2fa3d1e 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -1,5 +1,6 @@ from valve import Valve from utilities import optional_int_string_format +from two_gas_mixer import TwoGasMixer class Buffer(object): @@ -12,5 +13,10 @@ def __init__(self, index, buffer_gas, system_gas): def index(self, as_string=False, length=1): return optional_int_string_format(self._index, as_string, length) - def valve(self): - return self._valve \ No newline at end of file + def open_valve(self, mixer): + assert isinstance(mixer, TwoGasMixer) + if mixer.can_mix(self._buffer_gas, self._system_gas): + self._valve.open() + + def close_valve(self): + self._valve.close() diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index de99bc5f..d56c9f60 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -115,11 +115,37 @@ def valves(self): return [self._supply_valve, self._vacuum_extract_valve, self._cell_valve] + \ [b.valve for b in self._buffers.reverse()] - def cell_valve(self): - return self._cell_valve + def buffer_valve_is_open(self, buffer_number): + buff = self.buffer(buffer_number) + return buff.valve().is_open if buff is not None else False - def vacuum_valve(self): - return self._vacuum_extract_valve + def vacuum_valve_is_open(self): + return self._vacuum_extract_valve.is_open + + def cell_valve_is_open(self): + return self._cell_valve.is_open + + def open_buffer_valve(self, buffer_number): + buff = self.buffer(buffer_number) + if buff is not None: + buff.open_valve(self.mixer) + + def open_cell_valve(self): + self._cell_valve.open() + + def open_vacuum_valve(self): + self._vacuum_extract_valve.open() + + def close_buffer_valve(self, buffer_number): + buff = self.buffer(buffer_number) + if buff is not None: + buff.close_valve() + + def close_cell_valve(self): + self._cell_valve.close() + + def close_vacuum_valve(self): + self._vacuum_extract_valve.close() def buffers(self): return self._buffers diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 67eac6f7..fba20699 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -243,17 +243,23 @@ def _set_valve_status(self, buffer_number_raw, set_to_open): elif valve_number <= 0: return message_prefix + " Too Low" elif valve_number <= self.rig.buffer_count(): - valve = self.rig.buffer(valve_number).valve + valve_status = self.rig.buffer_valve_is_open(valve_number) + open_valve = self.rig.open_buffer_valve(valve_number) + close_valve = self.rig.close_buffer_valve(valve_number) elif valve_number == self.rig.buffer_count() + 1: - valve = self.rig.cell_valve() + valve_status = self.rig.cell_valve_is_open() + open_valve = self.rig.open_cell_valve() + close_valve = self.rig.close_cell_valve() elif valve_number == self.rig.buffer_count() + 2: - valve = self.rig.vacuum_valve() + valve_status = self.rig.vacuum_valve_is_open() + open_valve = self.rig.open_vacuum_valve() + close_valve = self.rig.close_vacuum_valve() else: return message_prefix + " Too High" - original_status = valve.is_open - valve.open() if set_to_open else valve.close() - new_status = self.rig.buffer(valve_number).valve.is_open + original_status = valve_status() + open_valve() if set_to_open else close_valve() + new_status = valve_stauts() def derive_status(is_open): return "open" if is_open else "closed" From 87242c5ffd1bfb99799440a352fffd9385fffdae Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 11:50:12 +0000 Subject: [PATCH 0046/1466] Abstract buffer valve logic --- plankton_emulators/volumetric_rig/buffer.py | 6 ++++++ .../volumetric_rig/interfaces/stream_interface.py | 10 +++++----- plankton_emulators/volumetric_rig/sensor.py | 6 +++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index d2fa3d1e..db2a7568 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -20,3 +20,9 @@ def open_valve(self, mixer): def close_valve(self): self._valve.close() + + def valve_is_open(self): + return self._valve.is_open + + def valve_is_enabled(self): + return self._valve.is_enabled diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index fba20699..1cd00f0a 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -78,8 +78,8 @@ def get_buffer_control_and_status(self, buffer_number_string="0"): buff.index(as_string=True), buff.buffer_gas.index(as_string=True), buff.buffer_gas.name(self.gas_output_length, " "), - "E" if buff.valve().is_enabled else "d", - "O" if buff.valve().is_open else "c", + "E" if buff.valve_is_enabled() else "d", + "O" if buff.valve_is_open() else "c", buff.system_gas.index(as_string=True), buff.system_gas.name() ]) @@ -198,8 +198,8 @@ def get_status_code(status): return "?" return "PTS " + \ - "".join([get_status_code(s.status) for s in self.rig.pressure_sensors(reverse=True)]) + \ - "".join([get_status_code(s.status) for s in self.rig.temperature_sensors(reverse=True)]) + "".join([get_status_code(s.status()) for s in + self.rig.pressure_sensors(reverse=True)+self.rig.temperature_sensors(reverse=True)]) def get_pressures(self): return " ".join(["PMV"] + @@ -259,7 +259,7 @@ def _set_valve_status(self, buffer_number_raw, set_to_open): original_status = valve_status() open_valve() if set_to_open else close_valve() - new_status = valve_stauts() + new_status = valve_status() def derive_status(is_open): return "open" if is_open else "closed" diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py index c466f709..9a56f33b 100644 --- a/plankton_emulators/volumetric_rig/sensor.py +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -3,10 +3,10 @@ class Sensor(object): def __init__(self): - self.status = SensorStatus.UNKNOWN + self._status = SensorStatus.UNKNOWN def set_status(self, status): - self.status = status + self._status = status def get_status(self): - return self.status \ No newline at end of file + return self._status \ No newline at end of file From 372d7ee3650547806435c4122f608f6fcc9db488 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 12:00:13 +0000 Subject: [PATCH 0047/1466] Fix method calls --- plankton_emulators/volumetric_rig/buffer.py | 8 +++++++ .../interfaces/stream_interface.py | 23 +++++++++++-------- .../volumetric_rig/system_gases.py | 4 ++-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index db2a7568..69dab016 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -5,6 +5,8 @@ class Buffer(object): def __init__(self, index, buffer_gas, system_gas): + assert buffer_gas is not None + assert system_gas is not None self._buffer_gas = buffer_gas self._system_gas = system_gas self._index = index @@ -26,3 +28,9 @@ def valve_is_open(self): def valve_is_enabled(self): return self._valve.is_enabled + + def buffer_gas(self): + return self._buffer_gas + + def system_gas(self): + return self._system_gas \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 1cd00f0a..400816a1 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -76,12 +76,12 @@ def get_buffer_control_and_status(self, buffer_number_string="0"): return " ".join([ message_prefix, buff.index(as_string=True), - buff.buffer_gas.index(as_string=True), - buff.buffer_gas.name(self.gas_output_length, " "), + buff.buffer_gas().index(as_string=True), + buff.buffer_gas().name(self.gas_output_length, " "), "E" if buff.valve_is_enabled() else "d", "O" if buff.valve_is_open() else "c", - buff.system_gas.index(as_string=True), - buff.system_gas.name() + buff.system_gas().index(as_string=True), + buff.system_gas().name() ]) def get_ethernet_and_hmi_status(self): @@ -100,12 +100,12 @@ def get_gas_control_and_status(self): words = list() words.append("") words.append(buff.index(as_string=True)) - words.append(buff.buffer_gas.index(as_string=True)) - words.append(buff.buffer_gas.pad_name(self.gas_output_length, " ")) + words.append(buff.buffer_gas().index(as_string=True)) + words.append(buff.buffer_gas().pad_name(self.gas_output_length, " ")) words.append("E" if buff.valve.is_enabled else "d") words.append("O" if buff.valve.is_open else "c") - words.append(buff.system_gas.index(as_string=True)) - words.append(buff.system_gas.name()) + words.append(buff.system_gas().index(as_string=True)) + words.append(buff.system_gas().name()) lines.append(" ".join(words)) lines.append("GCS") return '\r\n'.join(lines) @@ -115,7 +115,7 @@ def get_gas_mix_matrix(self): system_gases = self.rig.system_gases.gases column_headers = [gas.pad_name(self.gas_output_length, '-') for gas in system_gases] - row_titles = [" ".join([gas.index_string(), gas.pad_name(self.gas_output_length, ' ')]) for gas in system_gases] + row_titles = [" ".join([gas.index(as_string=True), gas.pad_name(self.gas_output_length, ' ')]) for gas in system_gases] mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] @@ -154,6 +154,11 @@ def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): gas1 = self.rig.system_gases.gas_by_index(gas1_index) gas2 = self.rig.system_gases.gas_by_index(gas2_index) + if gas1 is None: + gas1 = self.rig.system_gases.gas_by_index(0) + if gas2 is None: + gas2 = self.rig.system_gases.gas_by_index(0) + return ' '.join(["GMC", gas1.index(as_string=True), gas1.pad_name(self.gas_output_length, '.'), gas2.index(as_string=True), gas2.pad_name(self.gas_output_length, '.'), diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/plankton_emulators/volumetric_rig/system_gases.py index a97dfa98..926cd3e3 100644 --- a/plankton_emulators/volumetric_rig/system_gases.py +++ b/plankton_emulators/volumetric_rig/system_gases.py @@ -12,9 +12,9 @@ def gas_by_index(self, index): def gas_by_name(self, name): return self._get_by_method(name, "name") - def _get_by_method(self, value, attr): + def _get_by_method(self, value, method): try: - return next(g for g in self.gases if getattr(g, attr) == value) + return next(g for g in self.gases if getattr(g, method)() == value) except StopIteration: return None From c0fece3d6b588b1e497acd3ec934b650558081b0 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 12:00:59 +0000 Subject: [PATCH 0048/1466] This method no longer exists --- .../volumetric_rig/interfaces/stream_interface.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 400816a1..89cbe7ea 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -101,7 +101,7 @@ def get_gas_control_and_status(self): words.append("") words.append(buff.index(as_string=True)) words.append(buff.buffer_gas().index(as_string=True)) - words.append(buff.buffer_gas().pad_name(self.gas_output_length, " ")) + words.append(buff.buffer_gas().name(self.gas_output_length, " ")) words.append("E" if buff.valve.is_enabled else "d") words.append("O" if buff.valve.is_open else "c") words.append(buff.system_gas().index(as_string=True)) @@ -114,8 +114,8 @@ def get_gas_mix_matrix(self): # Gather data system_gases = self.rig.system_gases.gases - column_headers = [gas.pad_name(self.gas_output_length, '-') for gas in system_gases] - row_titles = [" ".join([gas.index(as_string=True), gas.pad_name(self.gas_output_length, ' ')]) for gas in system_gases] + column_headers = [gas.name(self.gas_output_length, '-') for gas in system_gases] + row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] @@ -160,8 +160,8 @@ def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): gas2 = self.rig.system_gases.gas_by_index(0) return ' '.join(["GMC", - gas1.index(as_string=True), gas1.pad_name(self.gas_output_length, '.'), - gas2.index(as_string=True), gas2.pad_name(self.gas_output_length, '.'), + gas1.index(as_string=True), gas1.name(self.gas_output_length, '.'), + gas2.index(as_string=True), gas2.name(self.gas_output_length, '.'), "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): From 695c73b4b985fd50e38913320f8f949148a85bc3 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 12:18:59 +0000 Subject: [PATCH 0049/1466] Logic wrong way around --- .../interfaces/stream_interface.py | 52 ++++++++----------- .../volumetric_rig/utilities.py | 2 +- 2 files changed, 24 insertions(+), 30 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 89cbe7ea..f9d168ab 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -53,6 +53,20 @@ def __init__(self, device, arguments=None): def get_identity(self): return "IDN,00," + self.rig.identify() + def _build_buffer_control_and_status_string(self, buffer_number): + buff = self.rig.buffer(buffer_number) + assert buff is not None + return " ".join([ + "", + buff.index(as_string=True), + buff.buffer_gas().index(as_string=True), + buff.buffer_gas().name(self.gas_output_length, " "), + "E" if buff.valve_is_enabled() else "d", + "O" if buff.valve_is_open() else "c", + buff.system_gas().index(as_string=True), + buff.system_gas().name() + ]) + def get_buffer_control_and_status(self, buffer_number_string="0"): try: buffer_number = int(buffer_number_string) @@ -70,19 +84,8 @@ def get_buffer_control_and_status(self, buffer_number_string="0"): return buffer_too_low elif buffer_number > len(self.rig.buffers()): return buffer_too_high - - buff = self.rig.buffer(buffer_number) - assert buff is not None - return " ".join([ - message_prefix, - buff.index(as_string=True), - buff.buffer_gas().index(as_string=True), - buff.buffer_gas().name(self.gas_output_length, " "), - "E" if buff.valve_is_enabled() else "d", - "O" if buff.valve_is_open() else "c", - buff.system_gas().index(as_string=True), - buff.system_gas().name() - ]) + else: + return "BCS " + self._build_buffer_control_and_status_string(buffer_number) def get_ethernet_and_hmi_status(self): # The syntax of the return string is odd: the separators are not consistent @@ -94,28 +97,19 @@ def get_ethernet_and_hmi_status(self): ]) def get_gas_control_and_status(self): - lines = list() - lines.append("No No Buffer E O No System") - for buff in self.rig.buffers(): - words = list() - words.append("") - words.append(buff.index(as_string=True)) - words.append(buff.buffer_gas().index(as_string=True)) - words.append(buff.buffer_gas().name(self.gas_output_length, " ")) - words.append("E" if buff.valve.is_enabled else "d") - words.append("O" if buff.valve.is_open else "c") - words.append(buff.system_gas().index(as_string=True)) - words.append(buff.system_gas().name()) - lines.append(" ".join(words)) - lines.append("GCS") - return '\r\n'.join(lines) + return "\r\n".join( + ["No No Buffer E O No System"] + + [self._build_buffer_control_and_status_string(b.index()) for b in self.rig.buffers()] + + ["GCS"] + ) def get_gas_mix_matrix(self): # Gather data system_gases = self.rig.system_gases.gases column_headers = [gas.name(self.gas_output_length, '-') for gas in system_gases] - row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] + row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) + for gas in system_gases] mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index 82929b75..83a00876 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -1,6 +1,6 @@ def optional_int_string_format(int, as_string, length): if as_string: - return str(int) if length is not None else int[:length].zfill(length) + return str(int) if length is None else str(int)[:length].zfill(length) else: return int From 47da0efbdf6dcc7b2944597c73d090643573a39a Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 12:19:25 +0000 Subject: [PATCH 0050/1466] Don't shadow name int --- plankton_emulators/volumetric_rig/utilities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index 83a00876..a76449a8 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -1,8 +1,8 @@ -def optional_int_string_format(int, as_string, length): +def optional_int_string_format(i, as_string, length): if as_string: - return str(int) if length is None else str(int)[:length].zfill(length) + return str(i) if length is None else str(i)[:length].zfill(length) else: - return int + return i def pad_string(s, length, padding_character): From c7dbbcc384b2862eaf96b7a8a2b3753f294fb4db Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 12:20:56 +0000 Subject: [PATCH 0051/1466] Wrong separator --- .../volumetric_rig/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index f9d168ab..7518c026 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -107,7 +107,7 @@ def get_gas_mix_matrix(self): # Gather data system_gases = self.rig.system_gases.gases - column_headers = [gas.name(self.gas_output_length, '-') for gas in system_gases] + column_headers = [gas.name(self.gas_output_length, '|') for gas in system_gases] row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] From 463ca9ff4237e0f137ad6e0ac547b65925babf48 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 12:24:20 +0000 Subject: [PATCH 0052/1466] Sort gases before returning --- .../volumetric_rig/interfaces/stream_interface.py | 2 +- plankton_emulators/volumetric_rig/system_gases.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 7518c026..6c2d2325 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -105,7 +105,7 @@ def get_gas_control_and_status(self): def get_gas_mix_matrix(self): # Gather data - system_gases = self.rig.system_gases.gases + system_gases = self.rig.system_gases.gases() column_headers = [gas.name(self.gas_output_length, '|') for gas in system_gases] row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/plankton_emulators/volumetric_rig/system_gases.py index 926cd3e3..c00ef55d 100644 --- a/plankton_emulators/volumetric_rig/system_gases.py +++ b/plankton_emulators/volumetric_rig/system_gases.py @@ -3,7 +3,7 @@ class SystemGases(object): def __init__(self, gases=list()): - self.gases = set() + self._gases = set() self._add_gases(gases) def gas_by_index(self, index): @@ -14,9 +14,12 @@ def gas_by_name(self, name): def _get_by_method(self, value, method): try: - return next(g for g in self.gases if getattr(g, method)() == value) + return next(g for g in self._gases if getattr(g, method)() == value) except StopIteration: return None def _add_gases(self, iterable): - self.gases = set.union(self.gases, {g for g in iterable if isinstance(g, Gas)}) + self._gases = set.union(self._gases, {g for g in iterable if isinstance(g, Gas)}) + + def gases(self): + return sorted(list(self._gases), key=lambda g: g.index()) From c32a6915c22fdf2f28cfefcb44f5f27cfc3d2b36 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 13:25:41 +0000 Subject: [PATCH 0053/1466] Add method for number of gases --- .../volumetric_rig/interfaces/stream_interface.py | 2 +- plankton_emulators/volumetric_rig/system_gases.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 6c2d2325..546d97fe 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -159,7 +159,7 @@ def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): - return len(self.rig.system_gases) + return self.rig.system_gases.gas_count() def get_hmi_status(self): return " ".join(["HMI " + self.rig.hmi_status() + " ", diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/plankton_emulators/volumetric_rig/system_gases.py index c00ef55d..ae19a2cf 100644 --- a/plankton_emulators/volumetric_rig/system_gases.py +++ b/plankton_emulators/volumetric_rig/system_gases.py @@ -23,3 +23,6 @@ def _add_gases(self, iterable): def gases(self): return sorted(list(self._gases), key=lambda g: g.index()) + + def gas_count(self): + return len(self._gases) From 0c2e8739ed02138c5bca2b9710ac7e9b362108df Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 13:29:17 +0000 Subject: [PATCH 0054/1466] Add missing output --- .../volumetric_rig/interfaces/stream_interface.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 546d97fe..36b3b741 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -162,12 +162,14 @@ def get_gas_number_available(self): return self.rig.system_gases.gas_count() def get_hmi_status(self): - return " ".join(["HMI " + self.rig.hmi_status() + " ", + # C, L and M are returned by the current rig. I don't know what they are + return ",".join(["HMI " + self.rig.hmi_status() + " ", self.rig.hmi_ip(), "B", self.rig.hmi_base_page(as_string=True, length=4), "S", - self.rig.hmi_sub_page(as_string=True, length=3)]) + self.rig.hmi_sub_page(as_string=True, length=3)]) + \ + ",C,0000,L,0020,M,0038" def get_hmi_count_cycles(self): return " ".join(["HMC"] + self.rig.hmi_count_cycles()) From 841a98cb3de3a507c9eeff03b5a91ad4e0f8e0f4 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 13:49:17 +0000 Subject: [PATCH 0055/1466] Unintentional type change --- .../volumetric_rig/interfaces/stream_interface.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 36b3b741..b721c60c 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,6 +1,7 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..device import SimulatedVolumetricRig from ..sensor_status import SensorStatus +from ..utilities import optional_int_string_format class VolumetricRigStreamInterface(StreamAdapter): @@ -23,7 +24,8 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("get_gas_number_available", "^GNA(?: .*)?$"), Cmd("get_hmi_status", "^HMI(?: .*)?$"), Cmd("get_hmi_count_cycles", "^HMC(?: .*)?$"), - Cmd("get_memory_location", "^RDM(?: .*)?"), + Cmd("get_memory_location", "^RDM$"), + Cmd("get_memory_location", "^RDM\s(\S*).*"), Cmd("get_pressure_and_temperature_status", "^PTS(?: .*)?$"), Cmd("get_pressures", "^PMV(?: .*)?$"), Cmd("get_temperatures", "^TMV(?: .*)?$"), @@ -179,7 +181,7 @@ def get_memory_location(self, location_raw="0"): location = int(location_raw) except ValueError: location = 0 - return " ".join(["RDM", location[:4].zfill(4), + return " ".join(["RDM", optional_int_string_format(location, as_string=True, length=4), self.rig.memory_location(location, as_string=True, length=6)]) def get_pressure_and_temperature_status(self): @@ -230,7 +232,7 @@ def derive_status(valve): def _set_valve_status(self, buffer_number_raw, set_to_open): try: valve_number = int(buffer_number_raw) - except TypeError: + except ValueError: valve_number = 0 command = "OPV" if set_to_open else "CLV" From 5a432b7e2571434b91f2c2b556f882b6ca143ac7 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:04:28 +0000 Subject: [PATCH 0056/1466] Improve regex format to avoid repetition --- .../interfaces/stream_interface.py | 57 +++++-------------- .../volumetric_rig/utilities.py | 12 +++- 2 files changed, 26 insertions(+), 43 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index b721c60c..32a76b5a 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,7 +1,7 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..device import SimulatedVolumetricRig from ..sensor_status import SensorStatus -from ..utilities import optional_int_string_format +from ..utilities import optional_int_string_format, convert_raw_to_int class VolumetricRigStreamInterface(StreamAdapter): @@ -13,19 +13,15 @@ class VolumetricRigStreamInterface(StreamAdapter): commands = { Cmd("get_identity", "^IDN(?: .*)?$"), Cmd("get_identity", "^\?(?: .*)?$"), - Cmd("get_buffer_control_and_status", "^BCS$"), - Cmd("get_buffer_control_and_status", "^BCS\s(\S*).*$"), + Cmd("get_buffer_control_and_status", "^BCS(?:\s(\S*))?.*$"), Cmd("get_ethernet_and_hmi_status", "^ETN(?: .*)?$"), Cmd("get_gas_control_and_status", "^GCS(?: .*)?$"), Cmd("get_gas_mix_matrix", "^GMM(?: .*)?$"), - Cmd("gas_mix_check", "^GMC$"), - Cmd("gas_mix_check", "^GMC\s(\S*)$"), - Cmd("gas_mix_check", "^GMC\s(\S*)\s(\S*).*$"), + Cmd("gas_mix_check", "^GMC(?:\s(\S*))?(?:\s(\S*))?.*$"), Cmd("get_gas_number_available", "^GNA(?: .*)?$"), Cmd("get_hmi_status", "^HMI(?: .*)?$"), Cmd("get_hmi_count_cycles", "^HMC(?: .*)?$"), - Cmd("get_memory_location", "^RDM$"), - Cmd("get_memory_location", "^RDM\s(\S*).*"), + Cmd("get_memory_location", "^RDM(?:\s(\S*))?.*"), Cmd("get_pressure_and_temperature_status", "^PTS(?: .*)?$"), Cmd("get_pressures", "^PMV(?: .*)?$"), Cmd("get_temperatures", "^TMV(?: .*)?$"), @@ -36,10 +32,8 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("get_system_status", "^STS(?: .*)?$"), Cmd("get_com_activity", "^COM(?: .*)?$"), Cmd("get_valve_status", "^VST(?: .*)?$"), - Cmd("set_valve_open", "^OPV$"), - Cmd("set_valve_open", "^OPV\s(\S*).*$"), - Cmd("set_valve_closed", "^CLV.*$"), - Cmd("set_valve_closed", "^CLV\s(\S*).*$"), + Cmd("set_valve_open", "^OPV(?:\s(\S*))?.*$"), + Cmd("set_valve_closed", "^CLV(?:\s(\S*))?.*$"), Cmd("halt", "^HLT(?: .*)?$"), } @@ -69,13 +63,8 @@ def _build_buffer_control_and_status_string(self, buffer_number): buff.system_gas().name() ]) - def get_buffer_control_and_status(self, buffer_number_string="0"): - try: - buffer_number = int(buffer_number_string) - except ValueError: - # This is how the volumetric rig currently responds - buffer_number = 0 - + def get_buffer_control_and_status(self, buffer_number_raw): + buffer_number = convert_raw_to_int(buffer_number_raw) message_prefix = "BCS" num_length = 3 error_message_prefix = " ".join([message_prefix, "Buffer", str(buffer_number)[:num_length].zfill(num_length)]) @@ -138,18 +127,9 @@ def get_gas_mix_matrix(self): return '\r\n'.join(lines) - def gas_mix_check(self, gas1_index_raw="0", gas2_index_raw="0"): - try: - gas1_index = int(gas1_index_raw) - except ValueError: - gas1_index = 0 - try: - gas2_index = int(gas2_index_raw) - except ValueError: - gas2_index = 0 - - gas1 = self.rig.system_gases.gas_by_index(gas1_index) - gas2 = self.rig.system_gases.gas_by_index(gas2_index) + def gas_mix_check(self, gas1_index_raw, gas2_index_raw): + gas1 = self.rig.system_gases.gas_by_index(convert_raw_to_int(gas1_index_raw)) + gas2 = self.rig.system_gases.gas_by_index(convert_raw_to_int(gas2_index_raw)) if gas1 is None: gas1 = self.rig.system_gases.gas_by_index(0) if gas2 is None: @@ -176,11 +156,8 @@ def get_hmi_status(self): def get_hmi_count_cycles(self): return " ".join(["HMC"] + self.rig.hmi_count_cycles()) - def get_memory_location(self, location_raw="0"): - try: - location = int(location_raw) - except ValueError: - location = 0 + def get_memory_location(self, location_raw): + location = convert_raw_to_int(location_raw) return " ".join(["RDM", optional_int_string_format(location, as_string=True, length=4), self.rig.memory_location(location, as_string=True, length=6)]) @@ -229,12 +206,8 @@ def derive_status(valve): return "VST Valve Status " + "".join([derive_status(v) for v in self.rig.valves()]) - def _set_valve_status(self, buffer_number_raw, set_to_open): - try: - valve_number = int(buffer_number_raw) - except ValueError: - valve_number = 0 - + def _set_valve_status(self, valve_number_raw, set_to_open): + valve_number = convert_raw_to_int(valve_number_raw) command = "OPV" if set_to_open else "CLV" message_prefix = " ".join([ command, diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index a76449a8..12a29bca 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -6,4 +6,14 @@ def optional_int_string_format(i, as_string, length): def pad_string(s, length, padding_character): - return s if length is None else s[:length] + (length - len(s))*padding_character \ No newline at end of file + return s if length is None else s[:length] + (length - len(s))*padding_character + + +def convert_raw_to_int(raw): + from types import StringType, IntType + if type(raw) == IntType: + return raw + elif type(raw) == StringType: + return int(raw.zfill(1)) + else: + return 0 \ No newline at end of file From f479ec37467651329d32a800161c5f305e0c71cb Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:12:12 +0000 Subject: [PATCH 0057/1466] Extend eth devices --- plankton_emulators/volumetric_rig/device.py | 20 +++--------- .../volumetric_rig/ethernet_device.py | 5 ++- .../volumetric_rig/hmi_device.py | 32 ++++++++++++++----- .../interfaces/stream_interface.py | 18 +++++------ 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index d56c9f60..0b173f67 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -69,23 +69,11 @@ def buffer(self, i): def memory_location(self, location, as_string, length): return optional_int_string_format(location, as_string, length) - def plc_ip(self): - return self._plc.ip + def plc(self): + return self._plc - def hmi_ip(self): - return self._hmi.ip - - def hmi_status(self): - return self._hmi.status - - def hmi_base_page(self, as_string=False, length=None): - return optional_int_string_format(self._hmi.base_page, as_string, length) - - def hmi_sub_page(self, as_string=False, length=None): - return optional_int_string_format(self._hmi.sub_page, as_string, length) - - def hmi_count_cycles(self): - return self._hmi.count_cycles + def hmi(self): + return self._hmi def halted(self): return self._halted diff --git a/plankton_emulators/volumetric_rig/ethernet_device.py b/plankton_emulators/volumetric_rig/ethernet_device.py index 31978c57..b90eb1cf 100644 --- a/plankton_emulators/volumetric_rig/ethernet_device.py +++ b/plankton_emulators/volumetric_rig/ethernet_device.py @@ -4,4 +4,7 @@ class EthernetDevice(object): def __init__(self, ip): assert type(ip) is StringType - self.ip = ip + self._ip = ip + + def ip(self): + return self._ip diff --git a/plankton_emulators/volumetric_rig/hmi_device.py b/plankton_emulators/volumetric_rig/hmi_device.py index 875e8bb8..1d653a49 100644 --- a/plankton_emulators/volumetric_rig/hmi_device.py +++ b/plankton_emulators/volumetric_rig/hmi_device.py @@ -1,4 +1,5 @@ from ethernet_device import EthernetDevice +from utilities import optional_int_string_format class HmiDevice(EthernetDevice): @@ -6,14 +7,29 @@ class HmiDevice(EthernetDevice): OK_STATUS = "OK" def __init__(self, ip): - self.status = HmiDevice.OK_STATUS - self.base_page = 34 - self.sub_page = 2 - self.count_cycles = ["999", "006", "002", "002", "002", "002", "002", "002", "001", "001", "310"] + self._status = HmiDevice.OK_STATUS + self._base_page = 34 + self._sub_page = 2 + self._count_cycles = ["999", "006", "002", "002", "002", "002", "002", "002", "001", "001", "310"] + self._count = 0 + self._max_grabbed = 38 + self._limit = 20 super(HmiDevice, self).__init__(ip) - def base_page_string(self): - return str(self.base_page).zfill(3) + def base_page(self, as_string, length): + return optional_int_string_format(self._base_page, as_string, length) - def sub_page_string(self): - return str(self.sub_page).zfill(3) + def sub_page(self, as_string, length): + return optional_int_string_format(self._sub_page, as_string, length) + + def count_cycles(self): + return list(self._count_cycles) + + def max_grabbed(self, as_string, length): + return optional_int_string_format(self._max_grabbed, as_string, length) + + def limit(self, as_string, length): + return optional_int_string_format(self._limit, as_string, length) + + def count(self, as_string, length): + return optional_int_string_format(self._count, as_string, length) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 32a76b5a..cefa7a74 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -82,7 +82,7 @@ def get_ethernet_and_hmi_status(self): # The syntax of the return string is odd: the separators are not consistent return " ".join([ "ETN:PLC", - self.rig.plc_ip() + ",HMI", + self.rig.plc.ip() + ",HMI", self.rig.hmi_status(), "," + self.rig.hmi_ip() ]) @@ -144,14 +144,14 @@ def get_gas_number_available(self): return self.rig.system_gases.gas_count() def get_hmi_status(self): - # C, L and M are returned by the current rig. I don't know what they are - return ",".join(["HMI " + self.rig.hmi_status() + " ", - self.rig.hmi_ip(), - "B", - self.rig.hmi_base_page(as_string=True, length=4), - "S", - self.rig.hmi_sub_page(as_string=True, length=3)]) + \ - ",C,0000,L,0020,M,0038" + hmi = self.rig.hmi() + return ",".join(["HMI " + hmi.status() + " ", hmi.ip(), + "B",hmi.base_page(as_string=True, length=3), + "S",hmi.sub_page(as_string=True, length=3), + "C",hmi.count(as_string=True, length=4), + "L",hmi.limit(as_string=True, length=4), + "M",hmi.max_grabbed(as_string=True, length=4) + ]) def get_hmi_count_cycles(self): return " ".join(["HMC"] + self.rig.hmi_count_cycles()) From ed8dd9bb085f68d6e7fb8758eed1ee85e96f86e7 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:13:30 +0000 Subject: [PATCH 0058/1466] Fix method names --- plankton_emulators/volumetric_rig/hmi_device.py | 5 ++++- .../volumetric_rig/interfaces/stream_interface.py | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plankton_emulators/volumetric_rig/hmi_device.py b/plankton_emulators/volumetric_rig/hmi_device.py index 1d653a49..7213a183 100644 --- a/plankton_emulators/volumetric_rig/hmi_device.py +++ b/plankton_emulators/volumetric_rig/hmi_device.py @@ -32,4 +32,7 @@ def limit(self, as_string, length): return optional_int_string_format(self._limit, as_string, length) def count(self, as_string, length): - return optional_int_string_format(self._count, as_string, length) \ No newline at end of file + return optional_int_string_format(self._count, as_string, length) + + def status(self): + return self._status \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index cefa7a74..6f701d3c 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -83,8 +83,8 @@ def get_ethernet_and_hmi_status(self): return " ".join([ "ETN:PLC", self.rig.plc.ip() + ",HMI", - self.rig.hmi_status(), - "," + self.rig.hmi_ip() + self.rig.hmi().status(), + "," + self.rig.hmi().ip() ]) def get_gas_control_and_status(self): @@ -154,7 +154,7 @@ def get_hmi_status(self): ]) def get_hmi_count_cycles(self): - return " ".join(["HMC"] + self.rig.hmi_count_cycles()) + return " ".join(["HMC"] + self.rig.hmi().count_cycles()) def get_memory_location(self, location_raw): location = convert_raw_to_int(location_raw) From f0ddc85047b466b4d60b8dbd3b9b8ce3b1ff8edc Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:15:07 +0000 Subject: [PATCH 0059/1466] Fix method name --- plankton_emulators/volumetric_rig/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py index 9a56f33b..d13bfb86 100644 --- a/plankton_emulators/volumetric_rig/sensor.py +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -8,5 +8,5 @@ def __init__(self): def set_status(self, status): self._status = status - def get_status(self): + def status(self): return self._status \ No newline at end of file From 46f4391ac24268c4b3d2168864e95f1aa73966f0 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:20:01 +0000 Subject: [PATCH 0060/1466] Reverse acts in place --- plankton_emulators/volumetric_rig/device.py | 8 ++++---- .../volumetric_rig/interfaces/stream_interface.py | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 0b173f67..45f89652 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -82,10 +82,10 @@ def halt(self): self._halted = True def pressure_sensors(self, reverse=False): - return self._pressure_sensors if not reverse else self._pressure_sensors.reverse() + return self._pressure_sensors if not reverse else list(reversed(self._pressure_sensors)) def temperature_sensors(self, reverse=False): - return self._temperature_sensors if not reverse else self._temperature_sensors.reverse() + return self._temperature_sensors if not reverse else list(reversed(self._temperature_sensors)) def target_pressure(self): return self._target_pressure @@ -101,11 +101,11 @@ def valve_count(self): def valves(self): return [self._supply_valve, self._vacuum_extract_valve, self._cell_valve] + \ - [b.valve for b in self._buffers.reverse()] + [b.valve for b in list(reversed(self._buffers))] def buffer_valve_is_open(self, buffer_number): buff = self.buffer(buffer_number) - return buff.valve().is_open if buff is not None else False + return buff.valve_is_open() if buff is not None else False def vacuum_valve_is_open(self): return self._vacuum_extract_valve.is_open diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 6f701d3c..ba89ea1d 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -164,6 +164,7 @@ def get_memory_location(self, location_raw): def get_pressure_and_temperature_status(self): def get_status_code(status): + print status if status == SensorStatus.DISABLED: return "D" elif status == SensorStatus.NO_REPLY: From 3ecb78bb78468f939da9bd17debeeb843ab78b37 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:29:15 +0000 Subject: [PATCH 0061/1466] Functionality the same, don't need separate types --- plankton_emulators/volumetric_rig/device.py | 7 +++---- .../volumetric_rig/interfaces/stream_interface.py | 5 ++--- .../volumetric_rig/pressure_sensor.py | 13 ------------- plankton_emulators/volumetric_rig/sensor.py | 10 +++++++++- .../volumetric_rig/temperature_sensor.py | 13 ------------- plankton_emulators/volumetric_rig/utilities.py | 7 +++++++ 6 files changed, 21 insertions(+), 34 deletions(-) delete mode 100644 plankton_emulators/volumetric_rig/pressure_sensor.py delete mode 100644 plankton_emulators/volumetric_rig/temperature_sensor.py diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 45f89652..0e58af92 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -6,11 +6,10 @@ from seed_gas_data import SeedGasData from ethernet_device import EthernetDevice from hmi_device import HmiDevice -from temperature_sensor import TemperatureSensor -from pressure_sensor import PressureSensor from valve import Valve from error_states import ErrorStates from utilities import optional_int_string_format +from sensor import Sensor class SimulatedVolumetricRig(Device): @@ -35,8 +34,8 @@ def __init__(self): self._hmi = HmiDevice("192.168.0.2") # Set up sensors - self._temperature_sensors = [TemperatureSensor() for _ in range(9)] - self._pressure_sensors = [PressureSensor() for _ in range(5)] + self._temperature_sensors = [Sensor() for _ in range(9)] + self._pressure_sensors = [Sensor() for _ in range(5)] # Set up special valves self._supply_valve = Valve() diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index ba89ea1d..e6dde2aa 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -164,7 +164,6 @@ def get_memory_location(self, location_raw): def get_pressure_and_temperature_status(self): def get_status_code(status): - print status if status == SensorStatus.DISABLED: return "D" elif status == SensorStatus.NO_REPLY: @@ -184,12 +183,12 @@ def get_status_code(status): def get_pressures(self): return " ".join(["PMV"] + - [p.pressure for p in self.rig.pressure_sensors(reverse=True)] + + [p.value(as_string=True) for p in self.rig.pressure_sensors(reverse=True)] + ["T", self.rig.target_pressure()]) def get_temperatures(self): return " ".join(["TMV"] + - [t.temperature for t in self.rig.temperature_sensors(reverse=True)]) + [t.value(as_string=True) for t in self.rig.temperature_sensors(reverse=True)]) def get_valve_status(self): diff --git a/plankton_emulators/volumetric_rig/pressure_sensor.py b/plankton_emulators/volumetric_rig/pressure_sensor.py deleted file mode 100644 index 5ec46b4d..00000000 --- a/plankton_emulators/volumetric_rig/pressure_sensor.py +++ /dev/null @@ -1,13 +0,0 @@ -from sensor import Sensor - - -class PressureSensor(Sensor): - def __init__(self): - self.pressure = 0.0 - super(PressureSensor,self).__init__() - - def set_pressure(self,pressure): - self.pressure = pressure - - def get_pressure(self): - return self.pressure \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py index d13bfb86..3b36128c 100644 --- a/plankton_emulators/volumetric_rig/sensor.py +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -1,12 +1,20 @@ from sensor_status import SensorStatus +from utilities import optional_float_string_format class Sensor(object): def __init__(self): self._status = SensorStatus.UNKNOWN + self._value = 0.0 def set_status(self, status): self._status = status def status(self): - return self._status \ No newline at end of file + return self._status + + def set_value(self, v): + self._value = v + + def value(self, as_string): + return optional_float_string_format(self._value, as_string) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/temperature_sensor.py b/plankton_emulators/volumetric_rig/temperature_sensor.py deleted file mode 100644 index af7ba6da..00000000 --- a/plankton_emulators/volumetric_rig/temperature_sensor.py +++ /dev/null @@ -1,13 +0,0 @@ -from sensor import Sensor - - -class TemperatureSensor(Sensor): - def __init__(self): - self.temperature = 0.0 - super(TemperatureSensor,self).__init__() - - def set_temperature(self,temperature): - self.temperature = temperature - - def get_temperature(self): - return self.temperature \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index 12a29bca..8e646e43 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -5,6 +5,13 @@ def optional_int_string_format(i, as_string, length): return i +def optional_float_string_format(f, as_string): + if as_string: + return "{0:5.2f}".format(f) + else: + return f + + def pad_string(s, length, padding_character): return s if length is None else s[:length] + (length - len(s))*padding_character From d3d33e158a09729de571b8e0971d35b48e594b99 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:33:37 +0000 Subject: [PATCH 0062/1466] Add missing conversion --- plankton_emulators/volumetric_rig/device.py | 6 +++--- .../volumetric_rig/interfaces/stream_interface.py | 2 +- plankton_emulators/volumetric_rig/utilities.py | 5 +---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 0e58af92..889ca20b 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -8,7 +8,7 @@ from hmi_device import HmiDevice from valve import Valve from error_states import ErrorStates -from utilities import optional_int_string_format +from utilities import optional_int_string_format, optional_float_string_format from sensor import Sensor @@ -86,8 +86,8 @@ def pressure_sensors(self, reverse=False): def temperature_sensors(self, reverse=False): return self._temperature_sensors if not reverse else list(reversed(self._temperature_sensors)) - def target_pressure(self): - return self._target_pressure + def target_pressure(self, as_string): + return optional_float_string_format(self._target_pressure, as_string) def status_code(self, as_string=False, length=None): return optional_int_string_format(2, as_string, length) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index e6dde2aa..6af224b7 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -184,7 +184,7 @@ def get_status_code(status): def get_pressures(self): return " ".join(["PMV"] + [p.value(as_string=True) for p in self.rig.pressure_sensors(reverse=True)] + - ["T", self.rig.target_pressure()]) + ["T", self.rig.target_pressure(as_string=True)]) def get_temperatures(self): return " ".join(["TMV"] + diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index 8e646e43..b546ed95 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -6,10 +6,7 @@ def optional_int_string_format(i, as_string, length): def optional_float_string_format(f, as_string): - if as_string: - return "{0:5.2f}".format(f) - else: - return f + return "{0:.2f}".format(f).zfill(5) if as_string else f def pad_string(s, length, padding_character): From 4233ad9d85d1563c81a97f2e76f3009ebac71c95 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:40:01 +0000 Subject: [PATCH 0063/1466] Abstract valve attributes --- plankton_emulators/volumetric_rig/device.py | 8 +++++--- .../interfaces/stream_interface.py | 2 +- plankton_emulators/volumetric_rig/valve.py | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 889ca20b..9dfc4007 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -98,9 +98,11 @@ def errors(self): def valve_count(self): return len(self.valves()) - def valves(self): - return [self._supply_valve, self._vacuum_extract_valve, self._cell_valve] + \ - [b.valve for b in list(reversed(self._buffers))] + def valves_status(self): + return [self._supply_valve.is_open(), + self._vacuum_extract_valve.is_open(), + self._cell_valve.is_open()] + \ + [b.valve_is_open() for b in list(reversed(self._buffers))] def buffer_valve_is_open(self, buffer_number): buff = self.buffer(buffer_number) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 6af224b7..90cb4b2b 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -204,7 +204,7 @@ def derive_status(valve): else: assert False - return "VST Valve Status " + "".join([derive_status(v) for v in self.rig.valves()]) + return "VST Valve Status " + "".join([derive_status(v) for v in self.rig.valves_status()]) def _set_valve_status(self, valve_number_raw, set_to_open): valve_number = convert_raw_to_int(valve_number_raw) diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py index c981f102..1fbdffdb 100644 --- a/plankton_emulators/volumetric_rig/valve.py +++ b/plankton_emulators/volumetric_rig/valve.py @@ -1,12 +1,18 @@ class Valve(object): def __init__(self): - self.is_enabled = True - self.is_open = False + self._is_enabled = True + self._is_open = False def open(self): - if self.is_enabled: - self.is_open = True + if self._is_enabled: + self._is_open = True def close(self): - if self.is_enabled: - self.is_open = False \ No newline at end of file + if self._is_enabled: + self._is_open = False + + def is_open(self): + return self._is_open + + def is_enabled(self): + return self._is_enabled \ No newline at end of file From 871197c5db8f20d1cb62ed09a7a4e1de531cb4f2 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:49:33 +0000 Subject: [PATCH 0064/1466] Use dictionary lookup for status codes --- plankton_emulators/volumetric_rig/buffer.py | 7 ++- plankton_emulators/volumetric_rig/device.py | 4 +- .../interfaces/stream_interface.py | 45 +++++++------------ plankton_emulators/volumetric_rig/valve.py | 11 ++++- .../volumetric_rig/valve_status.py | 2 + 5 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 plankton_emulators/volumetric_rig/valve_status.py diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 69dab016..8563e99f 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -24,10 +24,13 @@ def close_valve(self): self._valve.close() def valve_is_open(self): - return self._valve.is_open + return self._valve.is_open() def valve_is_enabled(self): - return self._valve.is_enabled + return self._valve.is_enabled() + + def valve_status(self): + return self._valve.status() def buffer_gas(self): return self._buffer_gas diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 9dfc4007..f503aab2 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -96,13 +96,13 @@ def errors(self): return self._errors def valve_count(self): - return len(self.valves()) + return len(self.valves_status()) def valves_status(self): return [self._supply_valve.is_open(), self._vacuum_extract_valve.is_open(), self._cell_valve.is_open()] + \ - [b.valve_is_open() for b in list(reversed(self._buffers))] + [b.valve_status() for b in list(reversed(self._buffers))] def buffer_valve_is_open(self, buffer_number): buff = self.buffer(buffer_number) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 90cb4b2b..8a111309 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -2,6 +2,7 @@ from ..device import SimulatedVolumetricRig from ..sensor_status import SensorStatus from ..utilities import optional_int_string_format, convert_raw_to_int +from ..valve_status import ValveStatus class VolumetricRigStreamInterface(StreamAdapter): @@ -163,22 +164,17 @@ def get_memory_location(self, location_raw): def get_pressure_and_temperature_status(self): - def get_status_code(status): - if status == SensorStatus.DISABLED: - return "D" - elif status == SensorStatus.NO_REPLY: - return "X" - elif status == SensorStatus.VALUE_IN_RANGE: - return "O" - elif status == SensorStatus.VALUE_TOO_LOW: - return "L" - elif status == SensorStatus.VALUE_TOO_HIGH: - return "H" - else: - return "?" + status_codes = { + SensorStatus.DISABLED:"D", + SensorStatus.NO_REPLY:"X", + SensorStatus.VALUE_IN_RANGE:"O", + SensorStatus.VALUE_TOO_LOW:"L", + SensorStatus.VALUE_TOO_HIGH:"H" + SensorStatus.UNKNOWN:"?" + } return "PTS " + \ - "".join([get_status_code(s.status()) for s in + "".join([status_codes[s.status()] for s in self.rig.pressure_sensors(reverse=True)+self.rig.temperature_sensors(reverse=True)]) def get_pressures(self): @@ -191,20 +187,13 @@ def get_temperatures(self): [t.value(as_string=True) for t in self.rig.temperature_sensors(reverse=True)]) def get_valve_status(self): - - def derive_status(valve): - if valve.enabled() and valve.is_open(): - return "O" - elif valve.enabled() and not valve.is_open(): - return "E" - elif not valve.enabled() and valve.is_open(): - return "!" - elif not valve.enabled() and not valve.is_open(): - return "D" - else: - assert False - - return "VST Valve Status " + "".join([derive_status(v) for v in self.rig.valves_status()]) + status_codes = { + ValveStatus.OPEN_AND_ENABLED:"O", + ValveStatus.CLOSED_AND_ENABLED:"E", + ValveStatus.CLOSED_AND_DISABLED:"D", + ValveStatus.OPEN_AND_DISABLED:"!" + } + return "VST Valve Status " + "".join([status_codes(v) for v in self.rig.valves_status()]) def _set_valve_status(self, valve_number_raw, set_to_open): valve_number = convert_raw_to_int(valve_number_raw) diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py index 1fbdffdb..efc767d9 100644 --- a/plankton_emulators/volumetric_rig/valve.py +++ b/plankton_emulators/volumetric_rig/valve.py @@ -1,3 +1,6 @@ +from valve_status import ValveStatus + + class Valve(object): def __init__(self): self._is_enabled = True @@ -15,4 +18,10 @@ def is_open(self): return self._is_open def is_enabled(self): - return self._is_enabled \ No newline at end of file + return self._is_enabled + + def status(self): + if self._is_open: + return ValveStatus.OPEN_AND_ENABLED if self._is_enabled else ValveStatus.OPEN_AND_DISABLED + else: + return ValveStatus.CLOSED_AND_ENABLED if self._is_enabled else ValveStatus.CLOSED_AND_DISABLED diff --git a/plankton_emulators/volumetric_rig/valve_status.py b/plankton_emulators/volumetric_rig/valve_status.py new file mode 100644 index 00000000..c1c99598 --- /dev/null +++ b/plankton_emulators/volumetric_rig/valve_status.py @@ -0,0 +1,2 @@ +class ValveStatus(object): + OPEN_AND_ENABLED, CLOSED_AND_ENABLED, OPEN_AND_DISABLED, CLOSED_AND_DISABLED = (i for i in range(4)) \ No newline at end of file From 366132c09da75cc8b8fa9f9171b3d24e2ac6b402 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:49:59 +0000 Subject: [PATCH 0065/1466] Missing comma --- .../volumetric_rig/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 8a111309..7727acf0 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -169,7 +169,7 @@ def get_pressure_and_temperature_status(self): SensorStatus.NO_REPLY:"X", SensorStatus.VALUE_IN_RANGE:"O", SensorStatus.VALUE_TOO_LOW:"L", - SensorStatus.VALUE_TOO_HIGH:"H" + SensorStatus.VALUE_TOO_HIGH:"H", SensorStatus.UNKNOWN:"?" } From 6c908a2c8a127ac96e34d952b1ed6928598eb442 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:50:32 +0000 Subject: [PATCH 0066/1466] Wrong parantheses --- .../volumetric_rig/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 7727acf0..8dabf592 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -193,7 +193,7 @@ def get_valve_status(self): ValveStatus.CLOSED_AND_DISABLED:"D", ValveStatus.OPEN_AND_DISABLED:"!" } - return "VST Valve Status " + "".join([status_codes(v) for v in self.rig.valves_status()]) + return "VST Valve Status " + "".join([status_codes[v] for v in self.rig.valves_status()]) def _set_valve_status(self, valve_number_raw, set_to_open): valve_number = convert_raw_to_int(valve_number_raw) From bf33db6a119ca4df8aa552dc50693dc7f8a24018 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 14:54:20 +0000 Subject: [PATCH 0067/1466] Use correct method for valve status --- plankton_emulators/volumetric_rig/device.py | 6 +++--- plankton_emulators/volumetric_rig/valve.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index f503aab2..4e553f49 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -99,9 +99,9 @@ def valve_count(self): return len(self.valves_status()) def valves_status(self): - return [self._supply_valve.is_open(), - self._vacuum_extract_valve.is_open(), - self._cell_valve.is_open()] + \ + return [self._supply_valve.status(), + self._vacuum_extract_valve.status(), + self._cell_valve.status()] + \ [b.valve_status() for b in list(reversed(self._buffers))] def buffer_valve_is_open(self, buffer_number): diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py index efc767d9..bad49e8b 100644 --- a/plankton_emulators/volumetric_rig/valve.py +++ b/plankton_emulators/volumetric_rig/valve.py @@ -7,6 +7,7 @@ def __init__(self): self._is_open = False def open(self): + print "Opening..." if self._is_enabled: self._is_open = True From 0d555b5aa803e3b4a522b9354b2d716089ef5ea5 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 15:07:52 +0000 Subject: [PATCH 0068/1466] Get argument passing right --- .../interfaces/stream_interface.py | 40 +++++++++---------- plankton_emulators/volumetric_rig/valve.py | 1 - 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 8dabf592..1539383c 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -203,46 +203,46 @@ def _set_valve_status(self, valve_number_raw, set_to_open): "Value", str(valve_number) ]) + args = list() if self.rig.halted(): return "CLV Rejected only allowed when running" elif valve_number <= 0: return message_prefix + " Too Low" elif valve_number <= self.rig.buffer_count(): - valve_status = self.rig.buffer_valve_is_open(valve_number) - open_valve = self.rig.open_buffer_valve(valve_number) - close_valve = self.rig.close_buffer_valve(valve_number) + valve_status = self.rig.buffer_valve_is_open + open_valve = self.rig.open_buffer_valve + close_valve = self.rig.close_buffer_valve + args.append(valve_number) elif valve_number == self.rig.buffer_count() + 1: - valve_status = self.rig.cell_valve_is_open() - open_valve = self.rig.open_cell_valve() - close_valve = self.rig.close_cell_valve() + valve_status = self.rig.cell_valve_is_open + open_valve = self.rig.open_cell_valve + close_valve = self.rig.close_cell_valve elif valve_number == self.rig.buffer_count() + 2: - valve_status = self.rig.vacuum_valve_is_open() - open_valve = self.rig.open_vacuum_valve() - close_valve = self.rig.close_vacuum_valve() + valve_status = self.rig.vacuum_valve_is_open + open_valve = self.rig.open_vacuum_valve + close_valve = self.rig.close_vacuum_valve else: return message_prefix + " Too High" - original_status = valve_status() - open_valve() if set_to_open else close_valve() - new_status = valve_status() + original_status = valve_status(*args) + open_valve(*args) if set_to_open else close_valve(*args) + new_status = valve_status(*args) - def derive_status(is_open): - return "open" if is_open else "closed" - - # e.g. CLV Valve Buffer 1 closed was open + status_codes = {True: "open", False:"closed"} return " ".join([ command, "Valve Buffer", str(valve_number), - derive_status(original_status), + status_codes[original_status], "was", - derive_status(new_status)]) + status_codes[new_status], + ]) def set_valve_closed(self, buffer_number_raw): - self._set_valve_status(buffer_number_raw, False) + return self._set_valve_status(buffer_number_raw, False) def set_valve_open(self, buffer_number_raw): - self._set_valve_status(buffer_number_raw, True) + return self._set_valve_status(buffer_number_raw, True) def halt(self): if self.rig.halted(): diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py index bad49e8b..efc767d9 100644 --- a/plankton_emulators/volumetric_rig/valve.py +++ b/plankton_emulators/volumetric_rig/valve.py @@ -7,7 +7,6 @@ def __init__(self): self._is_open = False def open(self): - print "Opening..." if self._is_enabled: self._is_open = True From 68cb14bd99bfee8a8f40d8080952f5db828503f9 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 15:45:31 +0000 Subject: [PATCH 0069/1466] Make state machine device so we can more easily simulate time --- plankton_emulators/volumetric_rig/device.py | 26 +++- .../interfaces/stream_interface.py | 126 +++++++++--------- plankton_emulators/volumetric_rig/states.py | 10 ++ 3 files changed, 93 insertions(+), 69 deletions(-) create mode 100644 plankton_emulators/volumetric_rig/states.py diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 4e553f49..410d5831 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -1,4 +1,3 @@ -from lewis.devices import Device from two_gas_mixer import TwoGasMixer from buffer import Buffer from gas import Gas @@ -10,10 +9,16 @@ from error_states import ErrorStates from utilities import optional_int_string_format, optional_float_string_format from sensor import Sensor +from states import DefaultInitState, DefaultRunningState +from collections import OrderedDict +from lewis.devices import StateMachineDevice + + +class SimulatedVolumetricRig(StateMachineDevice): + def _initialize_data(self): + self.serial_command_mode = False -class SimulatedVolumetricRig(Device): - def __init__(self): # Set up all available gases self.system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) @@ -50,8 +55,19 @@ def __init__(self): # Target pressure: We can't set this via serial self._target_pressure = 12.34 - # Parent constructor - super(SimulatedVolumetricRig, self).__init__() + def _get_state_handlers(self): + return { + 'init': DefaultInitState(), + 'running': DefaultRunningState(), + } + + def _get_initial_state(self): + return 'init' + + def _get_transition_handlers(self): + return OrderedDict([ + (('init', 'running'), lambda: self.serial_command_mode), + ]) def identify(self): return "ISIS Volumetric Gas Handing Panel" diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 1539383c..b4878e7f 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,5 +1,4 @@ from lewis.adapters.stream import StreamAdapter, Cmd -from ..device import SimulatedVolumetricRig from ..sensor_status import SensorStatus from ..utilities import optional_int_string_format, convert_raw_to_int from ..valve_status import ValveStatus @@ -42,16 +41,15 @@ class VolumetricRigStreamInterface(StreamAdapter): out_terminator = "\r\n" def __init__(self, device, arguments=None): - self.rig = SimulatedVolumetricRig() # Lots of formatted output is based on fixed length strings self.gas_output_length = 20 super(VolumetricRigStreamInterface, self).__init__(device, arguments) def get_identity(self): - return "IDN,00," + self.rig.identify() + return "IDN,00," + self._device.identify() def _build_buffer_control_and_status_string(self, buffer_number): - buff = self.rig.buffer(buffer_number) + buff = self._device.buffer(buffer_number) assert buff is not None return " ".join([ "", @@ -74,7 +72,7 @@ def get_buffer_control_and_status(self, buffer_number_raw): if buffer_number <= 0: return buffer_too_low - elif buffer_number > len(self.rig.buffers()): + elif buffer_number > len(self._device.buffers()): return buffer_too_high else: return "BCS " + self._build_buffer_control_and_status_string(buffer_number) @@ -83,26 +81,26 @@ def get_ethernet_and_hmi_status(self): # The syntax of the return string is odd: the separators are not consistent return " ".join([ "ETN:PLC", - self.rig.plc.ip() + ",HMI", - self.rig.hmi().status(), - "," + self.rig.hmi().ip() + self._device.plc().ip() + ",HMI", + self._device.hmi().status(), + "," + self._device.hmi().ip() ]) def get_gas_control_and_status(self): return "\r\n".join( ["No No Buffer E O No System"] + - [self._build_buffer_control_and_status_string(b.index()) for b in self.rig.buffers()] + + [self._build_buffer_control_and_status_string(b.index()) for b in self._device.buffers()] + ["GCS"] ) def get_gas_mix_matrix(self): # Gather data - system_gases = self.rig.system_gases.gases() + system_gases = self._device.system_gases.gases() column_headers = [gas.name(self.gas_output_length, '|') for gas in system_gases] row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] - mixable_chars = [["<" if self.rig.mixer.can_mix(g1, g2) else "." for g1 in system_gases] + mixable_chars = [["<" if self._device.mixer.can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] # Put data in output format @@ -129,71 +127,71 @@ def get_gas_mix_matrix(self): return '\r\n'.join(lines) def gas_mix_check(self, gas1_index_raw, gas2_index_raw): - gas1 = self.rig.system_gases.gas_by_index(convert_raw_to_int(gas1_index_raw)) - gas2 = self.rig.system_gases.gas_by_index(convert_raw_to_int(gas2_index_raw)) + gas1 = self._device.system_gases.gas_by_index(convert_raw_to_int(gas1_index_raw)) + gas2 = self._device.system_gases.gas_by_index(convert_raw_to_int(gas2_index_raw)) if gas1 is None: - gas1 = self.rig.system_gases.gas_by_index(0) + gas1 = self._device.system_gases.gas_by_index(0) if gas2 is None: - gas2 = self.rig.system_gases.gas_by_index(0) + gas2 = self._device.system_gases.gas_by_index(0) return ' '.join(["GMC", gas1.index(as_string=True), gas1.name(self.gas_output_length, '.'), gas2.index(as_string=True), gas2.name(self.gas_output_length, '.'), - "ok" if self.rig.mixer.can_mix(gas1, gas2) else "NO"]) + "ok" if self._device.mixer.can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): - return self.rig.system_gases.gas_count() + return self._device.system_gases.gas_count() def get_hmi_status(self): - hmi = self.rig.hmi() + hmi = self._device.hmi() return ",".join(["HMI " + hmi.status() + " ", hmi.ip(), - "B",hmi.base_page(as_string=True, length=3), - "S",hmi.sub_page(as_string=True, length=3), - "C",hmi.count(as_string=True, length=4), - "L",hmi.limit(as_string=True, length=4), - "M",hmi.max_grabbed(as_string=True, length=4) + "B", hmi.base_page(as_string=True, length=3), + "S", hmi.sub_page(as_string=True, length=3), + "C", hmi.count(as_string=True, length=4), + "L", hmi.limit(as_string=True, length=4), + "M", hmi.max_grabbed(as_string=True, length=4) ]) def get_hmi_count_cycles(self): - return " ".join(["HMC"] + self.rig.hmi().count_cycles()) + return " ".join(["HMC"] + self._device.hmi().count_cycles()) def get_memory_location(self, location_raw): location = convert_raw_to_int(location_raw) return " ".join(["RDM", optional_int_string_format(location, as_string=True, length=4), - self.rig.memory_location(location, as_string=True, length=6)]) + self._device.memory_location(location, as_string=True, length=6)]) def get_pressure_and_temperature_status(self): status_codes = { - SensorStatus.DISABLED:"D", - SensorStatus.NO_REPLY:"X", - SensorStatus.VALUE_IN_RANGE:"O", - SensorStatus.VALUE_TOO_LOW:"L", - SensorStatus.VALUE_TOO_HIGH:"H", - SensorStatus.UNKNOWN:"?" + SensorStatus.DISABLED: "D", + SensorStatus.NO_REPLY: "X", + SensorStatus.VALUE_IN_RANGE: "O", + SensorStatus.VALUE_TOO_LOW: "L", + SensorStatus.VALUE_TOO_HIGH: "H", + SensorStatus.UNKNOWN: "?" } return "PTS " + \ "".join([status_codes[s.status()] for s in - self.rig.pressure_sensors(reverse=True)+self.rig.temperature_sensors(reverse=True)]) + self._device.pressure_sensors(reverse=True)+self._device.temperature_sensors(reverse=True)]) def get_pressures(self): return " ".join(["PMV"] + - [p.value(as_string=True) for p in self.rig.pressure_sensors(reverse=True)] + - ["T", self.rig.target_pressure(as_string=True)]) + [p.value(as_string=True) for p in self._device.pressure_sensors(reverse=True)] + + ["T", self._device.target_pressure(as_string=True)]) def get_temperatures(self): return " ".join(["TMV"] + - [t.value(as_string=True) for t in self.rig.temperature_sensors(reverse=True)]) + [t.value(as_string=True) for t in self._device.temperature_sensors(reverse=True)]) def get_valve_status(self): status_codes = { - ValveStatus.OPEN_AND_ENABLED:"O", - ValveStatus.CLOSED_AND_ENABLED:"E", - ValveStatus.CLOSED_AND_DISABLED:"D", - ValveStatus.OPEN_AND_DISABLED:"!" + ValveStatus.OPEN_AND_ENABLED: "O", + ValveStatus.CLOSED_AND_ENABLED: "E", + ValveStatus.CLOSED_AND_DISABLED: "D", + ValveStatus.OPEN_AND_DISABLED: "!" } - return "VST Valve Status " + "".join([status_codes[v] for v in self.rig.valves_status()]) + return "VST Valve Status " + "".join([status_codes[v] for v in self._device.valves_status()]) def _set_valve_status(self, valve_number_raw, set_to_open): valve_number = convert_raw_to_int(valve_number_raw) @@ -204,23 +202,23 @@ def _set_valve_status(self, valve_number_raw, set_to_open): str(valve_number) ]) args = list() - if self.rig.halted(): + if self._device.halted(): return "CLV Rejected only allowed when running" elif valve_number <= 0: return message_prefix + " Too Low" - elif valve_number <= self.rig.buffer_count(): - valve_status = self.rig.buffer_valve_is_open - open_valve = self.rig.open_buffer_valve - close_valve = self.rig.close_buffer_valve + elif valve_number <= self._device.buffer_count(): + valve_status = self._device.buffer_valve_is_open + open_valve = self._device.open_buffer_valve + close_valve = self._device.close_buffer_valve args.append(valve_number) - elif valve_number == self.rig.buffer_count() + 1: - valve_status = self.rig.cell_valve_is_open - open_valve = self.rig.open_cell_valve - close_valve = self.rig.close_cell_valve - elif valve_number == self.rig.buffer_count() + 2: - valve_status = self.rig.vacuum_valve_is_open - open_valve = self.rig.open_vacuum_valve - close_valve = self.rig.close_vacuum_valve + elif valve_number == self._device.buffer_count() + 1: + valve_status = self._device.cell_valve_is_open + open_valve = self._device.open_cell_valve + close_valve = self._device.close_cell_valve + elif valve_number == self._device.buffer_count() + 2: + valve_status = self._device.vacuum_valve_is_open + open_valve = self._device.open_vacuum_valve + close_valve = self._device.close_vacuum_valve else: return message_prefix + " Too High" @@ -228,7 +226,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open): open_valve(*args) if set_to_open else close_valve(*args) new_status = valve_status(*args) - status_codes = {True: "open", False:"closed"} + status_codes = {True: "open", False: "closed"} return " ".join([ command, "Valve Buffer", @@ -245,25 +243,25 @@ def set_valve_open(self, buffer_number_raw): return self._set_valve_status(buffer_number_raw, True) def halt(self): - if self.rig.halted(): + if self._device.halted(): message = "SYSTEM ALREADY HALTED" else: - self.rig.halt() - assert self.rig.halted() + self._device.halt() + assert self._device.halted() message = "SYSTEM NOW HALTED" return "HLT *** " + message + " ***" def get_system_status(self): return " ".join([ "STS", - self.rig.status_code(as_string=True, length=2), - "STOP" if self.rig.errors().run else "run", - "HMI" if self.rig.errors().hmi else "hmi", + self._device.status_code(as_string=True, length=2), + "STOP" if self._device.errors().run else "run", + "HMI" if self._device.errors().hmi else "hmi", # Spelling error duplicated as on device - "GUAGES" if self.rig.errors().gauges else "guages", - "COMMS" if self.rig.errors().comms else "comms", - "HLT" if self.rig.halted() else "halted", - "E-STOP" if self.rig.errors().estop else "estop" + "GUAGES" if self._device.errors().gauges else "guages", + "COMMS" if self._device.errors().comms else "comms", + "HLT" if self._device.halted() else "halted", + "E-STOP" if self._device.errors().estop else "estop" ]) # Information about ports, relays and com traffic are currently returned statically diff --git a/plankton_emulators/volumetric_rig/states.py b/plankton_emulators/volumetric_rig/states.py new file mode 100644 index 00000000..b2af750e --- /dev/null +++ b/plankton_emulators/volumetric_rig/states.py @@ -0,0 +1,10 @@ +from lewis.core.statemachine import State +from lewis.core import approaches + + +class DefaultInitState(State): + pass + + +class DefaultRunningState(State): + pass \ No newline at end of file From b41dd4027b65fdb34433c6b2082558fd90231554 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 15:53:51 +0000 Subject: [PATCH 0070/1466] Cannot open vacuum valve when any buffers open --- plankton_emulators/volumetric_rig/device.py | 7 ++++++- plankton_emulators/volumetric_rig/states.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 410d5831..cf92c55d 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -139,7 +139,8 @@ def open_cell_valve(self): self._cell_valve.open() def open_vacuum_valve(self): - self._vacuum_extract_valve.open() + if not any([b.valve_is_open() for b in self._buffers]): + self._vacuum_extract_valve.open() def close_buffer_valve(self, buffer_number): buff = self.buffer(buffer_number) @@ -154,3 +155,7 @@ def close_vacuum_valve(self): def buffers(self): return self._buffers + + def update_buffer_pressures(self, dt): + for b in self._buffers: + b.update_pressure(dt) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/states.py b/plankton_emulators/volumetric_rig/states.py index b2af750e..7d047450 100644 --- a/plankton_emulators/volumetric_rig/states.py +++ b/plankton_emulators/volumetric_rig/states.py @@ -1,5 +1,4 @@ from lewis.core.statemachine import State -from lewis.core import approaches class DefaultInitState(State): @@ -7,4 +6,5 @@ class DefaultInitState(State): class DefaultRunningState(State): - pass \ No newline at end of file + def in_state(self, dt): + self._context.update_buffer_pressures(dt) \ No newline at end of file From 789b769bb3cc118c1096772f80a204d13527a195 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 15:57:29 +0000 Subject: [PATCH 0071/1466] Add purge mechanism --- plankton_emulators/volumetric_rig/buffer.py | 4 ++-- plankton_emulators/volumetric_rig/device.py | 8 ++++---- plankton_emulators/volumetric_rig/gas.py | 4 ++-- plankton_emulators/volumetric_rig/hmi_device.py | 12 ++++++------ .../volumetric_rig/interfaces/stream_interface.py | 13 +++++++++++-- plankton_emulators/volumetric_rig/sensor.py | 4 ++-- plankton_emulators/volumetric_rig/utilities.py | 4 ++-- 7 files changed, 29 insertions(+), 20 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 8563e99f..46c72bc2 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -1,5 +1,5 @@ from valve import Valve -from utilities import optional_int_string_format +from utilities import format_int from two_gas_mixer import TwoGasMixer @@ -13,7 +13,7 @@ def __init__(self, index, buffer_gas, system_gas): self._valve = Valve() def index(self, as_string=False, length=1): - return optional_int_string_format(self._index, as_string, length) + return format_int(self._index, as_string, length) def open_valve(self, mixer): assert isinstance(mixer, TwoGasMixer) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index cf92c55d..f42bc02e 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -7,7 +7,7 @@ from hmi_device import HmiDevice from valve import Valve from error_states import ErrorStates -from utilities import optional_int_string_format, optional_float_string_format +from utilities import format_int, format_float from sensor import Sensor from states import DefaultInitState, DefaultRunningState from collections import OrderedDict @@ -82,7 +82,7 @@ def buffer(self, i): return None def memory_location(self, location, as_string, length): - return optional_int_string_format(location, as_string, length) + return format_int(location, as_string, length) def plc(self): return self._plc @@ -103,10 +103,10 @@ def temperature_sensors(self, reverse=False): return self._temperature_sensors if not reverse else list(reversed(self._temperature_sensors)) def target_pressure(self, as_string): - return optional_float_string_format(self._target_pressure, as_string) + return format_float(self._target_pressure, as_string) def status_code(self, as_string=False, length=None): - return optional_int_string_format(2, as_string, length) + return format_int(2, as_string, length) def errors(self): return self._errors diff --git a/plankton_emulators/volumetric_rig/gas.py b/plankton_emulators/volumetric_rig/gas.py index 6c12236a..5c88663c 100644 --- a/plankton_emulators/volumetric_rig/gas.py +++ b/plankton_emulators/volumetric_rig/gas.py @@ -1,5 +1,5 @@ from types import StringType, IntType -from utilities import pad_string, optional_int_string_format +from utilities import pad_string, format_int class Gas(object): @@ -12,4 +12,4 @@ def name(self, length=None, padding_character=" "): return pad_string(self._name, length, padding_character) def index(self, as_string=False, length=2): - return optional_int_string_format(self._index, as_string, length) \ No newline at end of file + return format_int(self._index, as_string, length) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/hmi_device.py b/plankton_emulators/volumetric_rig/hmi_device.py index 7213a183..2cf9a50e 100644 --- a/plankton_emulators/volumetric_rig/hmi_device.py +++ b/plankton_emulators/volumetric_rig/hmi_device.py @@ -1,5 +1,5 @@ from ethernet_device import EthernetDevice -from utilities import optional_int_string_format +from utilities import format_int class HmiDevice(EthernetDevice): @@ -17,22 +17,22 @@ def __init__(self, ip): super(HmiDevice, self).__init__(ip) def base_page(self, as_string, length): - return optional_int_string_format(self._base_page, as_string, length) + return format_int(self._base_page, as_string, length) def sub_page(self, as_string, length): - return optional_int_string_format(self._sub_page, as_string, length) + return format_int(self._sub_page, as_string, length) def count_cycles(self): return list(self._count_cycles) def max_grabbed(self, as_string, length): - return optional_int_string_format(self._max_grabbed, as_string, length) + return format_int(self._max_grabbed, as_string, length) def limit(self, as_string, length): - return optional_int_string_format(self._limit, as_string, length) + return format_int(self._limit, as_string, length) def count(self, as_string, length): - return optional_int_string_format(self._count, as_string, length) + return format_int(self._count, as_string, length) def status(self): return self._status \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index b4878e7f..d162c012 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,6 +1,6 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..sensor_status import SensorStatus -from ..utilities import optional_int_string_format, convert_raw_to_int +from ..utilities import format_int, convert_raw_to_int from ..valve_status import ValveStatus @@ -11,6 +11,7 @@ class VolumetricRigStreamInterface(StreamAdapter): # Some commands that take input will respond with default (often invalid) parameters if not present. For example # "BCS" is the same as "BCS 00" and also "BCS AA". commands = { + Cmd("purge","^(.*)\!$"), Cmd("get_identity", "^IDN(?: .*)?$"), Cmd("get_identity", "^\?(?: .*)?$"), Cmd("get_buffer_control_and_status", "^BCS(?:\s(\S*))?.*$"), @@ -45,6 +46,14 @@ def __init__(self, device, arguments=None): self.gas_output_length = 20 super(VolumetricRigStreamInterface, self).__init__(device, arguments) + def purge(self, chars): + return " ".join([ + "PRG,00,Purge", + format_int(len(chars) + 1, True, 5), + "Characters", + chars+"!" + ]) + def get_identity(self): return "IDN,00," + self._device.identify() @@ -157,7 +166,7 @@ def get_hmi_count_cycles(self): def get_memory_location(self, location_raw): location = convert_raw_to_int(location_raw) - return " ".join(["RDM", optional_int_string_format(location, as_string=True, length=4), + return " ".join(["RDM", format_int(location, as_string=True, length=4), self._device.memory_location(location, as_string=True, length=6)]) def get_pressure_and_temperature_status(self): diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py index 3b36128c..38310cc6 100644 --- a/plankton_emulators/volumetric_rig/sensor.py +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -1,5 +1,5 @@ from sensor_status import SensorStatus -from utilities import optional_float_string_format +from utilities import format_float class Sensor(object): @@ -17,4 +17,4 @@ def set_value(self, v): self._value = v def value(self, as_string): - return optional_float_string_format(self._value, as_string) \ No newline at end of file + return format_float(self._value, as_string) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index b546ed95..daee586a 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -1,11 +1,11 @@ -def optional_int_string_format(i, as_string, length): +def format_int(i, as_string, length): if as_string: return str(i) if length is None else str(i)[:length].zfill(length) else: return i -def optional_float_string_format(f, as_string): +def format_float(f, as_string): return "{0:.2f}".format(f).zfill(5) if as_string else f From 8f92fd402e8ace421116061e5f01be25fb7e4037 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 16:03:45 +0000 Subject: [PATCH 0072/1466] Don't allow open/close commands on disabled valves --- plankton_emulators/volumetric_rig/device.py | 14 ++++++++++++-- .../interfaces/stream_interface.py | 16 +++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index f42bc02e..10b82b1b 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -125,10 +125,20 @@ def buffer_valve_is_open(self, buffer_number): return buff.valve_is_open() if buff is not None else False def vacuum_valve_is_open(self): - return self._vacuum_extract_valve.is_open + return self._vacuum_extract_valve.is_open() def cell_valve_is_open(self): - return self._cell_valve.is_open + return self._cell_valve.is_open() + + def buffer_valve_is_enabled(self, buffer_number): + buff = self.buffer(buffer_number) + return buff.valve_is_enabled() if buff is not None else False + + def vacuum_valve_is_enabled(self): + return self._vacuum_extract_valve.is_enabled() + + def cell_valve_is_enabled(self): + return self._cell_valve.is_enabled() def open_buffer_valve(self, buffer_number): buff = self.buffer(buffer_number) diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index d162c012..4ca6f355 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -216,24 +216,30 @@ def _set_valve_status(self, valve_number_raw, set_to_open): elif valve_number <= 0: return message_prefix + " Too Low" elif valve_number <= self._device.buffer_count(): - valve_status = self._device.buffer_valve_is_open + valve_enabled = self.device.buffer_valve_is_enabled + valve_is_open = self._device.buffer_valve_is_open open_valve = self._device.open_buffer_valve close_valve = self._device.close_buffer_valve args.append(valve_number) elif valve_number == self._device.buffer_count() + 1: - valve_status = self._device.cell_valve_is_open + valve_enabled = self.device.cell_valve_is_enabled + valve_is_open = self._device.cell_valve_is_open open_valve = self._device.open_cell_valve close_valve = self._device.close_cell_valve elif valve_number == self._device.buffer_count() + 2: - valve_status = self._device.vacuum_valve_is_open + valve_enabled = self.device.vacuum_valve_is_enabled + valve_is_open = self._device.vacuum_valve_is_open open_valve = self._device.open_vacuum_valve close_valve = self._device.close_vacuum_valve else: return message_prefix + " Too High" - original_status = valve_status(*args) + if not valve_enabled(*args): + return " ".join([command,"Rejected not enabled",format_int(valve_number,True,1)]) + + original_status = valve_is_open(*args) open_valve(*args) if set_to_open else close_valve(*args) - new_status = valve_status(*args) + new_status = valve_is_open(*args) status_codes = {True: "open", False: "closed"} return " ".join([ From 754f681ff073466d7b5048ec3512364d84889935 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 16:07:08 +0000 Subject: [PATCH 0073/1466] Add extra layer of halted logic --- plankton_emulators/volumetric_rig/device.py | 31 +++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 10b82b1b..48c5f4ad 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -16,6 +16,9 @@ class SimulatedVolumetricRig(StateMachineDevice): + + HALTED_MESSAGE = "Rejected only allowed when running" + def _initialize_data(self): self.serial_command_mode = False @@ -141,27 +144,33 @@ def cell_valve_is_enabled(self): return self._cell_valve.is_enabled() def open_buffer_valve(self, buffer_number): - buff = self.buffer(buffer_number) - if buff is not None: - buff.open_valve(self.mixer) + if not self._halted: + buff = self.buffer(buffer_number) + if buff is not None: + buff.open_valve(self.mixer) def open_cell_valve(self): - self._cell_valve.open() + if not self._halted: + self._cell_valve.open() def open_vacuum_valve(self): - if not any([b.valve_is_open() for b in self._buffers]): - self._vacuum_extract_valve.open() + if not self._halted: + if not any([b.valve_is_open() for b in self._buffers]): + self._vacuum_extract_valve.open() def close_buffer_valve(self, buffer_number): - buff = self.buffer(buffer_number) - if buff is not None: - buff.close_valve() + if not self._halted: + buff = self.buffer(buffer_number) + if buff is not None: + buff.close_valve() def close_cell_valve(self): - self._cell_valve.close() + if not self._halted: + self._cell_valve.close() def close_vacuum_valve(self): - self._vacuum_extract_valve.close() + if not self._halted: + self._vacuum_extract_valve.close() def buffers(self): return self._buffers From 00f6a479aca5a9f9da6e940953ea4829fe495849 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 16:39:55 +0000 Subject: [PATCH 0074/1466] Add target pressure logic into pressure sensor --- plankton_emulators/volumetric_rig/buffer.py | 25 ++++++++++++++++++- plankton_emulators/volumetric_rig/device.py | 15 ++++++----- .../volumetric_rig/pressure_sensor.py | 17 +++++++++++++ plankton_emulators/volumetric_rig/sensor.py | 2 +- plankton_emulators/volumetric_rig/valve.py | 6 +++++ 5 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 plankton_emulators/volumetric_rig/pressure_sensor.py diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 46c72bc2..88d8ecc6 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -4,6 +4,9 @@ class Buffer(object): + + PRESSURE_RATE = 1.0 + def __init__(self, index, buffer_gas, system_gas): assert buffer_gas is not None assert system_gas is not None @@ -11,6 +14,13 @@ def __init__(self, index, buffer_gas, system_gas): self._system_gas = system_gas self._index = index self._valve = Valve() + self._pressure = 0.0 + + def _disable_valve(self): + self._valve.disable() + + def _enable_valve(self): + self._valve.enable() def index(self, as_string=False, length=1): return format_int(self._index, as_string, length) @@ -36,4 +46,17 @@ def buffer_gas(self): return self._buffer_gas def system_gas(self): - return self._system_gas \ No newline at end of file + return self._system_gas + + def update_pressure(self, dt, pressure_limit): + if self._valve.is_open(): + self._pressure += Buffer.PRESSURE_RATE*dt + else: + self._pressure -= Buffer.PRESSURE_RATE*dt + + if self._pressure > pressure_limit: + self.close_valve() + self._disable_valve() + + def pressure(self): + return self._pressure \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 48c5f4ad..fda761ee 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -9,6 +9,7 @@ from error_states import ErrorStates from utilities import format_int, format_float from sensor import Sensor +from pressure_sensor import PressureSensor from states import DefaultInitState, DefaultRunningState from collections import OrderedDict @@ -20,7 +21,7 @@ class SimulatedVolumetricRig(StateMachineDevice): HALTED_MESSAGE = "Rejected only allowed when running" def _initialize_data(self): - self.serial_command_mode = False + self.serial_command_mode = True # Set up all available gases self.system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) @@ -41,9 +42,12 @@ def _initialize_data(self): self._plc = EthernetDevice("192.168.0.1") self._hmi = HmiDevice("192.168.0.2") + # Target pressure: We can't set this via serial + self._target_pressure = 100.00 + # Set up sensors self._temperature_sensors = [Sensor() for _ in range(9)] - self._pressure_sensors = [Sensor() for _ in range(5)] + self._pressure_sensors = [PressureSensor(self._target_pressure) for _ in range(5)] # Set up special valves self._supply_valve = Valve() @@ -55,9 +59,6 @@ def _initialize_data(self): self._status_code = 2 self._errors = ErrorStates() - # Target pressure: We can't set this via serial - self._target_pressure = 12.34 - def _get_state_handlers(self): return { 'init': DefaultInitState(), @@ -177,4 +178,6 @@ def buffers(self): def update_buffer_pressures(self, dt): for b in self._buffers: - b.update_pressure(dt) \ No newline at end of file + b.update_pressure(dt, self._target_pressure) + for p in self._pressure_sensors: + p.set_value(self._buffers[0].pressure()) diff --git a/plankton_emulators/volumetric_rig/pressure_sensor.py b/plankton_emulators/volumetric_rig/pressure_sensor.py new file mode 100644 index 00000000..70afb388 --- /dev/null +++ b/plankton_emulators/volumetric_rig/pressure_sensor.py @@ -0,0 +1,17 @@ +from sensor import Sensor +from sensor_status import SensorStatus + + +class PressureSensor(Sensor): + def __init__(self, target): + self._target = target + super(PressureSensor, self).__init__() + + def set_value(self, v): + super(PressureSensor, self).set_value(v) + if self._value < 0.0: + self._status = SensorStatus.VALUE_TOO_LOW + elif self._value > self._target: + self._status = SensorStatus.VALUE_TOO_HIGH + else: + self._status = SensorStatus.VALUE_IN_RANGE \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py index 38310cc6..57c271bf 100644 --- a/plankton_emulators/volumetric_rig/sensor.py +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -4,7 +4,7 @@ class Sensor(object): def __init__(self): - self._status = SensorStatus.UNKNOWN + self._status = SensorStatus.NO_REPLY self._value = 0.0 def set_status(self, status): diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py index efc767d9..51541e5f 100644 --- a/plankton_emulators/volumetric_rig/valve.py +++ b/plankton_emulators/volumetric_rig/valve.py @@ -25,3 +25,9 @@ def status(self): return ValveStatus.OPEN_AND_ENABLED if self._is_enabled else ValveStatus.OPEN_AND_DISABLED else: return ValveStatus.CLOSED_AND_ENABLED if self._is_enabled else ValveStatus.CLOSED_AND_DISABLED + + def disable(self): + self._is_enabled = False + + def enable(self): + self._is_enabled = True From 889a1f981343189c8e56e2d797cc21147a00516a Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 5 Jan 2017 16:44:18 +0000 Subject: [PATCH 0075/1466] Set limit behaviour --- plankton_emulators/volumetric_rig/buffer.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 88d8ecc6..9ea680e2 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -1,6 +1,7 @@ from valve import Valve from utilities import format_int from two_gas_mixer import TwoGasMixer +from lewis.core import approaches class Buffer(object): @@ -50,9 +51,10 @@ def system_gas(self): def update_pressure(self, dt, pressure_limit): if self._valve.is_open(): - self._pressure += Buffer.PRESSURE_RATE*dt + # Intentionally overshoot to check that the valve closes properly when the limit is reached + self._pressure = approaches.linear(self._pressure, 1.1*pressure_limit, Buffer.PRESSURE_RATE, dt) else: - self._pressure -= Buffer.PRESSURE_RATE*dt + self._pressure = approaches.linear(self._pressure, 0.0, Buffer.PRESSURE_RATE, dt) if self._pressure > pressure_limit: self.close_valve() From 40b7a97d00bd40bdd8c5c8dd4889726b475999cc Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 11:26:51 +0000 Subject: [PATCH 0076/1466] Add some dynamic behaviour --- plankton_emulators/volumetric_rig/buffer.py | 29 +++++---------- plankton_emulators/volumetric_rig/device.py | 36 ++++++++++++++----- .../volumetric_rig/pressure_sensor.py | 13 +++++-- plankton_emulators/volumetric_rig/sensor.py | 2 +- plankton_emulators/volumetric_rig/states.py | 2 +- 5 files changed, 49 insertions(+), 33 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 9ea680e2..ff24d901 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -1,13 +1,9 @@ from valve import Valve from utilities import format_int from two_gas_mixer import TwoGasMixer -from lewis.core import approaches class Buffer(object): - - PRESSURE_RATE = 1.0 - def __init__(self, index, buffer_gas, system_gas): assert buffer_gas is not None assert system_gas is not None @@ -15,7 +11,6 @@ def __init__(self, index, buffer_gas, system_gas): self._system_gas = system_gas self._index = index self._valve = Valve() - self._pressure = 0.0 def _disable_valve(self): self._valve.disable() @@ -34,6 +29,14 @@ def open_valve(self, mixer): def close_valve(self): self._valve.close() + def enable_valve(self): + self._valve.enable() + + def disable_valve(self): + # Valves must be closed before they are disabled + self._valve.close() + self._valve.disable() + def valve_is_open(self): return self._valve.is_open() @@ -47,18 +50,4 @@ def buffer_gas(self): return self._buffer_gas def system_gas(self): - return self._system_gas - - def update_pressure(self, dt, pressure_limit): - if self._valve.is_open(): - # Intentionally overshoot to check that the valve closes properly when the limit is reached - self._pressure = approaches.linear(self._pressure, 1.1*pressure_limit, Buffer.PRESSURE_RATE, dt) - else: - self._pressure = approaches.linear(self._pressure, 0.0, Buffer.PRESSURE_RATE, dt) - - if self._pressure > pressure_limit: - self.close_valve() - self._disable_valve() - - def pressure(self): - return self._pressure \ No newline at end of file + return self._system_gas \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index fda761ee..4bc76a81 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -27,9 +27,9 @@ def _initialize_data(self): self.system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) # Set mixable gases - self.mixer = TwoGasMixer() + self._mixer = TwoGasMixer() for name1, name2 in SeedGasData.mixable_gas_names(): - self.mixer.add_mixable(self.system_gases.gas_by_name(name1), self.system_gases.gas_by_name(name2)) + self._mixer.add_mixable(self.system_gases.gas_by_name(name1), self.system_gases.gas_by_name(name2)) # Set buffers buffer_gases = [(self.system_gases.gas_by_name(name1), @@ -47,7 +47,7 @@ def _initialize_data(self): # Set up sensors self._temperature_sensors = [Sensor() for _ in range(9)] - self._pressure_sensors = [PressureSensor(self._target_pressure) for _ in range(5)] + self._pressure_sensors = [PressureSensor() for _ in range(5)] # Set up special valves self._supply_valve = Valve() @@ -148,7 +148,7 @@ def open_buffer_valve(self, buffer_number): if not self._halted: buff = self.buffer(buffer_number) if buff is not None: - buff.open_valve(self.mixer) + buff.open_valve(self._mixer) def open_cell_valve(self): if not self._halted: @@ -176,8 +176,28 @@ def close_vacuum_valve(self): def buffers(self): return self._buffers - def update_buffer_pressures(self, dt): - for b in self._buffers: - b.update_pressure(dt, self._target_pressure) + def update_pressures(self, dt): + number_of_open_buffers = sum(1 for b in self._buffers if b.valve_is_open()) for p in self._pressure_sensors: - p.set_value(self._buffers[0].pressure()) + if number_of_open_buffers > 0: + # Approach a pressure above target pressure so we intentionally go over the limit + p.approach_value(dt, 1.1*self._target_pressure, float(number_of_open_buffers)/self.buffer_count()) + else: + p.approach_value(dt, 0.0) + self._check_pressure() + + # This calculates the pressure based on the 5 readings from the pressure sensor. At the moment this is done in an + # ad hoc fashion. The actual behaviour hasn't been set on the real device, and it is likely the output from the + # PMV command could change in the future to give the actual reference pressure. + def _overall_pressure(self): + return max(s.value() for s in self._pressure_sensors) + + def _check_pressure(self): + if self._overall_pressure() > self._target_pressure: + for b in self._buffers: + b.disable_valve() + elif self._overall_pressure() < 0.5*self._target_pressure: + for b in self._buffers: + b.enable_valve() + if self._overall_pressure() < 0.1*self._target_pressure: + b.open_valve(self._mixer) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/pressure_sensor.py b/plankton_emulators/volumetric_rig/pressure_sensor.py index 70afb388..8f7e5513 100644 --- a/plankton_emulators/volumetric_rig/pressure_sensor.py +++ b/plankton_emulators/volumetric_rig/pressure_sensor.py @@ -1,10 +1,14 @@ from sensor import Sensor from sensor_status import SensorStatus +from random import random +from lewis.core.approaches import linear as linearApproach class PressureSensor(Sensor): - def __init__(self, target): - self._target = target + + BASE_CHANGE_RATE = 10.0 + + def __init__(self): super(PressureSensor, self).__init__() def set_value(self, v): @@ -14,4 +18,7 @@ def set_value(self, v): elif self._value > self._target: self._status = SensorStatus.VALUE_TOO_HIGH else: - self._status = SensorStatus.VALUE_IN_RANGE \ No newline at end of file + self._status = SensorStatus.VALUE_IN_RANGE + + def approach_value(self, dt, target, rate_multiplier=1.0): + self._value = linearApproach(self._value, target, PressureSensor.BASE_CHANGE_RATE*rate_multiplier*random(), dt) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/sensor.py b/plankton_emulators/volumetric_rig/sensor.py index 57c271bf..7222b4a1 100644 --- a/plankton_emulators/volumetric_rig/sensor.py +++ b/plankton_emulators/volumetric_rig/sensor.py @@ -16,5 +16,5 @@ def status(self): def set_value(self, v): self._value = v - def value(self, as_string): + def value(self, as_string=False): return format_float(self._value, as_string) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/states.py b/plankton_emulators/volumetric_rig/states.py index 7d047450..aa395ba2 100644 --- a/plankton_emulators/volumetric_rig/states.py +++ b/plankton_emulators/volumetric_rig/states.py @@ -7,4 +7,4 @@ class DefaultInitState(State): class DefaultRunningState(State): def in_state(self, dt): - self._context.update_buffer_pressures(dt) \ No newline at end of file + self._context.update_pressures(dt) \ No newline at end of file From 83c38db9806cece62d62ca759bd8c683119c9099 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 11:48:24 +0000 Subject: [PATCH 0077/1466] Add private command until Michael tells me why lewis-control isn't working --- plankton_emulators/volumetric_rig/buffer.py | 6 ++- plankton_emulators/volumetric_rig/device.py | 11 ++++-- .../interfaces/stream_interface.py | 38 +++++++++++++++---- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index ff24d901..3bc9bea9 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -50,4 +50,8 @@ def buffer_gas(self): return self._buffer_gas def system_gas(self): - return self._system_gas \ No newline at end of file + return self._system_gas + + def set_system_gas(self, gas): + if not self._valve.is_open(): + self._system_gas = gas \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 4bc76a81..1017ab41 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -147,8 +147,10 @@ def cell_valve_is_enabled(self): def open_buffer_valve(self, buffer_number): if not self._halted: buff = self.buffer(buffer_number) - if buff is not None: - buff.open_valve(self._mixer) + # The buffer must exist and the system gas connected to it must be mixable with all the other current + # buffer gases + if buff is not None and all(self._mixer.can_mix(buff.system_gas(), b.buffer_gas()) for b in self._buffers): + buff.open_valve(self._mixer) def open_cell_valve(self): if not self._halted: @@ -200,4 +202,7 @@ def _check_pressure(self): for b in self._buffers: b.enable_valve() if self._overall_pressure() < 0.1*self._target_pressure: - b.open_valve(self._mixer) \ No newline at end of file + b.open_valve(self._mixer) + + def mixer(self): + return self._mixer \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 4ca6f355..8fb3b907 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -10,7 +10,7 @@ class VolumetricRigStreamInterface(StreamAdapter): # so "IDN" will respond as "IDN BLAH BLAH BLAH" and "BCS 01" would be the same has "BCS 01 02 03". # Some commands that take input will respond with default (often invalid) parameters if not present. For example # "BCS" is the same as "BCS 00" and also "BCS AA". - commands = { + serial_commands = { Cmd("purge","^(.*)\!$"), Cmd("get_identity", "^IDN(?: .*)?$"), Cmd("get_identity", "^\?(?: .*)?$"), @@ -38,6 +38,12 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("halt", "^HLT(?: .*)?$"), } + control_commands = { + Cmd("set_buffer_system_gas", "^_SBG(?:\s(\S*))?(?:\s(\S*))?.*$"), + } + + commands = set.union(serial_commands, control_commands) + in_terminator = "\r\n" out_terminator = "\r\n" @@ -109,7 +115,7 @@ def get_gas_mix_matrix(self): column_headers = [gas.name(self.gas_output_length, '|') for gas in system_gases] row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) for gas in system_gases] - mixable_chars = [["<" if self._device.mixer.can_mix(g1, g2) else "." for g1 in system_gases] + mixable_chars = [["<" if self._device.mixer().can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] # Put data in output format @@ -146,7 +152,7 @@ def gas_mix_check(self, gas1_index_raw, gas2_index_raw): return ' '.join(["GMC", gas1.index(as_string=True), gas1.name(self.gas_output_length, '.'), gas2.index(as_string=True), gas2.name(self.gas_output_length, '.'), - "ok" if self._device.mixer.can_mix(gas1, gas2) else "NO"]) + "ok" if self._device.mixer().can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): return self._device.system_gases.gas_count() @@ -216,18 +222,18 @@ def _set_valve_status(self, valve_number_raw, set_to_open): elif valve_number <= 0: return message_prefix + " Too Low" elif valve_number <= self._device.buffer_count(): - valve_enabled = self.device.buffer_valve_is_enabled + valve_enabled = self._device.buffer_valve_is_enabled valve_is_open = self._device.buffer_valve_is_open open_valve = self._device.open_buffer_valve close_valve = self._device.close_buffer_valve args.append(valve_number) elif valve_number == self._device.buffer_count() + 1: - valve_enabled = self.device.cell_valve_is_enabled + valve_enabled = self._device.cell_valve_is_enabled valve_is_open = self._device.cell_valve_is_open open_valve = self._device.open_cell_valve close_valve = self._device.close_cell_valve elif valve_number == self._device.buffer_count() + 2: - valve_enabled = self.device.vacuum_valve_is_enabled + valve_enabled = self._device.vacuum_valve_is_enabled valve_is_open = self._device.vacuum_valve_is_open open_valve = self._device.open_vacuum_valve close_valve = self._device.close_vacuum_valve @@ -299,4 +305,22 @@ def handle_error(self, request, error): if str(error) == "None of the device's commands matched.": return "URC,04,Unrecognised Command," + str(request) else: - print "An error occurred at request " + repr(request) + ": " + repr(error) \ No newline at end of file + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): + gas = self._device.system_gases.gas_by_index(convert_raw_to_int(gas_index_raw)) + buff = self._device.buffer(convert_raw_to_int(buffer_index_raw)) + if gas is not None and buff is not None: + original_gas = buff.system_gas() + buff.set_system_gas(gas) + new_gas = buff.system_gas() + return " ".join([ + "SBG Buffer", + buff.index(as_string=True), + "system gas was", + original_gas.name(), + "now", + new_gas.name() + ]) + else: + return "SBG Lookup failed" From cd538e5d687c07ba43cd7b0dc57646d736106c06 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 13:57:39 +0000 Subject: [PATCH 0078/1466] Add enable/disable valve function --- plankton_emulators/volumetric_rig/buffer.py | 2 +- plankton_emulators/volumetric_rig/device.py | 78 ++++++++++--- .../interfaces/stream_interface.py | 105 ++++++++++++------ .../volumetric_rig/pressure_sensor.py | 10 +- .../volumetric_rig/utilities.py | 13 ++- plankton_emulators/volumetric_rig/valve.py | 4 +- 6 files changed, 155 insertions(+), 57 deletions(-) diff --git a/plankton_emulators/volumetric_rig/buffer.py b/plankton_emulators/volumetric_rig/buffer.py index 3bc9bea9..45a34498 100644 --- a/plankton_emulators/volumetric_rig/buffer.py +++ b/plankton_emulators/volumetric_rig/buffer.py @@ -54,4 +54,4 @@ def system_gas(self): def set_system_gas(self, gas): if not self._valve.is_open(): - self._system_gas = gas \ No newline at end of file + self._system_gas = gas diff --git a/plankton_emulators/volumetric_rig/device.py b/plankton_emulators/volumetric_rig/device.py index 1017ab41..6867e943 100644 --- a/plankton_emulators/volumetric_rig/device.py +++ b/plankton_emulators/volumetric_rig/device.py @@ -22,6 +22,7 @@ class SimulatedVolumetricRig(StateMachineDevice): def _initialize_data(self): self.serial_command_mode = True + self._cycle_pressures = False # Set up all available gases self.system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) @@ -175,17 +176,63 @@ def close_vacuum_valve(self): if not self._halted: self._vacuum_extract_valve.close() + def enable_cell_valve(self): + if not self._halted: + self._cell_valve.enable() + + def enable_vacuum_valve(self): + if not self._halted: + self._vacuum_extract_valve.enable() + + def enable_buffer_valve(self, buffer_number): + if not self._halted: + buff = self.buffer(buffer_number) + if buff is not None: + buff.enable_valve() + + def disble_cell_valve(self): + if not self._halted: + self._cell_valve.disable() + + def disable_vacuum_valve(self): + if not self._halted: + self._vacuum_extract_valve.disable() + + def disable_buffer_valve(self, buffer_number): + if not self._halted: + buff = self.buffer(buffer_number) + if buff is not None: + buff.disable_valve() + def buffers(self): return self._buffers + def mixer(self): + return self._mixer + def update_pressures(self, dt): - number_of_open_buffers = sum(1 for b in self._buffers if b.valve_is_open()) - for p in self._pressure_sensors: - if number_of_open_buffers > 0: - # Approach a pressure above target pressure so we intentionally go over the limit - p.approach_value(dt, 1.1*self._target_pressure, float(number_of_open_buffers)/self.buffer_count()) - else: - p.approach_value(dt, 0.0) + + # This is a custom behaviour designed to cycle through various valve behaviours. It will ramp up the pressure + # to the maximum, close and disable all valves, then let the pressure drop and enable and subsequently reopen + # the valves + if self._cycle_pressures: + number_of_open_buffers = sum(1 for b in self._buffers if b.valve_is_open()) + for p in self._pressure_sensors: + base_rate = 10.0 + if number_of_open_buffers > 0: + # Approach a pressure above target pressure so we intentionally go over the limit + from random import random + p.approach_value(dt, 1.1*self._target_pressure, + base_rate*float(number_of_open_buffers)/self.buffer_count()*random()) + else: + p.approach_value(dt, 0.0, base_rate) + if self._overall_pressure() < 0.5 * self._target_pressure: + for b in self._buffers: + b.enable_valve() + if self._overall_pressure() < 0.1 * self._target_pressure: + b.open_valve(self._mixer) + + # Check if system pressure is over the maximum and disable valves if necessary self._check_pressure() # This calculates the pressure based on the 5 readings from the pressure sensor. At the moment this is done in an @@ -198,11 +245,14 @@ def _check_pressure(self): if self._overall_pressure() > self._target_pressure: for b in self._buffers: b.disable_valve() - elif self._overall_pressure() < 0.5*self._target_pressure: - for b in self._buffers: - b.enable_valve() - if self._overall_pressure() < 0.1*self._target_pressure: - b.open_valve(self._mixer) - def mixer(self): - return self._mixer \ No newline at end of file + def cycle_pressures(self, on): + assert isinstance(on, bool) + self._cycle_pressures = on + + def set_pressures(self, value): + for p in self._pressure_sensors: + p.set_value(value, self._target_pressure) + + def set_pressure_target(self, value): + self._target_pressure = value diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py index 8fb3b907..03df4805 100644 --- a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/plankton_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,6 +1,6 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..sensor_status import SensorStatus -from ..utilities import format_int, convert_raw_to_int +from ..utilities import format_int, convert_raw_to_int, convert_raw_to_float, convert_raw_to_bool from ..valve_status import ValveStatus @@ -12,34 +12,37 @@ class VolumetricRigStreamInterface(StreamAdapter): # "BCS" is the same as "BCS 00" and also "BCS AA". serial_commands = { Cmd("purge","^(.*)\!$"), - Cmd("get_identity", "^IDN(?: .*)?$"), - Cmd("get_identity", "^\?(?: .*)?$"), + Cmd("get_identity", "^IDN(?:\s.*)?$"), + Cmd("get_identity", "^\?(?:\s.*)?$"), Cmd("get_buffer_control_and_status", "^BCS(?:\s(\S*))?.*$"), - Cmd("get_ethernet_and_hmi_status", "^ETN(?: .*)?$"), - Cmd("get_gas_control_and_status", "^GCS(?: .*)?$"), - Cmd("get_gas_mix_matrix", "^GMM(?: .*)?$"), + Cmd("get_ethernet_and_hmi_status", "^ETN(?:\s.*)?$"), + Cmd("get_gas_control_and_status", "^GCS(?:\s.*)?$"), + Cmd("get_gas_mix_matrix", "^GMM(?:\s.*)?$"), Cmd("gas_mix_check", "^GMC(?:\s(\S*))?(?:\s(\S*))?.*$"), - Cmd("get_gas_number_available", "^GNA(?: .*)?$"), - Cmd("get_hmi_status", "^HMI(?: .*)?$"), - Cmd("get_hmi_count_cycles", "^HMC(?: .*)?$"), + Cmd("get_gas_number_available", "^GNA(?:\s.*)?$"), + Cmd("get_hmi_status", "^HMI(?:\s.*)?$"), + Cmd("get_hmi_count_cycles", "^HMC(?:\s.*)?$"), Cmd("get_memory_location", "^RDM(?:\s(\S*))?.*"), - Cmd("get_pressure_and_temperature_status", "^PTS(?: .*)?$"), - Cmd("get_pressures", "^PMV(?: .*)?$"), - Cmd("get_temperatures", "^TMV(?: .*)?$"), - Cmd("get_ports_and_relays_hex", "^PTR(?: .*)?$"), - Cmd("get_ports_output", "^POT(?: .*)?$"), - Cmd("get_ports_input", "^PIN(?: .*)?$"), - Cmd("get_ports_relays", "^PRY(?: .*)?$"), - Cmd("get_system_status", "^STS(?: .*)?$"), - Cmd("get_com_activity", "^COM(?: .*)?$"), - Cmd("get_valve_status", "^VST(?: .*)?$"), + Cmd("get_pressure_and_temperature_status", "^PTS(?:\s.*)?$"), + Cmd("get_pressures", "^PMV(?:\s.*)?$"), + Cmd("get_temperatures", "^TMV(?:\s.*)?$"), + Cmd("get_ports_and_relays_hex", "^PTR(?:\s.*)?$"), + Cmd("get_ports_output", "^POT(?:\s.*)?$"), + Cmd("get_ports_input", "^PIN(?:\s.*)?$"), + Cmd("get_ports_relays", "^PRY(?:\s.*)?$"), + Cmd("get_system_status", "^STS(?:\s.*)?$"), + Cmd("get_com_activity", "^COM(?:\s.*)?$"), + Cmd("get_valve_status", "^VST(?:\s.*)?$"), Cmd("set_valve_open", "^OPV(?:\s(\S*))?.*$"), Cmd("set_valve_closed", "^CLV(?:\s(\S*))?.*$"), - Cmd("halt", "^HLT(?: .*)?$"), + Cmd("halt", "^HLT(?:\s.*)?$"), } control_commands = { Cmd("set_buffer_system_gas", "^_SBG(?:\s(\S*))?(?:\s(\S*))?.*$"), + Cmd("set_pressure_cycling", "^_PCY(?:\s(\S*)).*$"), + Cmd("set_pressures", "^_SPR(?:\s(\S*)).*$"), + Cmd("set_pressure_target", "^_SPT(?:\s(\S*)).*$"), } commands = set.union(serial_commands, control_commands) @@ -208,9 +211,16 @@ def get_valve_status(self): } return "VST Valve Status " + "".join([status_codes[v] for v in self._device.valves_status()]) - def _set_valve_status(self, valve_number_raw, set_to_open): + def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=None): valve_number = convert_raw_to_int(valve_number_raw) - command = "OPV" if set_to_open else "CLV" + + if set_to_open is not None: + command = "OPV" if set_to_open else "CLV" + elif set_to_enabled is not None: + command = "_SVE" if set_to_open else "_SVD" + else: + assert False + message_prefix = " ".join([ command, "Value", @@ -222,39 +232,50 @@ def _set_valve_status(self, valve_number_raw, set_to_open): elif valve_number <= 0: return message_prefix + " Too Low" elif valve_number <= self._device.buffer_count(): - valve_enabled = self._device.buffer_valve_is_enabled + valve_is_enabled = self._device.buffer_valve_is_enabled valve_is_open = self._device.buffer_valve_is_open open_valve = self._device.open_buffer_valve close_valve = self._device.close_buffer_valve + enable_valve = self._device.enable_buffer_valve + disable_valve = self._device.disable_buffer_valve args.append(valve_number) elif valve_number == self._device.buffer_count() + 1: - valve_enabled = self._device.cell_valve_is_enabled + valve_is_enabled = self._device.cell_valve_is_enabled valve_is_open = self._device.cell_valve_is_open open_valve = self._device.open_cell_valve close_valve = self._device.close_cell_valve + enable_valve = self._device.enable_cell_valve + disable_valve = self._device.disable_cell_valve elif valve_number == self._device.buffer_count() + 2: - valve_enabled = self._device.vacuum_valve_is_enabled + valve_is_enabled = self._device.vacuum_valve_is_enabled valve_is_open = self._device.vacuum_valve_is_open open_valve = self._device.open_vacuum_valve close_valve = self._device.close_vacuum_valve + enable_valve = self._device.enable_vacuum_valve + disable_valve = self._device.disable_vacuum_valve else: return message_prefix + " Too High" - if not valve_enabled(*args): - return " ".join([command,"Rejected not enabled",format_int(valve_number,True,1)]) + if set_to_open is not None: + if not valve_is_enabled(*args): + return " ".join([command,"Rejected not enabled",format_int(valve_number,True,1)]) + original_status = valve_is_open(*args) + open_valve(*args) if set_to_open else close_valve(*args) + new_status = valve_is_open(*args) + status_codes = {True: "open", False: "closed"} + if set_to_enabled is not None: + original_status = valve_is_enabled(*args) + enable_valve(*args) if set_to_enabled else disable_valve(*args) + new_status = valve_is_enabled(*args) + status_codes = {True: "enabled", False: "disabled"} - original_status = valve_is_open(*args) - open_valve(*args) if set_to_open else close_valve(*args) - new_status = valve_is_open(*args) - - status_codes = {True: "open", False: "closed"} return " ".join([ command, "Valve Buffer", str(valve_number), - status_codes[original_status], - "was", status_codes[new_status], + "was", + status_codes[original_status], ]) def set_valve_closed(self, buffer_number_raw): @@ -324,3 +345,19 @@ def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): ]) else: return "SBG Lookup failed" + + def set_pressure_cycling(self, on_int_raw): + self._device.cycle_pressures(bool(convert_raw_to_int(on_int_raw))) + + def set_pressures(self, value_raw): + value = convert_raw_to_float(value_raw) + self._device.set_pressures(value) + return "SPR Pressures set to " + str(value) + + def set_pressure_target(self, value_raw): + value = convert_raw_to_float(value_raw) + self._device.set_pressure_target(value) + print "SPT Pressure target set to " + str(value) + + def disable_valve(self, number_raw): + self._set_valve_status(convert_raw_to_bool(number_raw), set_to_enabled=True) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/pressure_sensor.py b/plankton_emulators/volumetric_rig/pressure_sensor.py index 8f7e5513..f0baa127 100644 --- a/plankton_emulators/volumetric_rig/pressure_sensor.py +++ b/plankton_emulators/volumetric_rig/pressure_sensor.py @@ -6,19 +6,17 @@ class PressureSensor(Sensor): - BASE_CHANGE_RATE = 10.0 - def __init__(self): super(PressureSensor, self).__init__() - def set_value(self, v): + def set_value(self, v, target): super(PressureSensor, self).set_value(v) if self._value < 0.0: self._status = SensorStatus.VALUE_TOO_LOW - elif self._value > self._target: + elif self._value > target: self._status = SensorStatus.VALUE_TOO_HIGH else: self._status = SensorStatus.VALUE_IN_RANGE - def approach_value(self, dt, target, rate_multiplier=1.0): - self._value = linearApproach(self._value, target, PressureSensor.BASE_CHANGE_RATE*rate_multiplier*random(), dt) \ No newline at end of file + def approach_value(self, dt, target, rate): + self._value = linearApproach(self._value, target, rate, dt) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/utilities.py b/plankton_emulators/volumetric_rig/utilities.py index daee586a..460ad072 100644 --- a/plankton_emulators/volumetric_rig/utilities.py +++ b/plankton_emulators/volumetric_rig/utilities.py @@ -20,4 +20,15 @@ def convert_raw_to_int(raw): elif type(raw) == StringType: return int(raw.zfill(1)) else: - return 0 \ No newline at end of file + return 0 + + +def convert_raw_to_float(raw): + try: + return float(raw) + except: + return 0.0 + + +def convert_raw_to_bool(raw): + return bool(convert_raw_to_int(raw)) \ No newline at end of file diff --git a/plankton_emulators/volumetric_rig/valve.py b/plankton_emulators/volumetric_rig/valve.py index 51541e5f..2873e8a0 100644 --- a/plankton_emulators/volumetric_rig/valve.py +++ b/plankton_emulators/volumetric_rig/valve.py @@ -27,7 +27,9 @@ def status(self): return ValveStatus.CLOSED_AND_ENABLED if self._is_enabled else ValveStatus.CLOSED_AND_DISABLED def disable(self): - self._is_enabled = False + # Can't disable an open valve + if not self._is_open: + self._is_enabled = False def enable(self): self._is_enabled = True From 1ffa325273b2594c63bdb4cc0edf0f41b290a8d7 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 15:17:00 +0000 Subject: [PATCH 0079/1466] Rename plankton_emulators to lewis_emulators because framework name has changed --- {plankton_emulators => lewis_emulators}/__init__.py | 0 .../iris_cryo_valve/__init__.py | 0 {plankton_emulators => lewis_emulators}/iris_cryo_valve/device.py | 0 .../iris_cryo_valve/interfaces/__init__.py | 0 .../iris_cryo_valve/interfaces/stream_interface.py | 0 .../volumetric_rig/__init__.py | 0 {plankton_emulators => lewis_emulators}/volumetric_rig/buffer.py | 0 {plankton_emulators => lewis_emulators}/volumetric_rig/device.py | 0 .../volumetric_rig/error_states.py | 0 .../volumetric_rig/ethernet_device.py | 0 {plankton_emulators => lewis_emulators}/volumetric_rig/gas.py | 0 .../volumetric_rig/hmi_device.py | 0 .../volumetric_rig/interfaces/__init__.py | 0 .../volumetric_rig/interfaces/stream_interface.py | 0 .../volumetric_rig/pressure_sensor.py | 0 .../volumetric_rig/seed_gas_data.py | 0 {plankton_emulators => lewis_emulators}/volumetric_rig/sensor.py | 0 .../volumetric_rig/sensor_status.py | 0 {plankton_emulators => lewis_emulators}/volumetric_rig/states.py | 0 .../volumetric_rig/system_gases.py | 0 .../volumetric_rig/two_gas_mixer.py | 0 .../volumetric_rig/utilities.py | 0 {plankton_emulators => lewis_emulators}/volumetric_rig/valve.py | 0 .../volumetric_rig/valve_status.py | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename {plankton_emulators => lewis_emulators}/__init__.py (100%) rename {plankton_emulators => lewis_emulators}/iris_cryo_valve/__init__.py (100%) rename {plankton_emulators => lewis_emulators}/iris_cryo_valve/device.py (100%) rename {plankton_emulators => lewis_emulators}/iris_cryo_valve/interfaces/__init__.py (100%) rename {plankton_emulators => lewis_emulators}/iris_cryo_valve/interfaces/stream_interface.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/__init__.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/buffer.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/device.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/error_states.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/ethernet_device.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/gas.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/hmi_device.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/interfaces/__init__.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/interfaces/stream_interface.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/pressure_sensor.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/seed_gas_data.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/sensor.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/sensor_status.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/states.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/system_gases.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/two_gas_mixer.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/utilities.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/valve.py (100%) rename {plankton_emulators => lewis_emulators}/volumetric_rig/valve_status.py (100%) diff --git a/plankton_emulators/__init__.py b/lewis_emulators/__init__.py similarity index 100% rename from plankton_emulators/__init__.py rename to lewis_emulators/__init__.py diff --git a/plankton_emulators/iris_cryo_valve/__init__.py b/lewis_emulators/iris_cryo_valve/__init__.py similarity index 100% rename from plankton_emulators/iris_cryo_valve/__init__.py rename to lewis_emulators/iris_cryo_valve/__init__.py diff --git a/plankton_emulators/iris_cryo_valve/device.py b/lewis_emulators/iris_cryo_valve/device.py similarity index 100% rename from plankton_emulators/iris_cryo_valve/device.py rename to lewis_emulators/iris_cryo_valve/device.py diff --git a/plankton_emulators/iris_cryo_valve/interfaces/__init__.py b/lewis_emulators/iris_cryo_valve/interfaces/__init__.py similarity index 100% rename from plankton_emulators/iris_cryo_valve/interfaces/__init__.py rename to lewis_emulators/iris_cryo_valve/interfaces/__init__.py diff --git a/plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py b/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py similarity index 100% rename from plankton_emulators/iris_cryo_valve/interfaces/stream_interface.py rename to lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py diff --git a/plankton_emulators/volumetric_rig/__init__.py b/lewis_emulators/volumetric_rig/__init__.py similarity index 100% rename from plankton_emulators/volumetric_rig/__init__.py rename to lewis_emulators/volumetric_rig/__init__.py diff --git a/plankton_emulators/volumetric_rig/buffer.py b/lewis_emulators/volumetric_rig/buffer.py similarity index 100% rename from plankton_emulators/volumetric_rig/buffer.py rename to lewis_emulators/volumetric_rig/buffer.py diff --git a/plankton_emulators/volumetric_rig/device.py b/lewis_emulators/volumetric_rig/device.py similarity index 100% rename from plankton_emulators/volumetric_rig/device.py rename to lewis_emulators/volumetric_rig/device.py diff --git a/plankton_emulators/volumetric_rig/error_states.py b/lewis_emulators/volumetric_rig/error_states.py similarity index 100% rename from plankton_emulators/volumetric_rig/error_states.py rename to lewis_emulators/volumetric_rig/error_states.py diff --git a/plankton_emulators/volumetric_rig/ethernet_device.py b/lewis_emulators/volumetric_rig/ethernet_device.py similarity index 100% rename from plankton_emulators/volumetric_rig/ethernet_device.py rename to lewis_emulators/volumetric_rig/ethernet_device.py diff --git a/plankton_emulators/volumetric_rig/gas.py b/lewis_emulators/volumetric_rig/gas.py similarity index 100% rename from plankton_emulators/volumetric_rig/gas.py rename to lewis_emulators/volumetric_rig/gas.py diff --git a/plankton_emulators/volumetric_rig/hmi_device.py b/lewis_emulators/volumetric_rig/hmi_device.py similarity index 100% rename from plankton_emulators/volumetric_rig/hmi_device.py rename to lewis_emulators/volumetric_rig/hmi_device.py diff --git a/plankton_emulators/volumetric_rig/interfaces/__init__.py b/lewis_emulators/volumetric_rig/interfaces/__init__.py similarity index 100% rename from plankton_emulators/volumetric_rig/interfaces/__init__.py rename to lewis_emulators/volumetric_rig/interfaces/__init__.py diff --git a/plankton_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py similarity index 100% rename from plankton_emulators/volumetric_rig/interfaces/stream_interface.py rename to lewis_emulators/volumetric_rig/interfaces/stream_interface.py diff --git a/plankton_emulators/volumetric_rig/pressure_sensor.py b/lewis_emulators/volumetric_rig/pressure_sensor.py similarity index 100% rename from plankton_emulators/volumetric_rig/pressure_sensor.py rename to lewis_emulators/volumetric_rig/pressure_sensor.py diff --git a/plankton_emulators/volumetric_rig/seed_gas_data.py b/lewis_emulators/volumetric_rig/seed_gas_data.py similarity index 100% rename from plankton_emulators/volumetric_rig/seed_gas_data.py rename to lewis_emulators/volumetric_rig/seed_gas_data.py diff --git a/plankton_emulators/volumetric_rig/sensor.py b/lewis_emulators/volumetric_rig/sensor.py similarity index 100% rename from plankton_emulators/volumetric_rig/sensor.py rename to lewis_emulators/volumetric_rig/sensor.py diff --git a/plankton_emulators/volumetric_rig/sensor_status.py b/lewis_emulators/volumetric_rig/sensor_status.py similarity index 100% rename from plankton_emulators/volumetric_rig/sensor_status.py rename to lewis_emulators/volumetric_rig/sensor_status.py diff --git a/plankton_emulators/volumetric_rig/states.py b/lewis_emulators/volumetric_rig/states.py similarity index 100% rename from plankton_emulators/volumetric_rig/states.py rename to lewis_emulators/volumetric_rig/states.py diff --git a/plankton_emulators/volumetric_rig/system_gases.py b/lewis_emulators/volumetric_rig/system_gases.py similarity index 100% rename from plankton_emulators/volumetric_rig/system_gases.py rename to lewis_emulators/volumetric_rig/system_gases.py diff --git a/plankton_emulators/volumetric_rig/two_gas_mixer.py b/lewis_emulators/volumetric_rig/two_gas_mixer.py similarity index 100% rename from plankton_emulators/volumetric_rig/two_gas_mixer.py rename to lewis_emulators/volumetric_rig/two_gas_mixer.py diff --git a/plankton_emulators/volumetric_rig/utilities.py b/lewis_emulators/volumetric_rig/utilities.py similarity index 100% rename from plankton_emulators/volumetric_rig/utilities.py rename to lewis_emulators/volumetric_rig/utilities.py diff --git a/plankton_emulators/volumetric_rig/valve.py b/lewis_emulators/volumetric_rig/valve.py similarity index 100% rename from plankton_emulators/volumetric_rig/valve.py rename to lewis_emulators/volumetric_rig/valve.py diff --git a/plankton_emulators/volumetric_rig/valve_status.py b/lewis_emulators/volumetric_rig/valve_status.py similarity index 100% rename from plankton_emulators/volumetric_rig/valve_status.py rename to lewis_emulators/volumetric_rig/valve_status.py From f0572c9cfe9da8259b3845d9b76aacce53646f91 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 15:54:06 +0000 Subject: [PATCH 0080/1466] Make variable static --- .../interfaces/stream_interface.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 03df4805..801e26a1 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -11,7 +11,7 @@ class VolumetricRigStreamInterface(StreamAdapter): # Some commands that take input will respond with default (often invalid) parameters if not present. For example # "BCS" is the same as "BCS 00" and also "BCS AA". serial_commands = { - Cmd("purge","^(.*)\!$"), + Cmd("purge", "^(.*)\!$"), Cmd("get_identity", "^IDN(?:\s.*)?$"), Cmd("get_identity", "^\?(?:\s.*)?$"), Cmd("get_buffer_control_and_status", "^BCS(?:\s(\S*))?.*$"), @@ -38,6 +38,8 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("halt", "^HLT(?:\s.*)?$"), } + # These commands are intended solely as a control mechanism for the emulator. As an alternative, the Lewis + # backdoor can be used to modify the device state. control_commands = { Cmd("set_buffer_system_gas", "^_SBG(?:\s(\S*))?(?:\s(\S*))?.*$"), Cmd("set_pressure_cycling", "^_PCY(?:\s(\S*)).*$"), @@ -47,13 +49,12 @@ class VolumetricRigStreamInterface(StreamAdapter): commands = set.union(serial_commands, control_commands) - in_terminator = "\r\n" - out_terminator = "\r\n" + # You may need to change these to \r\n if using Telnet" + in_terminator = "\r" + out_terminator = "\r" - def __init__(self, device, arguments=None): - # Lots of formatted output is based on fixed length strings - self.gas_output_length = 20 - super(VolumetricRigStreamInterface, self).__init__(device, arguments) + # Lots of formatted output for the volumetric rig is based on fixed length strings + output_length = 20 def purge(self, chars): return " ".join([ @@ -73,7 +74,7 @@ def _build_buffer_control_and_status_string(self, buffer_number): "", buff.index(as_string=True), buff.buffer_gas().index(as_string=True), - buff.buffer_gas().name(self.gas_output_length, " "), + buff.buffer_gas().name(VolumetricRigStreamInterface.output_length, " "), "E" if buff.valve_is_enabled() else "d", "O" if buff.valve_is_open() else "c", buff.system_gas().index(as_string=True), @@ -115,9 +116,10 @@ def get_gas_mix_matrix(self): # Gather data system_gases = self._device.system_gases.gases() - column_headers = [gas.name(self.gas_output_length, '|') for gas in system_gases] - row_titles = [" ".join([gas.index(as_string=True), gas.name(self.gas_output_length, ' ')]) - for gas in system_gases] + column_headers = [gas.name(VolumetricRigStreamInterface.output_length, '|') for gas in system_gases] + row_titles = [" ".join( + [gas.index(as_string=True), gas.name(VolumetricRigStreamInterface.output_length, ' ')] + ) for gas in system_gases] mixable_chars = [["<" if self._device.mixer().can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] @@ -153,8 +155,8 @@ def gas_mix_check(self, gas1_index_raw, gas2_index_raw): gas2 = self._device.system_gases.gas_by_index(0) return ' '.join(["GMC", - gas1.index(as_string=True), gas1.name(self.gas_output_length, '.'), - gas2.index(as_string=True), gas2.name(self.gas_output_length, '.'), + gas1.index(as_string=True), gas1.name(VolumetricRigStreamInterface.output_length, '.'), + gas2.index(as_string=True), gas2.name(VolumetricRigStreamInterface.output_length, '.'), "ok" if self._device.mixer().can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): @@ -258,12 +260,12 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N if set_to_open is not None: if not valve_is_enabled(*args): - return " ".join([command,"Rejected not enabled",format_int(valve_number,True,1)]) + return " ".join([command, "Rejected not enabled", format_int(valve_number, True, 1)]) original_status = valve_is_open(*args) open_valve(*args) if set_to_open else close_valve(*args) new_status = valve_is_open(*args) status_codes = {True: "open", False: "closed"} - if set_to_enabled is not None: + else: original_status = valve_is_enabled(*args) enable_valve(*args) if set_to_enabled else disable_valve(*args) new_status = valve_is_enabled(*args) @@ -360,4 +362,4 @@ def set_pressure_target(self, value_raw): print "SPT Pressure target set to " + str(value) def disable_valve(self, number_raw): - self._set_valve_status(convert_raw_to_bool(number_raw), set_to_enabled=True) \ No newline at end of file + self._set_valve_status(convert_raw_to_bool(number_raw), set_to_enabled=True) From 77e3860ade85b0be6812f1b4673ebfa20d7dd3f0 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 16:55:43 +0000 Subject: [PATCH 0081/1466] Add docs to stream_interface --- .../interfaces/stream_interface.py | 352 +++++++++++++++--- 1 file changed, 303 insertions(+), 49 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 801e26a1..4a8ce596 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -33,8 +33,8 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("get_system_status", "^STS(?:\s.*)?$"), Cmd("get_com_activity", "^COM(?:\s.*)?$"), Cmd("get_valve_status", "^VST(?:\s.*)?$"), - Cmd("set_valve_open", "^OPV(?:\s(\S*))?.*$"), - Cmd("set_valve_closed", "^CLV(?:\s(\S*))?.*$"), + Cmd("open_valve", "^OPV(?:\s(\S*))?.*$"), + Cmd("close_valve", "^CLV(?:\s(\S*))?.*$"), Cmd("halt", "^HLT(?:\s.*)?$"), } @@ -45,6 +45,8 @@ class VolumetricRigStreamInterface(StreamAdapter): Cmd("set_pressure_cycling", "^_PCY(?:\s(\S*)).*$"), Cmd("set_pressures", "^_SPR(?:\s(\S*)).*$"), Cmd("set_pressure_target", "^_SPT(?:\s(\S*)).*$"), + Cmd("enable_valve", "^_ENV(?:\s(\S*)).*$"), + Cmd("disable_valve", "^_DIV(?:\s(\S*)).*$"), } commands = set.union(serial_commands, control_commands) @@ -57,6 +59,11 @@ class VolumetricRigStreamInterface(StreamAdapter): output_length = 20 def purge(self, chars): + """ Responds any current input to the screen without executing it + + Returns: + string : Purge message including ignored input + """ return " ".join([ "PRG,00,Purge", format_int(len(chars) + 1, True, 5), @@ -65,9 +72,23 @@ def purge(self, chars): ]) def get_identity(self): + """ Responds with the devices identity + + Returns: + string : Device identity + """ return "IDN,00," + self._device.identify() def _build_buffer_control_and_status_string(self, buffer_number): + """ + Get information about a specific buffer, its valve state and the gases connected to it. + + Args: + buffer_number : The index of the buffer + + Returns: + string : Information about the requested buffer + """ buff = self._device.buffer(buffer_number) assert buff is not None return " ".join([ @@ -82,6 +103,16 @@ def _build_buffer_control_and_status_string(self, buffer_number): ]) def get_buffer_control_and_status(self, buffer_number_raw): + """ + Get information about a specific buffer, its valve state and the gases connected to it. + + Args: + buffer_number_raw : The buffer "number" entered by a user. Although a number is expected, the command will + accept other types of input + + Returns: + string : Information about the requested buffer + """ buffer_number = convert_raw_to_int(buffer_number_raw) message_prefix = "BCS" num_length = 3 @@ -97,7 +128,13 @@ def get_buffer_control_and_status(self, buffer_number_raw): return "BCS " + self._build_buffer_control_and_status_string(buffer_number) def get_ethernet_and_hmi_status(self): - # The syntax of the return string is odd: the separators are not consistent + """ + Get information about the rig's hmi and plc ethernet devices + + Returns: + string : Information about the ethernet devices status. The syntax of the return string is odd: the + separators are not consistent + """ return " ".join([ "ETN:PLC", self._device.plc().ip() + ",HMI", @@ -106,6 +143,12 @@ def get_ethernet_and_hmi_status(self): ]) def get_gas_control_and_status(self): + """ + Get a list of information about all the buffers, their associated gases and valve statuses + + Returns: + string : Buffer information. One line per buffer with a header + """ return "\r\n".join( ["No No Buffer E O No System"] + [self._build_buffer_control_and_status_string(b.index()) for b in self._device.buffers()] + @@ -113,13 +156,16 @@ def get_gas_control_and_status(self): ) def get_gas_mix_matrix(self): - # Gather data - system_gases = self._device.system_gases.gases() + """ + Get information about which gases can be mixed together + Returns: + string : A 2D matrix representation of the ability to mix different gases with column and row titles + """ + system_gases = self._device.system_gases.gases() column_headers = [gas.name(VolumetricRigStreamInterface.output_length, '|') for gas in system_gases] - row_titles = [" ".join( - [gas.index(as_string=True), gas.name(VolumetricRigStreamInterface.output_length, ' ')] - ) for gas in system_gases] + row_titles = [" ".join([gas.index(as_string=True), gas.name(VolumetricRigStreamInterface.output_length, ' ')]) + for gas in system_gases] mixable_chars = [["<" if self._device.mixer().can_mix(g1, g2) else "." for g1 in system_gases] for g2 in system_gases] @@ -147,6 +193,18 @@ def get_gas_mix_matrix(self): return '\r\n'.join(lines) def gas_mix_check(self, gas1_index_raw, gas2_index_raw): + """ + Query whether two gases can be mixed + + Args: + gas1_index_raw : The index of the first gas. Although a number is expected, the command will + accept other types of input + gas2_index_raw : As above for the 2nd gas + + Returns: + string : An echo of the name and index of the requested gases as well as an ok/NO indicating whether the + gases can be mixed + """ gas1 = self._device.system_gases.gas_by_index(convert_raw_to_int(gas1_index_raw)) gas2 = self._device.system_gases.gas_by_index(convert_raw_to_int(gas2_index_raw)) if gas1 is None: @@ -160,9 +218,21 @@ def gas_mix_check(self, gas1_index_raw, gas2_index_raw): "ok" if self._device.mixer().can_mix(gas1, gas2) else "NO"]) def get_gas_number_available(self): + """ + Get the number of available gases + + Returns: + string : The number of available gases + """ return self._device.system_gases.gas_count() def get_hmi_status(self): + """ + Get the current status of the HMI + + Returns: + string : Information about the HMI + """ hmi = self._device.hmi() return ",".join(["HMI " + hmi.status() + " ", hmi.ip(), "B", hmi.base_page(as_string=True, length=3), @@ -173,14 +243,37 @@ def get_hmi_status(self): ]) def get_hmi_count_cycles(self): + """ + Get information about how frequently the HMI is disconnected + + Returns: + string : A list of integers indicating the number of occurrences of a disconnected count cycle of a specific + length + """ return " ".join(["HMC"] + self._device.hmi().count_cycles()) def get_memory_location(self, location_raw): + """ + Get the value stored in a particular location in memory + + Args: + location_raw : The memory location to read. Although a number is expected, the command will accept other + types of input + + Returns: + string : The memory location and the value stored there + """ location = convert_raw_to_int(location_raw) return " ".join(["RDM", format_int(location, as_string=True, length=4), self._device.memory_location(location, as_string=True, length=6)]) def get_pressure_and_temperature_status(self): + """ + Get the status of the temperature and pressure sensors + + Returns: + string : A letter for each sensor indicating its status. Refer to the spec for the meaning and sensor order + """ status_codes = { SensorStatus.DISABLED: "D", @@ -196,15 +289,35 @@ def get_pressure_and_temperature_status(self): self._device.pressure_sensors(reverse=True)+self._device.temperature_sensors(reverse=True)]) def get_pressures(self): + """ + Get the current pressure sensor readings, and target pressure + + Returns: + string : The pressure readings from each of the pressure sensors and the target pressure which, if exceeded, + will cause all buffer valves to close and disable + """ return " ".join(["PMV"] + [p.value(as_string=True) for p in self._device.pressure_sensors(reverse=True)] + ["T", self._device.target_pressure(as_string=True)]) def get_temperatures(self): + """ + Get the current temperature reading + + Returns: + string : The current temperature for each of the temperature sensors + """ return " ".join(["TMV"] + [t.value(as_string=True) for t in self._device.temperature_sensors(reverse=True)]) def get_valve_status(self): + """ + Get the status of the buffer and system valves + + Returns: + string : The status of each of the system valves represented by a letter. Refer to the specification for the + exact meaning and order + """ status_codes = { ValveStatus.OPEN_AND_ENABLED: "O", ValveStatus.CLOSED_AND_ENABLED: "E", @@ -214,63 +327,71 @@ def get_valve_status(self): return "VST Valve Status " + "".join([status_codes[v] for v in self._device.valves_status()]) def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=None): + """ + Change the valve status + + Returns: + string : Indicates the valve number, previous state, and new state + """ valve_number = convert_raw_to_int(valve_number_raw) + # We should have exactly one of these arguments if set_to_open is not None: command = "OPV" if set_to_open else "CLV" elif set_to_enabled is not None: - command = "_SVE" if set_to_open else "_SVD" + command = "_DIV" if set_to_open else "_ENV" else: assert False + # The command and valve number are always included message_prefix = " ".join([ command, "Value", str(valve_number) ]) + + # Select an action based on the input parameters. args = list() if self._device.halted(): - return "CLV Rejected only allowed when running" + return command + " Rejected only allowed when running" elif valve_number <= 0: return message_prefix + " Too Low" elif valve_number <= self._device.buffer_count(): - valve_is_enabled = self._device.buffer_valve_is_enabled - valve_is_open = self._device.buffer_valve_is_open - open_valve = self._device.open_buffer_valve - close_valve = self._device.close_buffer_valve - enable_valve = self._device.enable_buffer_valve - disable_valve = self._device.disable_buffer_valve + if set_to_open is not None: + action = self._device.open_buffer_valve if set_to_open else self._device.close_buffer_valve + current_state = self._device.buffer_valve_is_open + else: + action = self._device.enable_buffer_valve if set_to_enabled else self._device.disable_buffer_valve + current_state = self._device.buffer_valve_is_enabled args.append(valve_number) elif valve_number == self._device.buffer_count() + 1: - valve_is_enabled = self._device.cell_valve_is_enabled - valve_is_open = self._device.cell_valve_is_open - open_valve = self._device.open_cell_valve - close_valve = self._device.close_cell_valve - enable_valve = self._device.enable_cell_valve - disable_valve = self._device.disable_cell_valve + if set_to_open is not None: + action = self._device.open_cell_valve if set_to_open else self._device.close_cell_valve + current_state = self._device.cell_valve_is_open + else: + action = self._device.enable_cell_valve if set_to_enabled else self._device.disable_cell_valve + current_state = self._device.cell_valve_is_enabled elif valve_number == self._device.buffer_count() + 2: - valve_is_enabled = self._device.vacuum_valve_is_enabled - valve_is_open = self._device.vacuum_valve_is_open - open_valve = self._device.open_vacuum_valve - close_valve = self._device.close_vacuum_valve - enable_valve = self._device.enable_vacuum_valve - disable_valve = self._device.disable_vacuum_valve + if set_to_open is not None: + action = self._device.open_vacuum_valve if set_to_open else self._device.close_vacuum_valve + current_state = self._device.vacuum_valve_is_open + else: + action = self._device.enable_vacuum_valve if set_to_enabled else self._device.disable_vacuum_valve + current_state = self._device.vacuum_valve_is_enabled else: return message_prefix + " Too High" if set_to_open is not None: - if not valve_is_enabled(*args): + if not current_state(*args): return " ".join([command, "Rejected not enabled", format_int(valve_number, True, 1)]) - original_status = valve_is_open(*args) - open_valve(*args) if set_to_open else close_valve(*args) - new_status = valve_is_open(*args) status_codes = {True: "open", False: "closed"} else: - original_status = valve_is_enabled(*args) - enable_valve(*args) if set_to_enabled else disable_valve(*args) - new_status = valve_is_enabled(*args) status_codes = {True: "enabled", False: "disabled"} + # Execute the action and get the status before and after + original_status = current_state(*args) + action(*args) + new_status = current_state(*args) return " ".join([ command, "Valve Buffer", @@ -280,13 +401,77 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N status_codes[original_status], ]) - def set_valve_closed(self, buffer_number_raw): - return self._set_valve_status(buffer_number_raw, False) + def close_valve(self, valve_number_raw): + """ + Close a valve + + Args: + valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply + valve cannot be controlled via serial. Although a number is expected, the command will accept other types + of input + + + Returns: + string : Indicates the valve number, previous state, and new state + """ + return self._set_valve_status(valve_number_raw, set_to_open=False) + + def open_valve(self, valve_number_raw): + """ + Open a valve + + Args: + valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply + valve cannot be controlled via serial. Although a number is expected, the command will accept other types + of input + + + Returns: + string : Indicates the valve number, previous state, and new state + """ + return self._set_valve_status(valve_number_raw, set_to_open=True) - def set_valve_open(self, buffer_number_raw): - return self._set_valve_status(buffer_number_raw, True) + def enable_valve(self, valve_number_raw): + """ + Enable a valve + + Args: + valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply + valve cannot be controlled via serial. Although a number is expected, the command will accept other types + of input + + + Returns: + string : Indicates the valve number, previous state, and new state + """ + self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=True) + + def disable_valve(self, valve_number_raw): + """ + Disable a valve + + Args: + valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply + valve cannot be controlled via serial. Although a number is expected, the command will accept other types + of input + + + Returns: + string : Indicates the valve number, previous state, and new state + """ + self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=False) def halt(self): + """ + Halts the device. No further valve commands will be accepted. + + Returns: + string : Indicates that the system has been, or was already halted + """ if self._device.halted(): message = "SYSTEM ALREADY HALTED" else: @@ -296,6 +481,14 @@ def halt(self): return "HLT *** " + message + " ***" def get_system_status(self): + """ + Get information about the current system state. + + + Returns: + string : Information about the system. Capitalisation of a particular word indicates an error has occurred + in that subsystem. Refer to the specification for the meaning of system codes + """ return " ".join([ "STS", self._device.status_code(as_string=True, length=2), @@ -308,29 +501,52 @@ def get_system_status(self): "E-STOP" if self._device.errors().estop else "estop" ]) - # Information about ports, relays and com traffic are currently returned statically def get_ports_and_relays_hex(self): + """ + Returns: + string : Information about the ports and relays + """ return "PTR I:00 0000 0000 R:0000 0200 0000 O:00 0000 4400" def get_ports_output(self): + """ + Returns: + string : Information about the port output + """ return "POT qwertyus vsbbbbbbzyxwvuts aBhecSssvsbbbbbb" def get_ports_input(self): + """ + Returns: + string : Information about the port input + """ return "PIN qwertyui zyxwvutsrqponmlk abcdefghijklmneb" def get_ports_relays(self): + """ + Returns: + string : Information about the port relays. + """ return "PRY qwertyuiopasdfgh zyxwhmLsrqponmlk abcdefghihlbhace" def get_com_activity(self): + """ + Returns: + string : Information about activity over the COM port + """ return "COM ok 0113/0000" - def handle_error(self, request, error): - if str(error) == "None of the device's commands matched.": - return "URC,04,Unrecognised Command," + str(request) - else: - print "An error occurred at request " + repr(request) + ": " + repr(error) - def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): + """ + Changes the system gas associated with a particular buffer + + Args: + buffer_index_raw : The index of the buffer to update + gas_index_raw : The index of the gas to update + + Returns: + string : Indicates the buffer changed, the previous system gas and the new system gas + """ gas = self._device.system_gases.gas_by_index(convert_raw_to_int(gas_index_raw)) buff = self._device.buffer(convert_raw_to_int(buffer_index_raw)) if gas is not None and buff is not None: @@ -349,17 +565,55 @@ def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): return "SBG Lookup failed" def set_pressure_cycling(self, on_int_raw): - self._device.cycle_pressures(bool(convert_raw_to_int(on_int_raw))) + """ + Starts a sequence of pressure cycling. The pressure is increased until the target is met. This disables all + buffer valves. The system pressure is decreased and the valves are renabled and reopened when the pressure + falls below set limits. When the pressure reaches a minimum, the cycle is restarted. This allows simulation + of various valve conditions. + + Args: + on_int_raw : Whether to switch cycling on(1)/off(other) + + Returns: + string : Indicates whether cycling is enabled + """ + cycle = convert_raw_to_bool(on_int_raw) + self._device.cycle_pressures(cycle) + return "_PCY " + str(cycle) def set_pressures(self, value_raw): + """ + Set the reading for all pressure sensors to a fixed value + + Args: + value_raw : The value to apply to the pressure sensors + + Returns: + string : Echo the new pressure + """ value = convert_raw_to_float(value_raw) self._device.set_pressures(value) return "SPR Pressures set to " + str(value) def set_pressure_target(self, value_raw): + """ + Set the target (limit) pressure for the system + + Args: + value_raw : The new pressure target + + Returns: + string : Echo the new target + """ value = convert_raw_to_float(value_raw) self._device.set_pressure_target(value) print "SPT Pressure target set to " + str(value) - def disable_valve(self, number_raw): - self._set_valve_status(convert_raw_to_bool(number_raw), set_to_enabled=True) + def handle_error(self, request, error): + """ + Handle errors during execution. May be an unrecognised command or emulator failure. + """ + if str(error) == "None of the device's commands matched.": + return "URC,04,Unrecognised Command," + str(request) + else: + print "An error occurred at request " + repr(request) + ": " + repr(error) From 9e25a13fb0eccf0536ed9732f8c7205eadd8e49c Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 17:01:58 +0000 Subject: [PATCH 0082/1466] Add some additional documentation --- lewis_emulators/volumetric_rig/device.py | 18 ++++++++++++------ .../volumetric_rig/seed_gas_data.py | 3 +++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/volumetric_rig/device.py b/lewis_emulators/volumetric_rig/device.py index 6867e943..b65f58f1 100644 --- a/lewis_emulators/volumetric_rig/device.py +++ b/lewis_emulators/volumetric_rig/device.py @@ -21,6 +21,7 @@ class SimulatedVolumetricRig(StateMachineDevice): HALTED_MESSAGE = "Rejected only allowed when running" def _initialize_data(self): + # Device modes self.serial_command_mode = True self._cycle_pressures = False @@ -87,6 +88,7 @@ def buffer(self, i): return None def memory_location(self, location, as_string, length): + # Currently returns the value at the location as the location itself. return format_int(location, as_string, length) def plc(self): @@ -111,6 +113,7 @@ def target_pressure(self, as_string): return format_float(self._target_pressure, as_string) def status_code(self, as_string=False, length=None): + # We don't currently do any logic for the system status, it always returns 2 return format_int(2, as_string, length) def errors(self): @@ -120,6 +123,7 @@ def valve_count(self): return len(self.valves_status()) def valves_status(self): + # The valve order goes: supply, vacuum, cell, buffer(n), ... , buffer(1) return [self._supply_valve.status(), self._vacuum_extract_valve.status(), self._cell_valve.status()] + \ @@ -159,6 +163,7 @@ def open_cell_valve(self): def open_vacuum_valve(self): if not self._halted: + # We can't open the vacuum valve if any of the buffer valves are open if not any([b.valve_is_open() for b in self._buffers]): self._vacuum_extract_valve.open() @@ -190,7 +195,7 @@ def enable_buffer_valve(self, buffer_number): if buff is not None: buff.enable_valve() - def disble_cell_valve(self): + def disable_cell_valve(self): if not self._halted: self._cell_valve.disable() @@ -211,7 +216,6 @@ def mixer(self): return self._mixer def update_pressures(self, dt): - # This is a custom behaviour designed to cycle through various valve behaviours. It will ramp up the pressure # to the maximum, close and disable all valves, then let the pressure drop and enable and subsequently reopen # the valves @@ -235,22 +239,24 @@ def update_pressures(self, dt): # Check if system pressure is over the maximum and disable valves if necessary self._check_pressure() - # This calculates the pressure based on the 5 readings from the pressure sensor. At the moment this is done in an - # ad hoc fashion. The actual behaviour hasn't been set on the real device, and it is likely the output from the - # PMV command could change in the future to give the actual reference pressure. def _overall_pressure(self): + # This calculates the pressure based on the 5 readings from the pressure sensor. At the moment this is done in + # an ad hoc fashion. The actual behaviour hasn't been set on the real device, and it is likely the output from + # the PMV command could change in the future to give the actual reference pressure. return max(s.value() for s in self._pressure_sensors) def _check_pressure(self): + # Disable the buffer valves if the pressure exceeds the limit if self._overall_pressure() > self._target_pressure: for b in self._buffers: b.disable_valve() def cycle_pressures(self, on): - assert isinstance(on, bool) + # Switch on/off pressure cycling self._cycle_pressures = on def set_pressures(self, value): + # Sets all pressure sensors to have the same value for p in self._pressure_sensors: p.set_value(value, self._target_pressure) diff --git a/lewis_emulators/volumetric_rig/seed_gas_data.py b/lewis_emulators/volumetric_rig/seed_gas_data.py index 57d04ac9..467ab039 100644 --- a/lewis_emulators/volumetric_rig/seed_gas_data.py +++ b/lewis_emulators/volumetric_rig/seed_gas_data.py @@ -1,4 +1,7 @@ class SeedGasData(object): + """Contains information about gases and their mixing properties used to set up the initial device state""" + + # Gas names unknown = "UNKNOWN" empty = "EMPTY" vacuum_extract = "VACUUM EXTRACT" From 895e60960353702b77a25cbd761dbe5b1ac44f30 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 17:10:16 +0000 Subject: [PATCH 0083/1466] Check for enabled state on open/close --- .../volumetric_rig/interfaces/stream_interface.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 4a8ce596..7c6e5a8e 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -352,6 +352,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N # Select an action based on the input parameters. args = list() + enabled = lambda *args: None if self._device.halted(): return command + " Rejected only allowed when running" elif valve_number <= 0: @@ -359,6 +360,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N elif valve_number <= self._device.buffer_count(): if set_to_open is not None: action = self._device.open_buffer_valve if set_to_open else self._device.close_buffer_valve + enabled = self._device.buffer_valve_is_enabled current_state = self._device.buffer_valve_is_open else: action = self._device.enable_buffer_valve if set_to_enabled else self._device.disable_buffer_valve @@ -367,6 +369,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N elif valve_number == self._device.buffer_count() + 1: if set_to_open is not None: action = self._device.open_cell_valve if set_to_open else self._device.close_cell_valve + enabled = self._device.cell_valve_is_enabled current_state = self._device.cell_valve_is_open else: action = self._device.enable_cell_valve if set_to_enabled else self._device.disable_cell_valve @@ -374,6 +377,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N elif valve_number == self._device.buffer_count() + 2: if set_to_open is not None: action = self._device.open_vacuum_valve if set_to_open else self._device.close_vacuum_valve + enabled = self._device.vacuum_valve_is_enabled current_state = self._device.vacuum_valve_is_open else: action = self._device.enable_vacuum_valve if set_to_enabled else self._device.disable_vacuum_valve @@ -382,7 +386,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N return message_prefix + " Too High" if set_to_open is not None: - if not current_state(*args): + if not enabled(*args): return " ".join([command, "Rejected not enabled", format_int(valve_number, True, 1)]) status_codes = {True: "open", False: "closed"} else: From ff3f47b305dcf112560699bf138fca4e73053eee Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 17:11:43 +0000 Subject: [PATCH 0084/1466] Functions must return to output to port --- .../volumetric_rig/interfaces/stream_interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 7c6e5a8e..7b451187 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -451,7 +451,7 @@ def enable_valve(self, valve_number_raw): Returns: string : Indicates the valve number, previous state, and new state """ - self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=True) + return self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=True) def disable_valve(self, valve_number_raw): """ @@ -467,7 +467,7 @@ def disable_valve(self, valve_number_raw): Returns: string : Indicates the valve number, previous state, and new state """ - self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=False) + return self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=False) def halt(self): """ @@ -611,7 +611,7 @@ def set_pressure_target(self, value_raw): """ value = convert_raw_to_float(value_raw) self._device.set_pressure_target(value) - print "SPT Pressure target set to " + str(value) + return "SPT Pressure target set to " + str(value) def handle_error(self, request, error): """ From 7d755d5cbbd3d07b0104236bc8d6b24dc43f30b4 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 6 Jan 2017 17:13:48 +0000 Subject: [PATCH 0085/1466] Enable/disable valve number isn't a bool --- .../volumetric_rig/interfaces/stream_interface.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 7b451187..a2ffa23b 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -339,7 +339,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N if set_to_open is not None: command = "OPV" if set_to_open else "CLV" elif set_to_enabled is not None: - command = "_DIV" if set_to_open else "_ENV" + command = "_ENV" if set_to_enabled else "_DIV" else: assert False @@ -451,7 +451,7 @@ def enable_valve(self, valve_number_raw): Returns: string : Indicates the valve number, previous state, and new state """ - return self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=True) + return self._set_valve_status(convert_raw_to_int(valve_number_raw), set_to_enabled=True) def disable_valve(self, valve_number_raw): """ @@ -467,7 +467,7 @@ def disable_valve(self, valve_number_raw): Returns: string : Indicates the valve number, previous state, and new state """ - return self._set_valve_status(convert_raw_to_bool(valve_number_raw), set_to_enabled=False) + return self._set_valve_status(convert_raw_to_int(valve_number_raw), set_to_enabled=False) def halt(self): """ From 58d5150aac70617962dd7bdb344cacbbccbad0d9 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 9 Jan 2017 11:24:02 +0000 Subject: [PATCH 0086/1466] Make system gases attribute private --- lewis_emulators/volumetric_rig/device.py | 11 +++++++---- .../interfaces/stream_interface.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lewis_emulators/volumetric_rig/device.py b/lewis_emulators/volumetric_rig/device.py index b65f58f1..578eee10 100644 --- a/lewis_emulators/volumetric_rig/device.py +++ b/lewis_emulators/volumetric_rig/device.py @@ -26,16 +26,16 @@ def _initialize_data(self): self._cycle_pressures = False # Set up all available gases - self.system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) + self._system_gases = SystemGases([Gas(i, SeedGasData.names[i]) for i in range(len(SeedGasData.names))]) # Set mixable gases self._mixer = TwoGasMixer() for name1, name2 in SeedGasData.mixable_gas_names(): - self._mixer.add_mixable(self.system_gases.gas_by_name(name1), self.system_gases.gas_by_name(name2)) + self._mixer.add_mixable(self._system_gases.gas_by_name(name1), self._system_gases.gas_by_name(name2)) # Set buffers - buffer_gases = [(self.system_gases.gas_by_name(name1), - self.system_gases.gas_by_name(name2)) + buffer_gases = [(self._system_gases.gas_by_name(name1), + self._system_gases.gas_by_name(name2)) for name1, name2 in SeedGasData.buffer_gas_names()] self._buffers = [Buffer(i + 1, buffer_gases[i][0], buffer_gases[i][1]) for i in range(len(buffer_gases))] @@ -262,3 +262,6 @@ def set_pressures(self, value): def set_pressure_target(self, value): self._target_pressure = value + + def system_gases(self): + return self._system_gases diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index a2ffa23b..3e73519d 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -162,7 +162,7 @@ def get_gas_mix_matrix(self): Returns: string : A 2D matrix representation of the ability to mix different gases with column and row titles """ - system_gases = self._device.system_gases.gases() + system_gases = self._device.system_gases().gases() column_headers = [gas.name(VolumetricRigStreamInterface.output_length, '|') for gas in system_gases] row_titles = [" ".join([gas.index(as_string=True), gas.name(VolumetricRigStreamInterface.output_length, ' ')]) for gas in system_gases] @@ -188,7 +188,7 @@ def get_gas_mix_matrix(self): words.append(' '.join(mixable_chars[i])) lines.append(''.join(words)) # Add footer - lines.append("GMM allowance limit: " + str(len(system_gases))) + lines.append("GMM allowance limit: " + str(self._device.system_gases().gas_count())) return '\r\n'.join(lines) @@ -205,12 +205,12 @@ def gas_mix_check(self, gas1_index_raw, gas2_index_raw): string : An echo of the name and index of the requested gases as well as an ok/NO indicating whether the gases can be mixed """ - gas1 = self._device.system_gases.gas_by_index(convert_raw_to_int(gas1_index_raw)) - gas2 = self._device.system_gases.gas_by_index(convert_raw_to_int(gas2_index_raw)) + gas1 = self._device.system_gases().gas_by_index(convert_raw_to_int(gas1_index_raw)) + gas2 = self._device.system_gases().gas_by_index(convert_raw_to_int(gas2_index_raw)) if gas1 is None: - gas1 = self._device.system_gases.gas_by_index(0) + gas1 = self._device.system_gases().gas_by_index(0) if gas2 is None: - gas2 = self._device.system_gases.gas_by_index(0) + gas2 = self._device.system_gases().gas_by_index(0) return ' '.join(["GMC", gas1.index(as_string=True), gas1.name(VolumetricRigStreamInterface.output_length, '.'), @@ -224,7 +224,7 @@ def get_gas_number_available(self): Returns: string : The number of available gases """ - return self._device.system_gases.gas_count() + return self._device.system_gases().gas_count() def get_hmi_status(self): """ @@ -551,7 +551,7 @@ def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): Returns: string : Indicates the buffer changed, the previous system gas and the new system gas """ - gas = self._device.system_gases.gas_by_index(convert_raw_to_int(gas_index_raw)) + gas = self._device.system_gases().gas_by_index(convert_raw_to_int(gas_index_raw)) buff = self._device.buffer(convert_raw_to_int(buffer_index_raw)) if gas is not None and buff is not None: original_gas = buff.system_gas() From 9559072cddca64b6b93cbf3e7d06b2aae961560c Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 14:35:56 +0000 Subject: [PATCH 0087/1466] Add basic keitherley emulator --- lewis_emulators/keitherley_2400/__init__.py | 8 +++++ lewis_emulators/keitherley_2400/device.py | 35 +++++++++++++++++++ .../keitherley_2400/interfaces/__init__.py | 3 ++ .../interfaces/stream_interface.py | 30 ++++++++++++++++ lewis_emulators/keitherley_2400/utilities.py | 2 ++ 5 files changed, 78 insertions(+) create mode 100644 lewis_emulators/keitherley_2400/__init__.py create mode 100644 lewis_emulators/keitherley_2400/device.py create mode 100644 lewis_emulators/keitherley_2400/interfaces/__init__.py create mode 100644 lewis_emulators/keitherley_2400/interfaces/stream_interface.py create mode 100644 lewis_emulators/keitherley_2400/utilities.py diff --git a/lewis_emulators/keitherley_2400/__init__.py b/lewis_emulators/keitherley_2400/__init__.py new file mode 100644 index 00000000..6a502118 --- /dev/null +++ b/lewis_emulators/keitherley_2400/__init__.py @@ -0,0 +1,8 @@ +from .device import SimulatedKeitherley2400 + +__all__ = ['SimulatedKeitherley2400'] + + + + + diff --git a/lewis_emulators/keitherley_2400/device.py b/lewis_emulators/keitherley_2400/device.py new file mode 100644 index 00000000..dcc44faa --- /dev/null +++ b/lewis_emulators/keitherley_2400/device.py @@ -0,0 +1,35 @@ +from utilities import format +from collections import OrderedDict +from lewis.devices import StateMachineDevice + + +class SimulatedVolumetricRig(StateMachineDevice): + + def _initialize_data(self): + self.serial_command_mode = True + self._current = 1.111 + self._voltage = 2.222 + self._resistance = 3.333 + + def _get_state_handlers(self): + return { + 'init': DefaultInitState(), + 'running': DefaultRunningState(), + } + + def _get_initial_state(self): + return 'init' + + def _get_transition_handlers(self): + return OrderedDict([ + (('init', 'running'), lambda: self.serial_command_mode), + ]) + + def get_voltage(self, as_string=False): + return format(self._voltage, as_string) + + def get_current(self, as_string=False): + return format(self._current, as_string) + + def get_resistance(self, as_string=False): + return format(self._resistance, as_string) diff --git a/lewis_emulators/keitherley_2400/interfaces/__init__.py b/lewis_emulators/keitherley_2400/interfaces/__init__.py new file mode 100644 index 00000000..fe861ef8 --- /dev/null +++ b/lewis_emulators/keitherley_2400/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Keitherley2400StreamInterface + +__all__ = ['Keitherley2400StreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py new file mode 100644 index 00000000..5bc9c97a --- /dev/null +++ b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py @@ -0,0 +1,30 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + + +class Keitherley2400StreamInterface(StreamAdapter): + + # Commands that we expect via serial during normal operation + serial_commands = { + Cmd("get_values", "^:READ?(?:\s.*)?$"), + } + + # Private control commands that can be used as an alternative to the lewis backdoor + control_commands = { + } + + commands = set.union(serial_commands, control_commands) + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def get_values(self): + """ Get the current, voltage and resistance readings + + Returns: + string : A string of 3 doubles: voltage, current, resistance. In that order + """ + return " ".join([ + self._device.get_voltage(as_string=True), + self._device.get_current(as_string=True), + self._device.get_resistance(as_string=True) + ]) \ No newline at end of file diff --git a/lewis_emulators/keitherley_2400/utilities.py b/lewis_emulators/keitherley_2400/utilities.py new file mode 100644 index 00000000..d3ddc963 --- /dev/null +++ b/lewis_emulators/keitherley_2400/utilities.py @@ -0,0 +1,2 @@ +def format(f, as_string): + return "{0:.3f}".format(f) if as_string else f \ No newline at end of file From e67a71801874b05aa2c34c78dd242982ff61c28d Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 14:41:16 +0000 Subject: [PATCH 0088/1466] Add some missing bits to emulator --- lewis_emulators/keitherley_2400/device.py | 19 +++++++++++++++---- .../interfaces/stream_interface.py | 2 +- lewis_emulators/keitherley_2400/states.py | 14 ++++++++++++++ lewis_emulators/keitherley_2400/utilities.py | 4 ++-- 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 lewis_emulators/keitherley_2400/states.py diff --git a/lewis_emulators/keitherley_2400/device.py b/lewis_emulators/keitherley_2400/device.py index dcc44faa..2a3612cd 100644 --- a/lewis_emulators/keitherley_2400/device.py +++ b/lewis_emulators/keitherley_2400/device.py @@ -1,5 +1,6 @@ -from utilities import format +from utilities import format_value from collections import OrderedDict +from states import DefaultInitState, DefaultRunningState from lewis.devices import StateMachineDevice @@ -26,10 +27,20 @@ def _get_transition_handlers(self): ]) def get_voltage(self, as_string=False): - return format(self._voltage, as_string) + return format_value(self._voltage, as_string) def get_current(self, as_string=False): - return format(self._current, as_string) + return format_value(self._current, as_string) def get_resistance(self, as_string=False): - return format(self._resistance, as_string) + return format_value(self._resistance, as_string) + + def set_current(self, value): + self._current = value + + def set_voltage(self, value): + self._voltage = value + + def set_resistance(self, value): + self._resistance = value + diff --git a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py index 5bc9c97a..19997b37 100644 --- a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py @@ -27,4 +27,4 @@ def get_values(self): self._device.get_voltage(as_string=True), self._device.get_current(as_string=True), self._device.get_resistance(as_string=True) - ]) \ No newline at end of file + ]) diff --git a/lewis_emulators/keitherley_2400/states.py b/lewis_emulators/keitherley_2400/states.py new file mode 100644 index 00000000..8f7ecda4 --- /dev/null +++ b/lewis_emulators/keitherley_2400/states.py @@ -0,0 +1,14 @@ +from lewis.core.statemachine import State +from random import random + + +class DefaultInitState(State): + pass + + +class DefaultRunningState(State): + def in_state(self, dt): + max_out = 100.0 + self._context.set_current(random*max_out) + self._context.set_voltage(random*max_out) + self._context.set_resistance(random*max_out) \ No newline at end of file diff --git a/lewis_emulators/keitherley_2400/utilities.py b/lewis_emulators/keitherley_2400/utilities.py index d3ddc963..e4fc1d34 100644 --- a/lewis_emulators/keitherley_2400/utilities.py +++ b/lewis_emulators/keitherley_2400/utilities.py @@ -1,2 +1,2 @@ -def format(f, as_string): - return "{0:.3f}".format(f) if as_string else f \ No newline at end of file +def format_value(f, as_string): + return "{0:.3f}".format(f) if as_string else f From eb0009ee66b0c6094167be2dae6c4f2063a04cfc Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 14:41:36 +0000 Subject: [PATCH 0089/1466] 0 VIR at startup --- lewis_emulators/keitherley_2400/device.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/keitherley_2400/device.py b/lewis_emulators/keitherley_2400/device.py index 2a3612cd..781c4d83 100644 --- a/lewis_emulators/keitherley_2400/device.py +++ b/lewis_emulators/keitherley_2400/device.py @@ -8,9 +8,9 @@ class SimulatedVolumetricRig(StateMachineDevice): def _initialize_data(self): self.serial_command_mode = True - self._current = 1.111 - self._voltage = 2.222 - self._resistance = 3.333 + self._current = 0.0 + self._voltage = 0.0 + self._resistance = 0.0 def _get_state_handlers(self): return { From 7036b7da5bcf1196d1aee47073174d32f492d3b1 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 14:43:49 +0000 Subject: [PATCH 0090/1466] Class named incorrectly --- lewis_emulators/keitherley_2400/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/keitherley_2400/device.py b/lewis_emulators/keitherley_2400/device.py index 781c4d83..d6054730 100644 --- a/lewis_emulators/keitherley_2400/device.py +++ b/lewis_emulators/keitherley_2400/device.py @@ -4,7 +4,7 @@ from lewis.devices import StateMachineDevice -class SimulatedVolumetricRig(StateMachineDevice): +class SimulatedKeitherley2400(StateMachineDevice): def _initialize_data(self): self.serial_command_mode = True From cb7ebe1d16692e263b9d5666ba1ebe349cb3152f Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 14:44:49 +0000 Subject: [PATCH 0091/1466] Handle unexpected errors by printing to console --- .../keitherley_2400/interfaces/stream_interface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py index 19997b37..77d3eaba 100644 --- a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py @@ -28,3 +28,7 @@ def get_values(self): self._device.get_current(as_string=True), self._device.get_resistance(as_string=True) ]) + + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) \ No newline at end of file From b9e482f7f47e30b239b6ede9bd0e1de0bc6cc110 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 14:52:20 +0000 Subject: [PATCH 0092/1466] Make normal behaviour more interesting --- .../interfaces/stream_interface.py | 2 +- lewis_emulators/keitherley_2400/states.py | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py index 77d3eaba..e4be7e73 100644 --- a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keitherley_2400/interfaces/stream_interface.py @@ -5,7 +5,7 @@ class Keitherley2400StreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation serial_commands = { - Cmd("get_values", "^:READ?(?:\s.*)?$"), + Cmd("get_values", "^:READ\?(?:\s.*)?$"), } # Private control commands that can be used as an alternative to the lewis backdoor diff --git a/lewis_emulators/keitherley_2400/states.py b/lewis_emulators/keitherley_2400/states.py index 8f7ecda4..7b023771 100644 --- a/lewis_emulators/keitherley_2400/states.py +++ b/lewis_emulators/keitherley_2400/states.py @@ -1,5 +1,5 @@ from lewis.core.statemachine import State -from random import random +from random import uniform class DefaultInitState(State): @@ -8,7 +8,12 @@ class DefaultInitState(State): class DefaultRunningState(State): def in_state(self, dt): - max_out = 100.0 - self._context.set_current(random*max_out) - self._context.set_voltage(random*max_out) - self._context.set_resistance(random*max_out) \ No newline at end of file + def get_next_value(get_method): + return abs(get_method() + uniform(-1,1)*dt) + getters_and_setters = [ + (self._context.set_current,self._context.get_current), + (self._context.set_voltage,self._context.get_voltage), + (self._context.set_resistance,self._context.get_resistance), + ] + for gs in getters_and_setters: + gs[0](get_next_value(gs[1])) From 2878701832ac516c034b449bad1a2423dbd81723 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 16:59:17 +0000 Subject: [PATCH 0093/1466] Correct spelling of Keithley --- lewis_emulators/keitherley_2400/__init__.py | 8 -------- lewis_emulators/keitherley_2400/interfaces/__init__.py | 3 --- lewis_emulators/keithley_2400/__init__.py | 8 ++++++++ .../{keitherley_2400 => keithley_2400}/device.py | 2 +- lewis_emulators/keithley_2400/interfaces/__init__.py | 3 +++ .../interfaces/stream_interface.py | 2 +- .../{keitherley_2400 => keithley_2400}/states.py | 0 .../{keitherley_2400 => keithley_2400}/utilities.py | 0 8 files changed, 13 insertions(+), 13 deletions(-) delete mode 100644 lewis_emulators/keitherley_2400/__init__.py delete mode 100644 lewis_emulators/keitherley_2400/interfaces/__init__.py create mode 100644 lewis_emulators/keithley_2400/__init__.py rename lewis_emulators/{keitherley_2400 => keithley_2400}/device.py (95%) create mode 100644 lewis_emulators/keithley_2400/interfaces/__init__.py rename lewis_emulators/{keitherley_2400 => keithley_2400}/interfaces/stream_interface.py (94%) rename lewis_emulators/{keitherley_2400 => keithley_2400}/states.py (100%) rename lewis_emulators/{keitherley_2400 => keithley_2400}/utilities.py (100%) diff --git a/lewis_emulators/keitherley_2400/__init__.py b/lewis_emulators/keitherley_2400/__init__.py deleted file mode 100644 index 6a502118..00000000 --- a/lewis_emulators/keitherley_2400/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -from .device import SimulatedKeitherley2400 - -__all__ = ['SimulatedKeitherley2400'] - - - - - diff --git a/lewis_emulators/keitherley_2400/interfaces/__init__.py b/lewis_emulators/keitherley_2400/interfaces/__init__.py deleted file mode 100644 index fe861ef8..00000000 --- a/lewis_emulators/keitherley_2400/interfaces/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .stream_interface import Keitherley2400StreamInterface - -__all__ = ['Keitherley2400StreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/keithley_2400/__init__.py b/lewis_emulators/keithley_2400/__init__.py new file mode 100644 index 00000000..684ff8c2 --- /dev/null +++ b/lewis_emulators/keithley_2400/__init__.py @@ -0,0 +1,8 @@ +from .device import SimulatedKeithley2400 + +__all__ = ['SimulatedKeithley2400'] + + + + + diff --git a/lewis_emulators/keitherley_2400/device.py b/lewis_emulators/keithley_2400/device.py similarity index 95% rename from lewis_emulators/keitherley_2400/device.py rename to lewis_emulators/keithley_2400/device.py index d6054730..633fe2f5 100644 --- a/lewis_emulators/keitherley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -4,7 +4,7 @@ from lewis.devices import StateMachineDevice -class SimulatedKeitherley2400(StateMachineDevice): +class SimulatedKeithley2400(StateMachineDevice): def _initialize_data(self): self.serial_command_mode = True diff --git a/lewis_emulators/keithley_2400/interfaces/__init__.py b/lewis_emulators/keithley_2400/interfaces/__init__.py new file mode 100644 index 00000000..a38f0cc6 --- /dev/null +++ b/lewis_emulators/keithley_2400/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Keithley2400StreamInterface + +__all__ = ['Keithley2400StreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py similarity index 94% rename from lewis_emulators/keitherley_2400/interfaces/stream_interface.py rename to lewis_emulators/keithley_2400/interfaces/stream_interface.py index e4be7e73..7b3c368b 100644 --- a/lewis_emulators/keitherley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -1,7 +1,7 @@ from lewis.adapters.stream import StreamAdapter, Cmd -class Keitherley2400StreamInterface(StreamAdapter): +class Keithley2400StreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation serial_commands = { diff --git a/lewis_emulators/keitherley_2400/states.py b/lewis_emulators/keithley_2400/states.py similarity index 100% rename from lewis_emulators/keitherley_2400/states.py rename to lewis_emulators/keithley_2400/states.py diff --git a/lewis_emulators/keitherley_2400/utilities.py b/lewis_emulators/keithley_2400/utilities.py similarity index 100% rename from lewis_emulators/keitherley_2400/utilities.py rename to lewis_emulators/keithley_2400/utilities.py From 5c64910bfc9ab84182505664b2bcacec3937143b Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 10 Jan 2017 17:12:56 +0000 Subject: [PATCH 0094/1466] VIR should be CSV --- lewis_emulators/keithley_2400/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 7b3c368b..f4e82bdd 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -23,7 +23,7 @@ def get_values(self): Returns: string : A string of 3 doubles: voltage, current, resistance. In that order """ - return " ".join([ + return ", ".join([ self._device.get_voltage(as_string=True), self._device.get_current(as_string=True), self._device.get_resistance(as_string=True) From cbfb19f6fcf594db9cecde5233fcad9eaf711125 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 13 Jan 2017 09:14:57 +0000 Subject: [PATCH 0095/1466] Add reset command --- lewis_emulators/keithley_2400/device.py | 12 +++++++++--- .../keithley_2400/interfaces/stream_interface.py | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 633fe2f5..ec6a6425 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -6,11 +6,13 @@ class SimulatedKeithley2400(StateMachineDevice): + INITIAL_VALUE = 10.0 + def _initialize_data(self): self.serial_command_mode = True - self._current = 0.0 - self._voltage = 0.0 - self._resistance = 0.0 + self._current = SimulatedKeithley2400.INITIAL_VALUE + self._voltage = SimulatedKeithley2400.INITIAL_VALUE + self._resistance = SimulatedKeithley2400.INITIAL_VALUE def _get_state_handlers(self): return { @@ -44,3 +46,7 @@ def set_voltage(self, value): def set_resistance(self, value): self._resistance = value + def reset(self): + self._resistance = SimulatedKeithley2400.INITIAL_VALUE + self._current = SimulatedKeithley2400.INITIAL_VALUE + self._voltage = SimulatedKeithley2400.INITIAL_VALUE diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index f4e82bdd..146fa64f 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -5,7 +5,8 @@ class Keithley2400StreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation serial_commands = { - Cmd("get_values", "^:READ\?(?:\s.*)?$"), + Cmd("get_values", "^:READ\?$"), + Cmd("reset", "^\*RST$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -29,6 +30,9 @@ def get_values(self): self._device.get_resistance(as_string=True) ]) + def reset(self): + """ Resets the device """ + self._device.reset() def handle_error(self, request, error): - print "An error occurred at request " + repr(request) + ": " + repr(error) \ No newline at end of file + print "An error occurred at request " + repr(request) + ": " + repr(error) From 0291f4b6d86a5525f60e91b466b4915ca912b050 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 13 Jan 2017 10:58:27 +0000 Subject: [PATCH 0096/1466] Add output mode option --- lewis_emulators/keithley_2400/device.py | 21 ++++++++++++++++--- .../interfaces/stream_interface.py | 11 ++++++++++ lewis_emulators/keithley_2400/states.py | 11 +--------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index ec6a6425..7aaee638 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -1,4 +1,5 @@ from utilities import format_value +from random import uniform from collections import OrderedDict from states import DefaultInitState, DefaultRunningState from lewis.devices import StateMachineDevice @@ -13,6 +14,7 @@ def _initialize_data(self): self._current = SimulatedKeithley2400.INITIAL_VALUE self._voltage = SimulatedKeithley2400.INITIAL_VALUE self._resistance = SimulatedKeithley2400.INITIAL_VALUE + self._output_mode_on = True def _get_state_handlers(self): return { @@ -27,15 +29,18 @@ def _get_transition_handlers(self): return OrderedDict([ (('init', 'running'), lambda: self.serial_command_mode), ]) + + def _get_output(self, value, as_string): + return format_value(value if self._output_mode_on else 0.0, as_string) def get_voltage(self, as_string=False): - return format_value(self._voltage, as_string) + return self._get_output(self._voltage, as_string) def get_current(self, as_string=False): - return format_value(self._current, as_string) + return self._get_output(self._current, as_string) def get_resistance(self, as_string=False): - return format_value(self._resistance, as_string) + return self._get_output(self._resistance, as_string) def set_current(self, value): self._current = value @@ -50,3 +55,13 @@ def reset(self): self._resistance = SimulatedKeithley2400.INITIAL_VALUE self._current = SimulatedKeithley2400.INITIAL_VALUE self._voltage = SimulatedKeithley2400.INITIAL_VALUE + + def set_output_mode(self, is_on): + self._output_mode_on = is_on + + def update(self, dt): + def update_value(value): + return abs(value + uniform(-1,1)*dt) + self._current = update_value(self._current) + self._voltage = update_value(self._voltage) + self._resistance = update_value(self._resistance) \ No newline at end of file diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 146fa64f..1c07b3e5 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -7,6 +7,7 @@ class Keithley2400StreamInterface(StreamAdapter): serial_commands = { Cmd("get_values", "^:READ\?$"), Cmd("reset", "^\*RST$"), + Cmd("set_output_mode", "^\:OUTP\s(ON|OFF)$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -33,6 +34,16 @@ def get_values(self): def reset(self): """ Resets the device """ self._device.reset() + return "" + + def set_output_mode(self, new_mode): + if new_mode == "ON": + return self._device.set_output_mode(is_on=True) + elif new_mode == "OFF": + return self._device.set_output_mode(is_on=False) + else: + raise Exception("Invalid output mode received: " + str(new_mode)) def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) + diff --git a/lewis_emulators/keithley_2400/states.py b/lewis_emulators/keithley_2400/states.py index 7b023771..7b9c6af7 100644 --- a/lewis_emulators/keithley_2400/states.py +++ b/lewis_emulators/keithley_2400/states.py @@ -1,5 +1,4 @@ from lewis.core.statemachine import State -from random import uniform class DefaultInitState(State): @@ -8,12 +7,4 @@ class DefaultInitState(State): class DefaultRunningState(State): def in_state(self, dt): - def get_next_value(get_method): - return abs(get_method() + uniform(-1,1)*dt) - getters_and_setters = [ - (self._context.set_current,self._context.get_current), - (self._context.set_voltage,self._context.get_voltage), - (self._context.set_resistance,self._context.get_resistance), - ] - for gs in getters_and_setters: - gs[0](get_next_value(gs[1])) + self._context.update(dt) From 4bac87c76abb52cc26307c63c6f03f98ede84071 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 13 Jan 2017 11:32:21 +0000 Subject: [PATCH 0097/1466] Add mode RBV --- lewis_emulators/keithley_2400/device.py | 5 ++++- .../keithley_2400/interfaces/stream_interface.py | 14 +++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 7aaee638..6840a09c 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -56,9 +56,12 @@ def reset(self): self._current = SimulatedKeithley2400.INITIAL_VALUE self._voltage = SimulatedKeithley2400.INITIAL_VALUE - def set_output_mode(self, is_on): + def set_output_on(self, is_on): self._output_mode_on = is_on + def output_is_on(self): + return self._output_mode_on + def update(self, dt): def update_value(value): return abs(value + uniform(-1,1)*dt) diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 1c07b3e5..db054100 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -8,6 +8,7 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_values", "^:READ\?$"), Cmd("reset", "^\*RST$"), Cmd("set_output_mode", "^\:OUTP\s(ON|OFF)$"), + Cmd("get_output_mode", "^\:OUTP\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -34,15 +35,22 @@ def get_values(self): def reset(self): """ Resets the device """ self._device.reset() - return "" + return "*RST" def set_output_mode(self, new_mode): if new_mode == "ON": - return self._device.set_output_mode(is_on=True) + self._device.set_output_on(True) elif new_mode == "OFF": - return self._device.set_output_mode(is_on=False) + self._device.set_output_on(False) else: raise Exception("Invalid output mode received: " + str(new_mode)) + return ":OUTP " + str(new_mode) + + def get_output_mode(self): + if self._device.output_is_on(): + return "ON" + else: + return "OFF" def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From 3d7ac84e90f21b4ed1adf60b5b346e9a0212a236 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 13 Jan 2017 14:13:00 +0000 Subject: [PATCH 0098/1466] Add offset compensation --- lewis_emulators/keithley_2400/device.py | 11 ++++++- .../interfaces/stream_interface.py | 29 ++++++++++++++----- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 6840a09c..55c681d4 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -14,6 +14,7 @@ def _initialize_data(self): self._current = SimulatedKeithley2400.INITIAL_VALUE self._voltage = SimulatedKeithley2400.INITIAL_VALUE self._resistance = SimulatedKeithley2400.INITIAL_VALUE + self._offset_compensation_on = False self._output_mode_on = True def _get_state_handlers(self): @@ -31,7 +32,9 @@ def _get_transition_handlers(self): ]) def _get_output(self, value, as_string): - return format_value(value if self._output_mode_on else 0.0, as_string) + return format_value((value - SimulatedKeithley2400.INITIAL_VALUE if self._offset_compensation_on else value) + if self._output_mode_on else 0.0, + as_string) def get_voltage(self, as_string=False): return self._get_output(self._voltage, as_string) @@ -62,6 +65,12 @@ def set_output_on(self, is_on): def output_is_on(self): return self._output_mode_on + def set_offset_compensation_on(self, is_on): + self._offset_compensation_on = is_on + + def offset_compensation_is_on(self): + return self._offset_compensation_on + def update(self, dt): def update_value(value): return abs(value + uniform(-1,1)*dt) diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index db054100..144ab1dc 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -9,6 +9,8 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("reset", "^\*RST$"), Cmd("set_output_mode", "^\:OUTP\s(ON|OFF)$"), Cmd("get_output_mode", "^\:OUTP\?$"), + Cmd("set_offset_compensation", "^\:SENS:RES:OCOM\s(ON|OFF)$"), + Cmd("get_offset_compensation", "^\:SENS:RES:OCOM\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -37,21 +39,34 @@ def reset(self): self._device.reset() return "*RST" - def set_output_mode(self, new_mode): + def _set_on_off(self, set_method, type_string, command, new_mode): if new_mode == "ON": - self._device.set_output_on(True) + set_method(True) elif new_mode == "OFF": - self._device.set_output_on(False) + set_method(False) else: - raise Exception("Invalid output mode received: " + str(new_mode)) - return ":OUTP " + str(new_mode) + raise Exception("Invalid " + type_string + " " + str(new_mode)) + return command + " " + str(new_mode) - def get_output_mode(self): - if self._device.output_is_on(): + def _get_on_off(self, get_method): + if get_method(): return "ON" else: return "OFF" + def set_output_mode(self, new_mode): + return self._set_on_off(self._device.set_output_on, "output mode", "OUTP:", new_mode) + + def get_output_mode(self): + return self._get_on_off(self._device.output_is_on) + + def set_offset_compensation(self, new_mode): + return self._set_on_off(self._device.set_offset_compensation_on, "offset compensation mode", + ":SENS:RES:OCOM", new_mode) + + def get_offset_compensation(self): + return self._get_on_off(self._device.offset_compensation_is_on) + def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From 4dbee9b23172fa08e438b265411f9ac126e2e9ca Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 13 Jan 2017 15:53:40 +0000 Subject: [PATCH 0099/1466] Add auto sensing mode --- lewis_emulators/keithley_2400/device.py | 16 ++++++- .../interfaces/stream_interface.py | 46 +++++++++++++------ 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 55c681d4..516e8c96 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -16,6 +16,8 @@ def _initialize_data(self): self._resistance = SimulatedKeithley2400.INITIAL_VALUE self._offset_compensation_on = False self._output_mode_on = True + self._resistance_mode_auto = True + self._remote_sensing_on = False def _get_state_handlers(self): return { @@ -76,4 +78,16 @@ def update_value(value): return abs(value + uniform(-1,1)*dt) self._current = update_value(self._current) self._voltage = update_value(self._voltage) - self._resistance = update_value(self._resistance) \ No newline at end of file + self._resistance = update_value(self._resistance) + + def resistance_mode_is_auto(self): + return self._resistance_mode_auto + + def set_resistance_mode_auto(self, is_auto): + self._resistance_mode_auto = is_auto + + def remote_sensing_is_on(self): + return self._remote_sensing_on + + def set_remote_sensing_on(self, is_on): + self._remote_sensing_on = is_on diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 144ab1dc..31dfb288 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -11,6 +11,10 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_output_mode", "^\:OUTP\?$"), Cmd("set_offset_compensation", "^\:SENS:RES:OCOM\s(ON|OFF)$"), Cmd("get_offset_compensation", "^\:SENS:RES:OCOM\?$"), + Cmd("set_resistance_mode", "^\:SENS:RES:MODE\s(AUTO|MANUAL)$"), + Cmd("get_resistance_mode", "^\:SENS:RES:MODE\?$"), + Cmd("set_remote_sensing_mode", "^\:SYST:RSEN\s(ON|OFF)$"), + Cmd("get_remote_sensing_mode", "^\:SYST:RSEN\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -39,34 +43,48 @@ def reset(self): self._device.reset() return "*RST" - def _set_on_off(self, set_method, type_string, command, new_mode): - if new_mode == "ON": - set_method(True) - elif new_mode == "OFF": - set_method(False) - else: - raise Exception("Invalid " + type_string + " " + str(new_mode)) + def _set_mode(self, set_method, command, new_mode, mode_lookup): + set_method(mode_lookup[new_mode]) return command + " " + str(new_mode) + def _set_on_off(self, set_method, command, new_mode): + return self._set_mode(set_method, command, new_mode, {"ON": True, "OFF": False}) + + def _get_option(self, get_method, option_lookup): + return option_lookup[get_method()] + def _get_on_off(self, get_method): - if get_method(): - return "ON" - else: - return "OFF" + return self._get_option(get_method, {True: "ON", False: "OFF"}) + + def _get_auto_manual(self, get_method): + return self._get_option(get_method, {True: "AUTO", False: "MANUAL"}) def set_output_mode(self, new_mode): - return self._set_on_off(self._device.set_output_on, "output mode", "OUTP:", new_mode) + return self._set_on_off(self._device.set_output_on, "OUTP:", new_mode) def get_output_mode(self): return self._get_on_off(self._device.output_is_on) def set_offset_compensation(self, new_mode): - return self._set_on_off(self._device.set_offset_compensation_on, "offset compensation mode", - ":SENS:RES:OCOM", new_mode) + return self._set_on_off(self._device.set_offset_compensation_on, ":SENS:RES:OCOM", new_mode) + def get_offset_compensation(self): return self._get_on_off(self._device.offset_compensation_is_on) + def get_resistance_mode(self): + return self._get_auto_manual(self._device.resistance_mode_is_auto) + + def set_resistance_mode(self, new_mode): + return self._set_mode(self._device.set_resistance_mode_auto, ":SENS:RES:MODE", new_mode, + {"AUTO": True, "MANUAL": False}) + + def get_remote_sensing_mode(self): + return self._get_on_off(self._device.remote_sensing_is_on) + + def set_remote_sensing_mode(self, new_mode): + return self._set_on_off(self._device.set_remote_sensing_on, ":SYST:RSEN", new_mode) + def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From fb432ccdf457776e016af9c783c5b342ffa19ad1 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 16 Jan 2017 08:58:57 +0000 Subject: [PATCH 0100/1466] Add auto range selection --- lewis_emulators/keithley_2400/device.py | 7 +++++++ .../keithley_2400/interfaces/stream_interface.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 516e8c96..016c361e 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -18,6 +18,7 @@ def _initialize_data(self): self._output_mode_on = True self._resistance_mode_auto = True self._remote_sensing_on = False + self._auto_resistance_range = True def _get_state_handlers(self): return { @@ -91,3 +92,9 @@ def remote_sensing_is_on(self): def set_remote_sensing_on(self, is_on): self._remote_sensing_on = is_on + + def auto_resistance_range_is_on(self): + return self._auto_resistance_range + + def set_auto_resistance_on(self, is_on): + self._auto_resistance_range = is_on diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 31dfb288..db4bf21e 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -15,6 +15,8 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_resistance_mode", "^\:SENS:RES:MODE\?$"), Cmd("set_remote_sensing_mode", "^\:SYST:RSEN\s(ON|OFF)$"), Cmd("get_remote_sensing_mode", "^\:SYST:RSEN\?$"), + Cmd("set_auto_resistance_range", "^\:SENS:RES:RANG:AUTO\s(AUTO|MANUAL)$"), + Cmd("get_auto_resistance_range_on", "^\:SENS:RES:RANG:AUTO\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -85,6 +87,12 @@ def get_remote_sensing_mode(self): def set_remote_sensing_mode(self, new_mode): return self._set_on_off(self._device.set_remote_sensing_on, ":SYST:RSEN", new_mode) + def get_auto_resistance_range_on(self): + return self._get_option(self._device.auto_resistance_range_is_on, {True: "AUTO", False: "MANUAL"}) + + def set_auto_resistance_range(self, new_mode): + return self._set_mode(self._device.set_auto_resistance_on, ":SENS:RES:RANG:AUTO", new_mode, {"AUTO": True, "MANUAL": False}) + def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From c98bc1c61469f41e32e6112944917fa676234cc0 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Mon, 16 Jan 2017 11:31:03 +0000 Subject: [PATCH 0101/1466] Small format change and typo --- lewis_emulators/volumetric_rig/gas.py | 2 +- lewis_emulators/volumetric_rig/interfaces/stream_interface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/volumetric_rig/gas.py b/lewis_emulators/volumetric_rig/gas.py index 5c88663c..0434479a 100644 --- a/lewis_emulators/volumetric_rig/gas.py +++ b/lewis_emulators/volumetric_rig/gas.py @@ -3,7 +3,7 @@ class Gas(object): - def __init__(self,index,name): + def __init__(self, index, name): assert type(index) is IntType and type(name) is StringType self._index = index self._name = name diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 3e73519d..2d5c45ed 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -7,7 +7,7 @@ class VolumetricRigStreamInterface(StreamAdapter): # The rig typically splits a command by whitespace and then uses the arguments it needs and then ignores the rest - # so "IDN" will respond as "IDN BLAH BLAH BLAH" and "BCS 01" would be the same has "BCS 01 02 03". + # so "IDN" will respond as "IDN BLAH BLAH BLAH" and "BCS 01" would be the same as "BCS 01 02 03". # Some commands that take input will respond with default (often invalid) parameters if not present. For example # "BCS" is the same as "BCS 00" and also "BCS AA". serial_commands = { From 74f1c6cb956f65f42599ee2fbf8ab68dc98a1381 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 16 Jan 2017 14:10:09 +0000 Subject: [PATCH 0102/1466] Set resistance range --- lewis_emulators/keithley_2400/device.py | 7 +++++++ .../keithley_2400/interfaces/stream_interface.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 016c361e..2dfa6078 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -19,6 +19,7 @@ def _initialize_data(self): self._resistance_mode_auto = True self._remote_sensing_on = False self._auto_resistance_range = True + self._resistance_range = 200000000 def _get_state_handlers(self): return { @@ -98,3 +99,9 @@ def auto_resistance_range_is_on(self): def set_auto_resistance_on(self, is_on): self._auto_resistance_range = is_on + + def get_resistance_range(self): + return self._resistance_range + + def set_resistance_range(self, value): + self._resistance_range = value diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index db4bf21e..9ea53fb9 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -17,6 +17,8 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_remote_sensing_mode", "^\:SYST:RSEN\?$"), Cmd("set_auto_resistance_range", "^\:SENS:RES:RANG:AUTO\s(AUTO|MANUAL)$"), Cmd("get_auto_resistance_range_on", "^\:SENS:RES:RANG:AUTO\?$"), + Cmd("set_resistance_range", "^\:SENS:RES:RANG\s([2][0]*)$"), + Cmd("get_resistance_range", "^\:SENS:RES:RANG\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor @@ -93,6 +95,12 @@ def get_auto_resistance_range_on(self): def set_auto_resistance_range(self, new_mode): return self._set_mode(self._device.set_auto_resistance_on, ":SENS:RES:RANG:AUTO", new_mode, {"AUTO": True, "MANUAL": False}) + def get_resistance_range(self): + return self._device.get_resistance_range() + + def set_resistance_range(self, value): + return self._device.set_resistance_range(int(value)) + def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From ebb665c6f1541be8a6869bb33afcc2d17d184633 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 16 Jan 2017 17:15:36 +0000 Subject: [PATCH 0103/1466] Sort single command --- lewis_emulators/neocera_ltc21/__init__.py | 3 +++ lewis_emulators/neocera_ltc21/device.py | 27 +++++++++++++++++++ .../neocera_ltc21/interfaces/__init__.py | 3 +++ .../interfaces/stream_interface.py | 18 +++++++++++++ lewis_emulators/neocera_ltc21/states.py | 12 +++++++++ 5 files changed, 63 insertions(+) create mode 100644 lewis_emulators/neocera_ltc21/__init__.py create mode 100644 lewis_emulators/neocera_ltc21/device.py create mode 100644 lewis_emulators/neocera_ltc21/interfaces/__init__.py create mode 100644 lewis_emulators/neocera_ltc21/interfaces/stream_interface.py create mode 100644 lewis_emulators/neocera_ltc21/states.py diff --git a/lewis_emulators/neocera_ltc21/__init__.py b/lewis_emulators/neocera_ltc21/__init__.py new file mode 100644 index 00000000..3856b097 --- /dev/null +++ b/lewis_emulators/neocera_ltc21/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedNeocera + +__all__ = ['SimulatedNeocera'] diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py new file mode 100644 index 00000000..8da7e371 --- /dev/null +++ b/lewis_emulators/neocera_ltc21/device.py @@ -0,0 +1,27 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice + +from lewis.core import approaches + +from lewis_emulators.neocera_ltc21.states import MonitorState + + +class SimulatedNeocera(StateMachineDevice): + + + def _initialize_data(self): + pass + + def _get_state_handlers(self): + return { + MonitorState.NAME: MonitorState() + } + + def _get_initial_state(self): + return MonitorState.NAME + + def _get_transition_handlers(self): + return OrderedDict([ + ((MonitorState.NAME, 'moving'), lambda: False), + ]) diff --git a/lewis_emulators/neocera_ltc21/interfaces/__init__.py b/lewis_emulators/neocera_ltc21/interfaces/__init__.py new file mode 100644 index 00000000..693c44e4 --- /dev/null +++ b/lewis_emulators/neocera_ltc21/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import NeoceraStreamInterface + +__all__ = ['NeoceraStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py new file mode 100644 index 00000000..ab94c8a9 --- /dev/null +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -0,0 +1,18 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + + +class NeoceraStreamInterface(StreamAdapter): + + commands = { + Cmd("get_state", r"[\r\n]*QISTATE\?[\r\n]*"), + } + + in_terminator = ";" + out_terminator = ";" + + def get_state(self): + return "this is my state" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py new file mode 100644 index 00000000..48f2bed4 --- /dev/null +++ b/lewis_emulators/neocera_ltc21/states.py @@ -0,0 +1,12 @@ +from lewis.core.statemachine import State + + +class OffState(State): + pass + + +class MonitorState(State): + NAME = __name__ + +class ControlState(State): + pass \ No newline at end of file From cffec6487aaf7ae108ae3c4e42db5ee4549b7219 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 11:07:26 +0000 Subject: [PATCH 0104/1466] Set manual resistance mode properties --- .../keithley_2400/control_modes.py | 42 +++++ lewis_emulators/keithley_2400/device.py | 168 ++++++++++++------ .../interfaces/stream_interface.py | 91 ++++++---- 3 files changed, 214 insertions(+), 87 deletions(-) create mode 100644 lewis_emulators/keithley_2400/control_modes.py diff --git a/lewis_emulators/keithley_2400/control_modes.py b/lewis_emulators/keithley_2400/control_modes.py new file mode 100644 index 00000000..545bea44 --- /dev/null +++ b/lewis_emulators/keithley_2400/control_modes.py @@ -0,0 +1,42 @@ +class Mode(object): + MODES = [] + + +class AutoMode(Mode): + AUTO = "AUTO" + MANUAL = "MAN" + MODES = [AUTO, MANUAL] + + +class ResistanceMode(AutoMode): + pass + + +class ResistanceRangeMode(AutoMode): + AUTO = "1" + MANUAL = "0" + MODES = [AUTO, MANUAL] + + +class SourceMode(Mode): + CURRENT = "CURR" + VOLTAGE = "VOLT" + MODES = [CURRENT, VOLTAGE] + + +class OnOffMode(Mode): + ON = "ON" + OFF = "OFF" + MODES = [ON, OFF] + + +class RemoteSensingMode(OnOffMode): + pass + + +class OffsetCompensationMode(OnOffMode): + pass + + +class OutputMode(OnOffMode): + pass \ No newline at end of file diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 2dfa6078..494dd23b 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -2,24 +2,37 @@ from random import uniform from collections import OrderedDict from states import DefaultInitState, DefaultRunningState +from control_modes import * from lewis.devices import StateMachineDevice class SimulatedKeithley2400(StateMachineDevice): - INITIAL_VALUE = 10.0 + INITIAL_CURRENT = 0.1 + INITIAL_CURRENT_COMPLIANCE = INITIAL_CURRENT + INITIAL_VOLTAGE = 10.0 + INITIAL_VOLTAGE_COMPLIANCE = INITIAL_VOLTAGE + MINIMUM_CURRENT = 1.0e-20 def _initialize_data(self): self.serial_command_mode = True - self._current = SimulatedKeithley2400.INITIAL_VALUE - self._voltage = SimulatedKeithley2400.INITIAL_VALUE - self._resistance = SimulatedKeithley2400.INITIAL_VALUE - self._offset_compensation_on = False - self._output_mode_on = True - self._resistance_mode_auto = True - self._remote_sensing_on = False - self._auto_resistance_range = True + + # Power properties + self._current = SimulatedKeithley2400.INITIAL_CURRENT + self._voltage = SimulatedKeithley2400.INITIAL_VOLTAGE + + # Modes + self._output_mode = OutputMode.ON + self._offset_compensation_mode = OffsetCompensationMode.OFF + self._resistance_mode = ResistanceMode.AUTO + self._remote_sensing_mode = RemoteSensingMode.OFF + self._resistance_range_mode = ResistanceRangeMode.AUTO + self._source_mode = SourceMode.CURRENT + + # Mode settings self._resistance_range = 200000000 + self._current_compliance = SimulatedKeithley2400.INITIAL_CURRENT_COMPLIANCE + self._voltage_compliance = SimulatedKeithley2400.INITIAL_VOLTAGE_COMPLIANCE def _get_state_handlers(self): return { @@ -35,73 +48,122 @@ def _get_transition_handlers(self): (('init', 'running'), lambda: self.serial_command_mode), ]) - def _get_output(self, value, as_string): - return format_value((value - SimulatedKeithley2400.INITIAL_VALUE if self._offset_compensation_on else value) - if self._output_mode_on else 0.0, - as_string) + def _resistance(self): + r = self._voltage/max(self._current, SimulatedKeithley2400.MINIMUM_CURRENT) + return min(r, self._resistance_range) if self._resistance_range_mode == ResistanceRangeMode.MANUAL else r + + def _format_power_output(self, value, as_string, offset=0.0): + """ + Some properties like output mode and offset compensation affect the output without affecting the underlying + model. Those adjustments are applied here. + """ + if self._output_mode == OutputMode.OFF: + output_value = 0.0 + elif self._offset_compensation_mode == OffsetCompensationMode.ON: + output_value = value - offset + else: + output_value = value + return format_value(output_value, as_string) + + def set_voltage(self, value): + self._voltage = value + + def set_current(self, value): + self._current = value def get_voltage(self, as_string=False): - return self._get_output(self._voltage, as_string) + return self._format_power_output(self._voltage, as_string, SimulatedKeithley2400.INITIAL_VOLTAGE) def get_current(self, as_string=False): - return self._get_output(self._current, as_string) + return self._format_power_output(self._current, as_string, SimulatedKeithley2400.INITIAL_CURRENT) def get_resistance(self, as_string=False): - return self._get_output(self._resistance, as_string) + return self._format_power_output(self._resistance(), as_string) - def set_current(self, value): - self._current = value + def update(self, dt): + def update_value(value): + return abs(value + uniform(-1,1)*dt) + new_current = max(update_value(self._current), SimulatedKeithley2400.MINIMUM_CURRENT) + new_voltage = update_value(self._voltage) - def set_voltage(self, value): - self._voltage = value - def set_resistance(self, value): - self._resistance = value + if self._resistance_mode == ResistanceMode.MANUAL: + if new_current < self._current_compliance or self._source_mode == SourceMode.VOLTAGE: + self._current = new_current + if new_voltage < self._voltage_compliance or self._source_mode == SourceMode.CURRENT: + self._voltage = new_voltage + elif self._resistance_mode == ResistanceMode.AUTO: + self._current = new_current + self._voltage = new_voltage def reset(self): - self._resistance = SimulatedKeithley2400.INITIAL_VALUE - self._current = SimulatedKeithley2400.INITIAL_VALUE - self._voltage = SimulatedKeithley2400.INITIAL_VALUE + self._voltage = SimulatedKeithley2400.INITIAL_POWER_VALUE + self._current = SimulatedKeithley2400.INITIAL_POWER_VALUE - def set_output_on(self, is_on): - self._output_mode_on = is_on + @staticmethod + def _check_mode(mode, mode_class): + if mode in mode_class.MODES: + return True + else: + print "Invalid mode, " + mode + ", received for: " + mode_class.__name__ + return False - def output_is_on(self): - return self._output_mode_on + def set_output_mode(self, mode): + if SimulatedKeithley2400._check_mode(mode, OutputMode): + self._output_mode = mode - def set_offset_compensation_on(self, is_on): - self._offset_compensation_on = is_on + def get_output_mode(self): + return self._output_mode - def offset_compensation_is_on(self): - return self._offset_compensation_on + def set_offset_compensation_mode(self, mode): + if SimulatedKeithley2400._check_mode(mode, OffsetCompensationMode): + self._offset_compensation_mode = mode - def update(self, dt): - def update_value(value): - return abs(value + uniform(-1,1)*dt) - self._current = update_value(self._current) - self._voltage = update_value(self._voltage) - self._resistance = update_value(self._resistance) + def get_offset_compensation_mode(self): + return self._offset_compensation_mode - def resistance_mode_is_auto(self): - return self._resistance_mode_auto + def set_resistance_mode(self, mode): + if SimulatedKeithley2400._check_mode(mode, ResistanceMode): + self._resistance_mode = mode - def set_resistance_mode_auto(self, is_auto): - self._resistance_mode_auto = is_auto + def get_resistance_mode(self): + return self._resistance_mode - def remote_sensing_is_on(self): - return self._remote_sensing_on + def set_remote_sensing_mode(self, mode): + if SimulatedKeithley2400._check_mode(mode, RemoteSensingMode): + self._remote_sensing_mode = mode - def set_remote_sensing_on(self, is_on): - self._remote_sensing_on = is_on + def get_remote_sensing_mode(self): + return self._remote_sensing_mode - def auto_resistance_range_is_on(self): - return self._auto_resistance_range + def set_resistance_range_mode(self, mode): + if SimulatedKeithley2400._check_mode(mode, ResistanceRangeMode): + self._resistance_range_mode = mode - def set_auto_resistance_on(self, is_on): - self._auto_resistance_range = is_on + def get_resistance_range_mode(self): + return self._resistance_range_mode - def get_resistance_range(self): - return self._resistance_range + def set_source_mode(self, mode): + if SimulatedKeithley2400._check_mode(mode, SourceMode): + self._source_mode = mode + + def get_source_mode(self): + return self._source_mode def set_resistance_range(self, value): self._resistance_range = value + + def get_resistance_range(self): + return self._resistance_range + + def set_current_compliance(self, value): + self._current_compliance = value + + def get_current_compliance(self): + return self._current_compliance + + def set_voltage_compliance(self, value): + self._voltage_compliance = value + + def get_voltage_compliance(self): + return self._voltage_compliance diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 9ea53fb9..3ca8684b 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -9,20 +9,28 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("reset", "^\*RST$"), Cmd("set_output_mode", "^\:OUTP\s(ON|OFF)$"), Cmd("get_output_mode", "^\:OUTP\?$"), - Cmd("set_offset_compensation", "^\:SENS:RES:OCOM\s(ON|OFF)$"), - Cmd("get_offset_compensation", "^\:SENS:RES:OCOM\?$"), - Cmd("set_resistance_mode", "^\:SENS:RES:MODE\s(AUTO|MANUAL)$"), + Cmd("set_offset_compensation_mode", "^\:SENS:RES:OCOM\s(ON|OFF)$"), + Cmd("get_offset_compensation_mode", "^\:SENS:RES:OCOM\?$"), + Cmd("set_resistance_mode", "^\:SENS:RES:MODE\s(AUTO|MAN)$"), Cmd("get_resistance_mode", "^\:SENS:RES:MODE\?$"), Cmd("set_remote_sensing_mode", "^\:SYST:RSEN\s(ON|OFF)$"), Cmd("get_remote_sensing_mode", "^\:SYST:RSEN\?$"), - Cmd("set_auto_resistance_range", "^\:SENS:RES:RANG:AUTO\s(AUTO|MANUAL)$"), - Cmd("get_auto_resistance_range_on", "^\:SENS:RES:RANG:AUTO\?$"), + Cmd("set_resistance_range_mode", "^\:SENS:RES:RANG:AUTO\s(0|1)$"), + Cmd("get_resistance_range_mode", "^\:SENS:RES:RANG:AUTO\?$"), Cmd("set_resistance_range", "^\:SENS:RES:RANG\s([2][0]*)$"), Cmd("get_resistance_range", "^\:SENS:RES:RANG\?$"), + Cmd("set_source_mode", "^\:SOUR:FUNC\s(CURR|VOLT)$"), + Cmd("get_source_mode", "^\:SOUR:FUNC\?$"), + Cmd("set_current_compliance", "^\:SENS:CURR\s([-+]?[0-9]*\.?[0-9]+)$"), + Cmd("get_current_compliance", "^\:SENS:CURR\?$"), + Cmd("set_voltage_compliance", "^\:SENS:VOLT\s([-+]?[0-9]*\.?[0-9]+)$"), + Cmd("get_voltage_compliance", "^\:SENS:VOLT\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor control_commands = { + Cmd("set_voltage", "^\:_CTRL:VOLT\s([-+]?[0-9]*\.?[0-9]+)$"), + Cmd("set_current", "^\:_CTRL:CURR\s([-+]?[0-9]*\.?[0-9]+)$"), } commands = set.union(serial_commands, control_commands) @@ -47,59 +55,74 @@ def reset(self): self._device.reset() return "*RST" - def _set_mode(self, set_method, command, new_mode, mode_lookup): - set_method(mode_lookup[new_mode]) - return command + " " + str(new_mode) + def set_current(self, value): + self._device.set_current(float(value)) + return "Current set to: " + str(value) - def _set_on_off(self, set_method, command, new_mode): - return self._set_mode(set_method, command, new_mode, {"ON": True, "OFF": False}) + def set_voltage(self, value): + self._device.set_voltage(float(value)) + return "Voltage set to: " + str(value) def _get_option(self, get_method, option_lookup): return option_lookup[get_method()] - def _get_on_off(self, get_method): - return self._get_option(get_method, {True: "ON", False: "OFF"}) - - def _get_auto_manual(self, get_method): - return self._get_option(get_method, {True: "AUTO", False: "MANUAL"}) + def _set_mode(self, set_method, mode, command): + set_method(mode) + return command + " " + mode def set_output_mode(self, new_mode): - return self._set_on_off(self._device.set_output_on, "OUTP:", new_mode) + return self._set_mode(self._device.set_output_mode, new_mode, "OUTP:") def get_output_mode(self): - return self._get_on_off(self._device.output_is_on) + return self._device.get_output_mode() - def set_offset_compensation(self, new_mode): - return self._set_on_off(self._device.set_offset_compensation_on, ":SENS:RES:OCOM", new_mode) + def set_offset_compensation_mode(self, new_mode): + return self._set_mode(self._device.set_offset_compensation_mode, new_mode, ":SENS:RES:OCOM") + def get_offset_compensation_mode(self): + return self._device.get_offset_compensation_mode() - def get_offset_compensation(self): - return self._get_on_off(self._device.offset_compensation_is_on) + def set_resistance_mode(self, new_mode): + return self._set_mode(self._device.set_resistance_mode, new_mode, ":SENS:RES:MODE") def get_resistance_mode(self): - return self._get_auto_manual(self._device.resistance_mode_is_auto) + return self._device.get_resistance_mode() - def set_resistance_mode(self, new_mode): - return self._set_mode(self._device.set_resistance_mode_auto, ":SENS:RES:MODE", new_mode, - {"AUTO": True, "MANUAL": False}) + def set_remote_sensing_mode(self, new_mode): + return self._set_mode(self._device.set_remote_sensing_mode, new_mode, ":SYST:RSEN") def get_remote_sensing_mode(self): - return self._get_on_off(self._device.remote_sensing_is_on) + return self._device.get_remote_sensing_mode() - def set_remote_sensing_mode(self, new_mode): - return self._set_on_off(self._device.set_remote_sensing_on, ":SYST:RSEN", new_mode) + def set_resistance_range_mode(self, new_mode): + return self._set_mode(self._device.set_resistance_range_mode, new_mode, ":SENS:RES:RANG:AUTO") - def get_auto_resistance_range_on(self): - return self._get_option(self._device.auto_resistance_range_is_on, {True: "AUTO", False: "MANUAL"}) + def get_resistance_range_mode(self): + return self._device.get_resistance_range_mode() - def set_auto_resistance_range(self, new_mode): - return self._set_mode(self._device.set_auto_resistance_on, ":SENS:RES:RANG:AUTO", new_mode, {"AUTO": True, "MANUAL": False}) + def set_resistance_range(self, value): + return self._device.set_resistance_range(int(value)) def get_resistance_range(self): return self._device.get_resistance_range() - def set_resistance_range(self, value): - return self._device.set_resistance_range(int(value)) + def set_source_mode(self, new_mode): + return self._set_mode(self._device.set_source_mode, new_mode, ":SOUR:FUNC") + + def get_source_mode(self): + return self._device.get_source_mode() + + def set_current_compliance(self, value): + return self._device.set_current_compliance(float(value)) + + def get_current_compliance(self): + return self._device.get_current_compliance() + + def set_voltage_compliance(self, value): + return self._device.set_voltage_compliance(float(value)) + + def get_voltage_compliance(self): + return self._device.get_voltage_compliance() def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From e0045155cf1de9e14d2f2049ff51b338b79bd143 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 11:49:09 +0000 Subject: [PATCH 0105/1466] Finishing touches --- .../keithley_2400/control_modes.py | 28 +++++++++++-------- lewis_emulators/keithley_2400/device.py | 10 +++++-- .../interfaces/stream_interface.py | 12 +++++--- lewis_emulators/keithley_2400/utilities.py | 1 + 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/keithley_2400/control_modes.py b/lewis_emulators/keithley_2400/control_modes.py index 545bea44..e8ba317b 100644 --- a/lewis_emulators/keithley_2400/control_modes.py +++ b/lewis_emulators/keithley_2400/control_modes.py @@ -1,18 +1,14 @@ -class Mode(object): - MODES = [] +""" +Effectively enumerators for each of the various modes supported by the device. Allows checking for invalid mode +strings and helps avoid typos. +""" -class AutoMode(Mode): - AUTO = "AUTO" - MANUAL = "MAN" - MODES = [AUTO, MANUAL] - - -class ResistanceMode(AutoMode): - pass +class Mode(object): + MODES = [] -class ResistanceRangeMode(AutoMode): +class ResistanceRangeMode(Mode): AUTO = "1" MANUAL = "0" MODES = [AUTO, MANUAL] @@ -24,6 +20,16 @@ class SourceMode(Mode): MODES = [CURRENT, VOLTAGE] +class AutoMode(Mode): + AUTO = "AUTO" + MANUAL = "MAN" + MODES = [AUTO, MANUAL] + + +class ResistanceMode(AutoMode): + pass + + class OnOffMode(Mode): ON = "ON" OFF = "OFF" diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 494dd23b..4cf1a961 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -15,6 +15,7 @@ class SimulatedKeithley2400(StateMachineDevice): MINIMUM_CURRENT = 1.0e-20 def _initialize_data(self): + """ Initialize all of the device's attributes """ self.serial_command_mode = True # Power properties @@ -49,6 +50,7 @@ def _get_transition_handlers(self): ]) def _resistance(self): + # The device only tracks current and voltage. Resistance is calculated as a dependent variable r = self._voltage/max(self._current, SimulatedKeithley2400.MINIMUM_CURRENT) return min(r, self._resistance_range) if self._resistance_range_mode == ResistanceRangeMode.MANUAL else r @@ -81,13 +83,14 @@ def get_resistance(self, as_string=False): return self._format_power_output(self._resistance(), as_string) def update(self, dt): + """ Update the current and voltage values based on the current mode and time elapsed """ def update_value(value): return abs(value + uniform(-1,1)*dt) new_current = max(update_value(self._current), SimulatedKeithley2400.MINIMUM_CURRENT) new_voltage = update_value(self._voltage) - if self._resistance_mode == ResistanceMode.MANUAL: + # Restrict the current if we're in current compliance mode. Similarly for voltage if new_current < self._current_compliance or self._source_mode == SourceMode.VOLTAGE: self._current = new_current if new_voltage < self._voltage_compliance or self._source_mode == SourceMode.CURRENT: @@ -97,11 +100,12 @@ def update_value(value): self._voltage = new_voltage def reset(self): - self._voltage = SimulatedKeithley2400.INITIAL_POWER_VALUE - self._current = SimulatedKeithley2400.INITIAL_POWER_VALUE + """ Set all the attributes back to their initial values """ + self._initialize_data() @staticmethod def _check_mode(mode, mode_class): + """ Make sure the mode requested exists in the related class """ if mode in mode_class.MODES: return True else: diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 3ca8684b..cc398ad2 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -7,6 +7,7 @@ class Keithley2400StreamInterface(StreamAdapter): serial_commands = { Cmd("get_values", "^:READ\?$"), Cmd("reset", "^\*RST$"), + Cmd("identify", "^\*IDN?"), Cmd("set_output_mode", "^\:OUTP\s(ON|OFF)$"), Cmd("get_output_mode", "^\:OUTP\?$"), Cmd("set_offset_compensation_mode", "^\:SENS:RES:OCOM\s(ON|OFF)$"), @@ -55,6 +56,10 @@ def reset(self): self._device.reset() return "*RST" + def identify(self): + """ Replies with the device's identity """ + return "Keithley 2400 Source Meter emulator" + def set_current(self, value): self._device.set_current(float(value)) return "Current set to: " + str(value) @@ -63,10 +68,8 @@ def set_voltage(self, value): self._device.set_voltage(float(value)) return "Voltage set to: " + str(value) - def _get_option(self, get_method, option_lookup): - return option_lookup[get_method()] - def _set_mode(self, set_method, mode, command): + """ The generic form of how mode sets are executed and responded to """ set_method(mode) return command + " " + mode @@ -125,5 +128,6 @@ def get_voltage_compliance(self): return self._device.get_voltage_compliance() def handle_error(self, request, error): - print "An error occurred at request " + repr(request) + ": " + repr(error) + print "An error occurred at request " + repr(request) + ": " + repr(error) + return str(error) diff --git a/lewis_emulators/keithley_2400/utilities.py b/lewis_emulators/keithley_2400/utilities.py index e4fc1d34..75bfb564 100644 --- a/lewis_emulators/keithley_2400/utilities.py +++ b/lewis_emulators/keithley_2400/utilities.py @@ -1,2 +1,3 @@ def format_value(f, as_string): + """ Format a floating point value into either a string or return it as is """ return "{0:.3f}".format(f) if as_string else f From 5d3cecddfd12d865d14e0785ac2cf10693cb8fea Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 17 Jan 2017 11:57:30 +0000 Subject: [PATCH 0106/1466] Added basic support for Neocera being in two states - monitor and control --- lewis_emulators/neocera_ltc21/device.py | 60 +++++++++++++++++-- .../interfaces/stream_interface.py | 51 ++++++++++++++-- lewis_emulators/neocera_ltc21/states.py | 4 +- 3 files changed, 104 insertions(+), 11 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 8da7e371..23ebc890 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -4,24 +4,74 @@ from lewis.core import approaches -from lewis_emulators.neocera_ltc21.states import MonitorState +from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState class SimulatedNeocera(StateMachineDevice): - def _initialize_data(self): - pass + + """ + + Sets the initial state of the device + + """ + + self.current_state = self._get_initial_state() def _get_state_handlers(self): + + """ + + Returns: states and their names + + """ return { MonitorState.NAME: MonitorState() } def _get_initial_state(self): - return MonitorState.NAME + """ + + Returns: the name of the initial state + + """ + return ControlState.NAME def _get_transition_handlers(self): + + """ + + Returns: the state transitions + + """ + return OrderedDict([ - ((MonitorState.NAME, 'moving'), lambda: False), + ((MonitorState.NAME, ControlState.NAME), lambda: self.current_state == ControlState.NAME), + ((ControlState.NAME, MonitorState.NAME), lambda: self.current_state == MonitorState.NAME), ]) + + def set_state_monitor(self): + """ + + Sets the current state to MONITOR + + """ + self.current_state = MonitorState.NAME + + def set_state_control(self): + """ + + Sets the current state to CONTROL + + """ + self.current_state = ControlState.NAME + + @property + def state(self): + """ + + Returns: the state + + """ + return self._csm.state diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index ab94c8a9..52845060 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -1,18 +1,61 @@ +import re + from lewis.adapters.stream import StreamAdapter, Cmd +from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState + + +def get_regex(arg): + + """ + + Takes a command and turns it into a regex for lewis + + Args: + arg: the command to turn into a regex + + Returns: a regex for lewis + + """ + + arg = re.escape(arg) + output = r"[\r\n]*" + arg + r"[\r\n]*" + return output class NeoceraStreamInterface(StreamAdapter): commands = { - Cmd("get_state", r"[\r\n]*QISTATE\?[\r\n]*"), + Cmd("get_state", get_regex("QISTATE?")), + Cmd("set_state_monitor", get_regex("SMON")), + Cmd("set_state_control", get_regex("SCONT")), } - in_terminator = ";" - out_terminator = ";" + in_terminator = ";\r\n" + out_terminator = ";\r\n" def get_state(self): - return "this is my state" + + """ + Gets the current state of the device + + Returns: a single character string containing a number which represents the state of the device + + """ + + if self._device.state == MonitorState.NAME: + return "0" + elif self._device.state == ControlState.NAME: + return "1" def handle_error(self, request, error): + """ + + Handles errors. + + Args: + request: + error: + + """ print "An error occurred at request " + repr(request) + ": " + repr(error) diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 48f2bed4..3cd6e2f6 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -6,7 +6,7 @@ class OffState(State): class MonitorState(State): - NAME = __name__ + NAME = 'monitor' class ControlState(State): - pass \ No newline at end of file + NAME = 'control' \ No newline at end of file From 9d8622399c3ee4446923cde0ad2444e8d523326a Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 12:19:11 +0000 Subject: [PATCH 0107/1466] Allow C and V as valve identifiers --- .../interfaces/stream_interface.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 3e73519d..e9f5ea78 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -326,6 +326,22 @@ def get_valve_status(self): } return "VST Valve Status " + "".join([status_codes[v] for v in self._device.valves_status()]) + @staticmethod + def _convert_raw_valve_to_int(raw): + """ + Get the valve number from its identifier + + :param raw: The raw valve identifier + :return: An integer indicating the valve number + """ + if str(raw).lower() == "c": + n = 7 + elif str(raw).lower() == "v": + n = 8 + else: + n = convert_raw_to_int(raw) + return n + def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=None): """ Change the valve status @@ -333,7 +349,7 @@ def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=N Returns: string : Indicates the valve number, previous state, and new state """ - valve_number = convert_raw_to_int(valve_number_raw) + valve_number = VolumetricRigStreamInterface._convert_raw_valve_to_int(valve_number_raw) # We should have exactly one of these arguments if set_to_open is not None: From db779c867af43f2363b740cdbba5b6cbb0bdb3f7 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 12:20:16 +0000 Subject: [PATCH 0108/1466] Make variable name more appropriate. May not be int --- lewis_emulators/volumetric_rig/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index d1076eaf..e9337df5 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -342,14 +342,14 @@ def _convert_raw_valve_to_int(raw): n = convert_raw_to_int(raw) return n - def _set_valve_status(self, valve_number_raw, set_to_open=None, set_to_enabled=None): + def _set_valve_status(self, valve_identifier_raw, set_to_open=None, set_to_enabled=None): """ Change the valve status Returns: string : Indicates the valve number, previous state, and new state """ - valve_number = VolumetricRigStreamInterface._convert_raw_valve_to_int(valve_number_raw) + valve_number = VolumetricRigStreamInterface._convert_raw_valve_to_int(valve_identifier_raw) # We should have exactly one of these arguments if set_to_open is not None: From aed7300e32269ca962c3cf3510d79d4ad95bc799 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 12:30:02 +0000 Subject: [PATCH 0109/1466] Add some documentation --- lewis_emulators/volumetric_rig/buffer.py | 20 ++++++++++++++++++- .../volumetric_rig/error_states.py | 3 +++ .../volumetric_rig/ethernet_device.py | 3 +++ lewis_emulators/volumetric_rig/gas.py | 3 +++ .../volumetric_rig/pressure_sensor.py | 16 +++++++++------ .../volumetric_rig/seed_gas_data.py | 4 +++- lewis_emulators/volumetric_rig/sensor.py | 3 +++ .../volumetric_rig/sensor_status.py | 3 +++ .../volumetric_rig/system_gases.py | 3 +++ .../volumetric_rig/two_gas_mixer.py | 3 +++ lewis_emulators/volumetric_rig/valve.py | 3 +++ .../volumetric_rig/valve_status.py | 3 +++ 12 files changed, 59 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/volumetric_rig/buffer.py b/lewis_emulators/volumetric_rig/buffer.py index 45a34498..f306dd77 100644 --- a/lewis_emulators/volumetric_rig/buffer.py +++ b/lewis_emulators/volumetric_rig/buffer.py @@ -4,6 +4,11 @@ class Buffer(object): + """ + A buffer contains a gas and is connected to a supply of a specific system gas via a valve. The system gas can be + changed and the buffer fills from the system gas it is connected to. The valve can only be opened if mixing of + the system and buffer gas are permitted + """ def __init__(self, index, buffer_gas, system_gas): assert buffer_gas is not None assert system_gas is not None @@ -22,6 +27,12 @@ def index(self, as_string=False, length=1): return format_int(self._index, as_string, length) def open_valve(self, mixer): + """ + Try to open the valve between the buffer and system. Nothing will happen if the buffer and system gases are not + allowed to mix. + + :param mixer: The details of which gases can be mixed + """ assert isinstance(mixer, TwoGasMixer) if mixer.can_mix(self._buffer_gas, self._system_gas): self._valve.open() @@ -33,7 +44,9 @@ def enable_valve(self): self._valve.enable() def disable_valve(self): - # Valves must be closed before they are disabled + """ + Disable the valve. If the valve is open when this is requested then it will be automatically closed + """ self._valve.close() self._valve.disable() @@ -53,5 +66,10 @@ def system_gas(self): return self._system_gas def set_system_gas(self, gas): + """ + Set a new system gas. This is only possible if the valve is closed. + + :param gas: The new system gas + """ if not self._valve.is_open(): self._system_gas = gas diff --git a/lewis_emulators/volumetric_rig/error_states.py b/lewis_emulators/volumetric_rig/error_states.py index b671c473..622f0350 100644 --- a/lewis_emulators/volumetric_rig/error_states.py +++ b/lewis_emulators/volumetric_rig/error_states.py @@ -1,4 +1,7 @@ class ErrorStates(object): + """ + The possible error states the device can be in + """ def __init__(self): self.run = False self.hmi = False diff --git a/lewis_emulators/volumetric_rig/ethernet_device.py b/lewis_emulators/volumetric_rig/ethernet_device.py index b90eb1cf..07ddb361 100644 --- a/lewis_emulators/volumetric_rig/ethernet_device.py +++ b/lewis_emulators/volumetric_rig/ethernet_device.py @@ -2,6 +2,9 @@ class EthernetDevice(object): + """ + An ethernet device that the rig communicates with + """ def __init__(self, ip): assert type(ip) is StringType self._ip = ip diff --git a/lewis_emulators/volumetric_rig/gas.py b/lewis_emulators/volumetric_rig/gas.py index 0434479a..d83bec8b 100644 --- a/lewis_emulators/volumetric_rig/gas.py +++ b/lewis_emulators/volumetric_rig/gas.py @@ -3,6 +3,9 @@ class Gas(object): + """ + A gas within the system, identified by either its name or an integer index + """ def __init__(self, index, name): assert type(index) is IntType and type(name) is StringType self._index = index diff --git a/lewis_emulators/volumetric_rig/pressure_sensor.py b/lewis_emulators/volumetric_rig/pressure_sensor.py index f0baa127..0981a9c2 100644 --- a/lewis_emulators/volumetric_rig/pressure_sensor.py +++ b/lewis_emulators/volumetric_rig/pressure_sensor.py @@ -1,22 +1,26 @@ from sensor import Sensor from sensor_status import SensorStatus -from random import random -from lewis.core.approaches import linear as linearApproach class PressureSensor(Sensor): + """ + A sensor that reads the pressure. + """ def __init__(self): super(PressureSensor, self).__init__() def set_value(self, v, target): + """ + Updates the pressure reading with a new value along with the sensor's status + + :param v: The new value + :param target: The target/limit value + """ super(PressureSensor, self).set_value(v) if self._value < 0.0: self._status = SensorStatus.VALUE_TOO_LOW elif self._value > target: self._status = SensorStatus.VALUE_TOO_HIGH else: - self._status = SensorStatus.VALUE_IN_RANGE - - def approach_value(self, dt, target, rate): - self._value = linearApproach(self._value, target, rate, dt) \ No newline at end of file + self._status = SensorStatus.VALUE_IN_RANGE \ No newline at end of file diff --git a/lewis_emulators/volumetric_rig/seed_gas_data.py b/lewis_emulators/volumetric_rig/seed_gas_data.py index 467ab039..4b61f6ec 100644 --- a/lewis_emulators/volumetric_rig/seed_gas_data.py +++ b/lewis_emulators/volumetric_rig/seed_gas_data.py @@ -1,5 +1,7 @@ class SeedGasData(object): - """Contains information about gases and their mixing properties used to set up the initial device state""" + """ + Contains information about gases and their mixing properties used to set up the initial device state + """ # Gas names unknown = "UNKNOWN" diff --git a/lewis_emulators/volumetric_rig/sensor.py b/lewis_emulators/volumetric_rig/sensor.py index 7222b4a1..23882990 100644 --- a/lewis_emulators/volumetric_rig/sensor.py +++ b/lewis_emulators/volumetric_rig/sensor.py @@ -3,6 +3,9 @@ class Sensor(object): + """ + A basic sensor which monitors a value and keeps track of its own status + """ def __init__(self): self._status = SensorStatus.NO_REPLY self._value = 0.0 diff --git a/lewis_emulators/volumetric_rig/sensor_status.py b/lewis_emulators/volumetric_rig/sensor_status.py index f3263a79..2035ebcb 100644 --- a/lewis_emulators/volumetric_rig/sensor_status.py +++ b/lewis_emulators/volumetric_rig/sensor_status.py @@ -1,2 +1,5 @@ class SensorStatus(object): + """ + An enumeration of possible sensor states + """ UNKNOWN, DISABLED, NO_REPLY, VALUE_IN_RANGE, VALUE_TOO_LOW, VALUE_TOO_HIGH = (i for i in range(6)) diff --git a/lewis_emulators/volumetric_rig/system_gases.py b/lewis_emulators/volumetric_rig/system_gases.py index ae19a2cf..a43c8215 100644 --- a/lewis_emulators/volumetric_rig/system_gases.py +++ b/lewis_emulators/volumetric_rig/system_gases.py @@ -2,6 +2,9 @@ class SystemGases(object): + """ + The collection of gases that are available within the system + """ def __init__(self, gases=list()): self._gases = set() self._add_gases(gases) diff --git a/lewis_emulators/volumetric_rig/two_gas_mixer.py b/lewis_emulators/volumetric_rig/two_gas_mixer.py index f8cb2373..d2b24408 100644 --- a/lewis_emulators/volumetric_rig/two_gas_mixer.py +++ b/lewis_emulators/volumetric_rig/two_gas_mixer.py @@ -1,4 +1,7 @@ class TwoGasMixer(object): + """ + Keeps a record of whether pairs of gases can be mixed + """ def __init__(self): self.mixable = set() diff --git a/lewis_emulators/volumetric_rig/valve.py b/lewis_emulators/volumetric_rig/valve.py index 2873e8a0..213e2a73 100644 --- a/lewis_emulators/volumetric_rig/valve.py +++ b/lewis_emulators/volumetric_rig/valve.py @@ -2,6 +2,9 @@ class Valve(object): + """ + Valves can either be enabled/disabled and open/closed. A valve should never be open and disabled. + """ def __init__(self): self._is_enabled = True self._is_open = False diff --git a/lewis_emulators/volumetric_rig/valve_status.py b/lewis_emulators/volumetric_rig/valve_status.py index c1c99598..1decefce 100644 --- a/lewis_emulators/volumetric_rig/valve_status.py +++ b/lewis_emulators/volumetric_rig/valve_status.py @@ -1,2 +1,5 @@ class ValveStatus(object): + """ + An enumeration of possible valve states. OPEN_AND_DISABLED should never happen + """ OPEN_AND_ENABLED, CLOSED_AND_ENABLED, OPEN_AND_DISABLED, CLOSED_AND_DISABLED = (i for i in range(4)) \ No newline at end of file From 0b30eab2d05f919cc864f8375eaf7e69bbcc6759 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 13:20:00 +0000 Subject: [PATCH 0110/1466] Convert documentation style --- .../interfaces/stream_interface.py | 154 ++++++------------ 1 file changed, 52 insertions(+), 102 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index e9337df5..99ed4675 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -59,10 +59,11 @@ class VolumetricRigStreamInterface(StreamAdapter): output_length = 20 def purge(self, chars): - """ Responds any current input to the screen without executing it + """ + Responds any current input to the screen without executing it - Returns: - string : Purge message including ignored input + :param chars: Whatever characters are left over in the buffer + :return: Purge message including ignored input """ return " ".join([ "PRG,00,Purge", @@ -74,8 +75,7 @@ def purge(self, chars): def get_identity(self): """ Responds with the devices identity - Returns: - string : Device identity + :return: Device identity """ return "IDN,00," + self._device.identify() @@ -83,11 +83,8 @@ def _build_buffer_control_and_status_string(self, buffer_number): """ Get information about a specific buffer, its valve state and the gases connected to it. - Args: - buffer_number : The index of the buffer - - Returns: - string : Information about the requested buffer + :param buffer_number : The index of the buffer + :return: Information about the requested buffer """ buff = self._device.buffer(buffer_number) assert buff is not None @@ -106,12 +103,9 @@ def get_buffer_control_and_status(self, buffer_number_raw): """ Get information about a specific buffer, its valve state and the gases connected to it. - Args: - buffer_number_raw : The buffer "number" entered by a user. Although a number is expected, the command will - accept other types of input - - Returns: - string : Information about the requested buffer + :param buffer_number_raw : The buffer "number" entered by a user. Although a number is expected, the command + will accept other types of input + :return: Information about the requested buffer """ buffer_number = convert_raw_to_int(buffer_number_raw) message_prefix = "BCS" @@ -131,8 +125,7 @@ def get_ethernet_and_hmi_status(self): """ Get information about the rig's hmi and plc ethernet devices - Returns: - string : Information about the ethernet devices status. The syntax of the return string is odd: the + :return: Information about the ethernet devices status. The syntax of the return string is odd: the separators are not consistent """ return " ".join([ @@ -146,8 +139,7 @@ def get_gas_control_and_status(self): """ Get a list of information about all the buffers, their associated gases and valve statuses - Returns: - string : Buffer information. One line per buffer with a header + :return: Buffer information. One line per buffer with a header """ return "\r\n".join( ["No No Buffer E O No System"] + @@ -159,8 +151,7 @@ def get_gas_mix_matrix(self): """ Get information about which gases can be mixed together - Returns: - string : A 2D matrix representation of the ability to mix different gases with column and row titles + :return: A 2D matrix representation of the ability to mix different gases with column and row titles """ system_gases = self._device.system_gases().gases() column_headers = [gas.name(VolumetricRigStreamInterface.output_length, '|') for gas in system_gases] @@ -201,8 +192,8 @@ def gas_mix_check(self, gas1_index_raw, gas2_index_raw): accept other types of input gas2_index_raw : As above for the 2nd gas - Returns: - string : An echo of the name and index of the requested gases as well as an ok/NO indicating whether the + + :return: An echo of the name and index of the requested gases as well as an ok/NO indicating whether the gases can be mixed """ gas1 = self._device.system_gases().gas_by_index(convert_raw_to_int(gas1_index_raw)) @@ -221,8 +212,8 @@ def get_gas_number_available(self): """ Get the number of available gases - Returns: - string : The number of available gases + + :return: The number of available gases """ return self._device.system_gases().gas_count() @@ -230,8 +221,7 @@ def get_hmi_status(self): """ Get the current status of the HMI - Returns: - string : Information about the HMI + :return: Information about the HMI """ hmi = self._device.hmi() return ",".join(["HMI " + hmi.status() + " ", hmi.ip(), @@ -246,8 +236,7 @@ def get_hmi_count_cycles(self): """ Get information about how frequently the HMI is disconnected - Returns: - string : A list of integers indicating the number of occurrences of a disconnected count cycle of a specific + :return: A list of integers indicating the number of occurrences of a disconnected count cycle of a specific length """ return " ".join(["HMC"] + self._device.hmi().count_cycles()) @@ -260,8 +249,7 @@ def get_memory_location(self, location_raw): location_raw : The memory location to read. Although a number is expected, the command will accept other types of input - Returns: - string : The memory location and the value stored there + :return: The memory location and the value stored there """ location = convert_raw_to_int(location_raw) return " ".join(["RDM", format_int(location, as_string=True, length=4), @@ -271,8 +259,7 @@ def get_pressure_and_temperature_status(self): """ Get the status of the temperature and pressure sensors - Returns: - string : A letter for each sensor indicating its status. Refer to the spec for the meaning and sensor order + :return: A letter for each sensor indicating its status. Refer to the spec for the meaning and sensor order """ status_codes = { @@ -292,8 +279,7 @@ def get_pressures(self): """ Get the current pressure sensor readings, and target pressure - Returns: - string : The pressure readings from each of the pressure sensors and the target pressure which, if exceeded, + :return: The pressure readings from each of the pressure sensors and the target pressure which, if exceeded, will cause all buffer valves to close and disable """ return " ".join(["PMV"] + @@ -304,8 +290,7 @@ def get_temperatures(self): """ Get the current temperature reading - Returns: - string : The current temperature for each of the temperature sensors + :return: The current temperature for each of the temperature sensors """ return " ".join(["TMV"] + [t.value(as_string=True) for t in self._device.temperature_sensors(reverse=True)]) @@ -314,8 +299,7 @@ def get_valve_status(self): """ Get the status of the buffer and system valves - Returns: - string : The status of each of the system valves represented by a letter. Refer to the specification for the + :return: The status of each of the system valves represented by a letter. Refer to the specification for the exact meaning and order """ status_codes = { @@ -346,8 +330,10 @@ def _set_valve_status(self, valve_identifier_raw, set_to_open=None, set_to_enabl """ Change the valve status - Returns: - string : Indicates the valve number, previous state, and new state + :param valve_identifier_raw: A raw value that identifies the valve + :param set_to_open: Whether to set the valve to open(True)/closed(False)/do noting(None) + :param set_to_enabled: Whether to set the valve to enabled(True)/disabled(False)/do noting(None) + :return: Indicates the valve number, previous state, and new state """ valve_number = VolumetricRigStreamInterface._convert_raw_valve_to_int(valve_identifier_raw) @@ -425,15 +411,11 @@ def close_valve(self, valve_number_raw): """ Close a valve - Args: - valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + :param valve_number_raw: The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply valve cannot be controlled via serial. Although a number is expected, the command will accept other types of input - - - Returns: - string : Indicates the valve number, previous state, and new state + :return: Indicates the valve number, previous state, and new state """ return self._set_valve_status(valve_number_raw, set_to_open=False) @@ -441,15 +423,11 @@ def open_valve(self, valve_number_raw): """ Open a valve - Args: - valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + :param valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply valve cannot be controlled via serial. Although a number is expected, the command will accept other types of input - - - Returns: - string : Indicates the valve number, previous state, and new state + :return: Indicates the valve number, previous state, and new state """ return self._set_valve_status(valve_number_raw, set_to_open=True) @@ -457,15 +435,11 @@ def enable_valve(self, valve_number_raw): """ Enable a valve - Args: - valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + :param valve_number_raw: The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply valve cannot be controlled via serial. Although a number is expected, the command will accept other types of input - - - Returns: - string : Indicates the valve number, previous state, and new state + :return: Indicates the valve number, previous state, and new state """ return self._set_valve_status(convert_raw_to_int(valve_number_raw), set_to_enabled=True) @@ -473,15 +447,11 @@ def disable_valve(self, valve_number_raw): """ Disable a valve - Args: - valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n + :param valve_number_raw: The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply valve cannot be controlled via serial. Although a number is expected, the command will accept other types of input - - - Returns: - string : Indicates the valve number, previous state, and new state + :return: Indicates the valve number, previous state, and new state """ return self._set_valve_status(convert_raw_to_int(valve_number_raw), set_to_enabled=False) @@ -489,8 +459,7 @@ def halt(self): """ Halts the device. No further valve commands will be accepted. - Returns: - string : Indicates that the system has been, or was already halted + :return: Indicates that the system has been, or was already halted """ if self._device.halted(): message = "SYSTEM ALREADY HALTED" @@ -504,9 +473,7 @@ def get_system_status(self): """ Get information about the current system state. - - Returns: - string : Information about the system. Capitalisation of a particular word indicates an error has occurred + :return: Information about the system. Capitalisation of a particular word indicates an error has occurred in that subsystem. Refer to the specification for the meaning of system codes """ return " ".join([ @@ -523,36 +490,31 @@ def get_system_status(self): def get_ports_and_relays_hex(self): """ - Returns: - string : Information about the ports and relays + :return: Information about the ports and relays """ return "PTR I:00 0000 0000 R:0000 0200 0000 O:00 0000 4400" def get_ports_output(self): """ - Returns: - string : Information about the port output + :return: Information about the port output """ return "POT qwertyus vsbbbbbbzyxwvuts aBhecSssvsbbbbbb" def get_ports_input(self): """ - Returns: - string : Information about the port input + :return: Information about the port input """ return "PIN qwertyui zyxwvutsrqponmlk abcdefghijklmneb" def get_ports_relays(self): """ - Returns: - string : Information about the port relays. + :return: Information about the port relays. """ return "PRY qwertyuiopasdfgh zyxwhmLsrqponmlk abcdefghihlbhace" def get_com_activity(self): """ - Returns: - string : Information about activity over the COM port + :return: Information about activity over the COM port """ return "COM ok 0113/0000" @@ -560,12 +522,9 @@ def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): """ Changes the system gas associated with a particular buffer - Args: - buffer_index_raw : The index of the buffer to update - gas_index_raw : The index of the gas to update - - Returns: - string : Indicates the buffer changed, the previous system gas and the new system gas + :param buffer_index_raw: The index of the buffer to update + :param gas_index_raw: The index of the gas to update + :return: Indicates the buffer changed, the previous system gas and the new system gas """ gas = self._device.system_gases().gas_by_index(convert_raw_to_int(gas_index_raw)) buff = self._device.buffer(convert_raw_to_int(buffer_index_raw)) @@ -591,11 +550,8 @@ def set_pressure_cycling(self, on_int_raw): falls below set limits. When the pressure reaches a minimum, the cycle is restarted. This allows simulation of various valve conditions. - Args: - on_int_raw : Whether to switch cycling on(1)/off(other) - - Returns: - string : Indicates whether cycling is enabled + :param on_int_raw: Whether to switch cycling on(1)/off(other) + :return: Indicates whether cycling is enabled """ cycle = convert_raw_to_bool(on_int_raw) self._device.cycle_pressures(cycle) @@ -605,11 +561,8 @@ def set_pressures(self, value_raw): """ Set the reading for all pressure sensors to a fixed value - Args: - value_raw : The value to apply to the pressure sensors - - Returns: - string : Echo the new pressure + :param value_raw: The value to apply to the pressure sensors + :return: Echo the new pressure """ value = convert_raw_to_float(value_raw) self._device.set_pressures(value) @@ -619,11 +572,8 @@ def set_pressure_target(self, value_raw): """ Set the target (limit) pressure for the system - Args: - value_raw : The new pressure target - - Returns: - string : Echo the new target + :param value_raw: The new pressure target + :return: Echo the new target """ value = convert_raw_to_float(value_raw) self._device.set_pressure_target(value) From e80c9444788feadd4fd18c875628b4532fb023f5 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 14:15:15 +0000 Subject: [PATCH 0111/1466] Use C and V as valve identifiers --- lewis_emulators/volumetric_rig/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 99ed4675..47c44e4c 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -52,8 +52,8 @@ class VolumetricRigStreamInterface(StreamAdapter): commands = set.union(serial_commands, control_commands) # You may need to change these to \r\n if using Telnet" - in_terminator = "\r" - out_terminator = "\r" + in_terminator = "\r\n" + out_terminator = "\r\n" # Lots of formatted output for the volumetric rig is based on fixed length strings output_length = 20 From 176b77b6e036858d29b8c56373124116ae5810ab Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 17 Jan 2017 14:15:45 +0000 Subject: [PATCH 0112/1466] \r\n for Telnet only --- lewis_emulators/volumetric_rig/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 47c44e4c..99ed4675 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -52,8 +52,8 @@ class VolumetricRigStreamInterface(StreamAdapter): commands = set.union(serial_commands, control_commands) # You may need to change these to \r\n if using Telnet" - in_terminator = "\r\n" - out_terminator = "\r\n" + in_terminator = "\r" + out_terminator = "\r" # Lots of formatted output for the volumetric rig is based on fixed length strings output_length = 20 From 33500314aa6f6c52aa02739cadcfc93f8be869b8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 19 Jan 2017 16:13:05 +0000 Subject: [PATCH 0113/1466] . --- lewis_emulators/neocera_ltc21/device.py | 3 +++ lewis_emulators/neocera_ltc21/interfaces/stream_interface.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 23ebc890..2707ff84 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -18,6 +18,9 @@ def _initialize_data(self): """ self.current_state = self._get_initial_state() + self.temperature = 0 + self.setpoint = 0 + self.unit = "C" def _get_state_handlers(self): diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 52845060..e5ae9105 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -28,6 +28,7 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("get_state", get_regex("QISTATE?")), Cmd("set_state_monitor", get_regex("SMON")), Cmd("set_state_control", get_regex("SCONT")), + Cmd("get_temperature_and_unit", get_regex("QSAMP?1")), } in_terminator = ";\r\n" @@ -47,6 +48,9 @@ def get_state(self): elif self._device.state == ControlState.NAME: return "1" + def get_temperature_and_unit(self): + return "{0:10f}{1:1s}".format(self._device.temperature, self._device.unit) + def handle_error(self, request, error): """ From 54dd64054893efe752bd281f6905a49fc2640581 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Thu, 19 Jan 2017 16:20:29 +0000 Subject: [PATCH 0114/1466] Add comments and correct line terminator --- lewis_emulators/neocera_ltc21/device.py | 6 +++--- .../interfaces/stream_interface.py | 4 ++-- lewis_emulators/neocera_ltc21/states.py | 19 +++++++++++++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 2707ff84..16c6d9f1 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -1,13 +1,13 @@ from collections import OrderedDict from lewis.devices import StateMachineDevice - -from lewis.core import approaches - from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState class SimulatedNeocera(StateMachineDevice): + """ + Simulated Neocera LTG21 temperature controller + """ def _initialize_data(self): diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index e5ae9105..6f5c9132 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -31,8 +31,8 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("get_temperature_and_unit", get_regex("QSAMP?1")), } - in_terminator = ";\r\n" - out_terminator = ";\r\n" + in_terminator = ";" + out_terminator = ";\n" def get_state(self): diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 3cd6e2f6..0533611d 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -1,12 +1,27 @@ from lewis.core.statemachine import State +from lewis.core import approaches class OffState(State): - pass + """ + Device is in off state. + It does not display the temperature on the front it is not monitoring or controlling it. + + """ + NAME = 'off' class MonitorState(State): + """ + Temperature is being monitored but heater is switched off + """ NAME = 'monitor' + class ControlState(State): - NAME = 'control' \ No newline at end of file + """ + Temperature is being controller and monitored. The device will try to use the heater to make + the temperature the same as the set point. + """ + + NAME = 'control' From a8086967f84d86225305397f6872e67498a08ea3 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Fri, 20 Jan 2017 14:27:07 +0000 Subject: [PATCH 0115/1466] Add get sample temps from either sensor --- lewis_emulators/neocera_ltc21/device.py | 13 +++--- .../neocera_ltc21/device_errors.py | 12 +++++ .../interfaces/stream_interface.py | 45 +++++++++++++++---- 3 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 lewis_emulators/neocera_ltc21/device_errors.py diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 16c6d9f1..68c57e2a 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -1,8 +1,9 @@ from collections import OrderedDict from lewis.devices import StateMachineDevice -from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState +from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors +from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState class SimulatedNeocera(StateMachineDevice): """ @@ -18,9 +19,10 @@ def _initialize_data(self): """ self.current_state = self._get_initial_state() - self.temperature = 0 - self.setpoint = 0 - self.unit = "C" + self.temperature = [0, 1] + self.set_point = [0, 2] + self.unit = ["C", "K"] + self.error = NeoceraDeviceErrors() def _get_state_handlers(self): @@ -30,7 +32,8 @@ def _get_state_handlers(self): """ return { - MonitorState.NAME: MonitorState() + MonitorState.NAME: MonitorState(), + ControlState.NAME: ControlState() } def _get_initial_state(self): diff --git a/lewis_emulators/neocera_ltc21/device_errors.py b/lewis_emulators/neocera_ltc21/device_errors.py new file mode 100644 index 00000000..b09d17ab --- /dev/null +++ b/lewis_emulators/neocera_ltc21/device_errors.py @@ -0,0 +1,12 @@ + + +class NeoceraDeviceErrors(object): + """ + Class to represent errors + """ + + # bad parameter has been encountered + BAD_PARAMETER = "Bad parameter" + + def __init__(self, *error_list): + self.errors = error_list diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 6f5c9132..fa017fb0 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -1,34 +1,43 @@ import re from lewis.adapters.stream import StreamAdapter, Cmd + +from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState -def get_regex(arg): +def get_regex(command, *args): """ - Takes a command and turns it into a regex for lewis + Takes a command and optional arguments and turns then into a regex for lewis Args: - arg: the command to turn into a regex + command: the command to turn into a regex Returns: a regex for lewis """ - arg = re.escape(arg) - output = r"[\r\n]*" + arg + r"[\r\n]*" - return output + command = re.escape(command) + args_regex = "" + for arg in args: + args_regex += "{stripped}({arg})".format(arg=arg, stripped=r"[\r\n\s]*") + + return "{stripped}{command}{args_regex}{stripped}".format( + stripped=r"[\r\n\s]*", args_regex=args_regex, command=command) class NeoceraStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ commands = { Cmd("get_state", get_regex("QISTATE?")), Cmd("set_state_monitor", get_regex("SMON")), Cmd("set_state_control", get_regex("SCONT")), - Cmd("get_temperature_and_unit", get_regex("QSAMP?1")), + Cmd("get_temperature_and_unit", get_regex("QSAMP?", "\d")), } in_terminator = ";" @@ -48,8 +57,26 @@ def get_state(self): elif self._device.state == ControlState.NAME: return "1" - def get_temperature_and_unit(self): - return "{0:10f}{1:1s}".format(self._device.temperature, self._device.unit) + def get_temperature_and_unit(self, sensor_number): + """ + Return the temperature and unit for the sensor number given. + Args: + sensor_number: sensor number + + Returns: formatted temperature and unit for the device + + """ + sensor_index = int(sensor_number) - 1 + try: + temp = self._device.temperature[sensor_index] + unit = self._device.unit[sensor_index] + if temp is None: + return " ------ " + return "{0:8f}{1:1s}".format(temp, unit) + except (IndexError, ValueError, TypeError): + print "Error: invalid sensor number requested '{0}'".format(sensor_number) + self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + return "" def handle_error(self, request, error): """ From 51b804edb195da101e71ae7edd372fb111ef14ec Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Fri, 20 Jan 2017 14:47:42 +0000 Subject: [PATCH 0116/1466] Setpoint reading --- lewis_emulators/neocera_ltc21/device.py | 7 ++-- .../interfaces/stream_interface.py | 39 ++++++++++++++++--- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 68c57e2a..0dee205c 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -19,9 +19,10 @@ def _initialize_data(self): """ self.current_state = self._get_initial_state() - self.temperature = [0, 1] - self.set_point = [0, 2] - self.unit = ["C", "K"] + self.temperatures = [(0, "C"), (1, "K")] + + self.setpoints = [(2, "C"), (3, "K")] + self.error = NeoceraDeviceErrors() def _get_state_handlers(self): diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index fa017fb0..987cc5f1 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -38,6 +38,7 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("set_state_monitor", get_regex("SMON")), Cmd("set_state_control", get_regex("SCONT")), Cmd("get_temperature_and_unit", get_regex("QSAMP?", "\d")), + Cmd("get_setpoint_and_unit", get_regex("QSETP?", "\d")), } in_terminator = ";" @@ -66,18 +67,46 @@ def get_temperature_and_unit(self, sensor_number): Returns: formatted temperature and unit for the device """ - sensor_index = int(sensor_number) - 1 + return self._get_indexed_value_with_unit(self._device.temperatures, sensor_number) + + def _get_indexed_value_with_unit(self, device_temperatures, item_number): + """ + Get a temperature like value back from device temperatures in the format produced by the device + Args: + device_temperatures: device temperatures list + item_number: item to return + + Returns: temp and units; e.g. setpoint 1.2K + + """ try: - temp = self._device.temperature[sensor_index] - unit = self._device.unit[sensor_index] - if temp is None: + sensor_index = int(item_number) - 1 + temperature_combined = device_temperatures[sensor_index] + + # for temperatures it can not read + if temperature_combined is None: return " ------ " + + temp, unit = temperature_combined + return "{0:8f}{1:1s}".format(temp, unit) + except (IndexError, ValueError, TypeError): - print "Error: invalid sensor number requested '{0}'".format(sensor_number) + print "Error: invalid sensor number requested '{0}'".format(item_number) self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) return "" + def get_setpoint_and_unit(self, set_point_number): + """ + + Args: + set_point_number: the number of set point top return, 1=HEATER, 2=ANALOG + + Returns: setpoint with unit + + """ + return self._get_indexed_value_with_unit(self._device.setpoints, set_point_number) + def handle_error(self, request, error): """ From deb58d76d9cd2add7d97c4a5ce861b53e5c2bfca Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Fri, 20 Jan 2017 15:16:44 +0000 Subject: [PATCH 0117/1466] Set setpoint --- .../interfaces/stream_interface.py | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 987cc5f1..5ffdc300 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -21,8 +21,10 @@ def get_regex(command, *args): command = re.escape(command) args_regex = "" + comma = "" for arg in args: - args_regex += "{stripped}({arg})".format(arg=arg, stripped=r"[\r\n\s]*") + args_regex += "{stripped}{comma}({arg})".format(arg=arg, stripped=r"[\r\n\s]*", comma=comma) + comma = "," return "{stripped}{command}{args_regex}{stripped}".format( stripped=r"[\r\n\s]*", args_regex=args_regex, command=command) @@ -39,6 +41,7 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("set_state_control", get_regex("SCONT")), Cmd("get_temperature_and_unit", get_regex("QSAMP?", "\d")), Cmd("get_setpoint_and_unit", get_regex("QSETP?", "\d")), + Cmd("set_setpoint", get_regex("SETP", "\d", "[+-]?\d+\.?\d+")), } in_terminator = ";" @@ -100,13 +103,35 @@ def get_setpoint_and_unit(self, set_point_number): """ Args: - set_point_number: the number of set point top return, 1=HEATER, 2=ANALOG + set_point_number: the number of set point top return; 1=HEATER, 2=ANALOG Returns: setpoint with unit """ return self._get_indexed_value_with_unit(self._device.setpoints, set_point_number) + def set_setpoint(self, set_point_number, value): + """ + Set the setpoint. + Args: + set_point_number: set point number; 1=HEATER, 2=ANALOG + value: value to set it to + + Returns: blank + + """ + try: + sensor_number = int(set_point_number) - 1 + setpoint = float(value) + + temp, unit = self._device.setpoints[sensor_number] + self._device.setpoints[sensor_number] = (setpoint, unit) + except (IndexError, ValueError, TypeError): + print "Error: invalid sensor number, '{0}', or setpoint value, '{1}'".format(set_point_number, value) + self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + return "" + + def handle_error(self, request, error): """ @@ -118,4 +143,3 @@ def handle_error(self, request, error): """ print "An error occurred at request " + repr(request) + ": " + repr(error) - From 84aa26bc0c00d0f043928996a97eb976e0321ac9 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:19:05 +0000 Subject: [PATCH 0118/1466] Update interfaces based on real device --- lewis_emulators/keithley_2400/control_modes.py | 4 ++-- .../keithley_2400/interfaces/stream_interface.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lewis_emulators/keithley_2400/control_modes.py b/lewis_emulators/keithley_2400/control_modes.py index e8ba317b..3778ddc3 100644 --- a/lewis_emulators/keithley_2400/control_modes.py +++ b/lewis_emulators/keithley_2400/control_modes.py @@ -31,8 +31,8 @@ class ResistanceMode(AutoMode): class OnOffMode(Mode): - ON = "ON" - OFF = "OFF" + ON = "1" + OFF = "0" MODES = [ON, OFF] diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index cc398ad2..faf42532 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -8,13 +8,13 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_values", "^:READ\?$"), Cmd("reset", "^\*RST$"), Cmd("identify", "^\*IDN?"), - Cmd("set_output_mode", "^\:OUTP\s(ON|OFF)$"), + Cmd("set_output_mode", "^\:OUTP\s(1|0)$"), Cmd("get_output_mode", "^\:OUTP\?$"), - Cmd("set_offset_compensation_mode", "^\:SENS:RES:OCOM\s(ON|OFF)$"), + Cmd("set_offset_compensation_mode", "^\:SENS:RES:OCOM\s(1|0)$"), Cmd("get_offset_compensation_mode", "^\:SENS:RES:OCOM\?$"), Cmd("set_resistance_mode", "^\:SENS:RES:MODE\s(AUTO|MAN)$"), Cmd("get_resistance_mode", "^\:SENS:RES:MODE\?$"), - Cmd("set_remote_sensing_mode", "^\:SYST:RSEN\s(ON|OFF)$"), + Cmd("set_remote_sensing_mode", "^\:SYST:RSEN\s(1|0)$"), Cmd("get_remote_sensing_mode", "^\:SYST:RSEN\?$"), Cmd("set_resistance_range_mode", "^\:SENS:RES:RANG:AUTO\s(0|1)$"), Cmd("get_resistance_range_mode", "^\:SENS:RES:RANG:AUTO\?$"), @@ -22,10 +22,10 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_resistance_range", "^\:SENS:RES:RANG\?$"), Cmd("set_source_mode", "^\:SOUR:FUNC\s(CURR|VOLT)$"), Cmd("get_source_mode", "^\:SOUR:FUNC\?$"), - Cmd("set_current_compliance", "^\:SENS:CURR\s([-+]?[0-9]*\.?[0-9]+)$"), - Cmd("get_current_compliance", "^\:SENS:CURR\?$"), - Cmd("set_voltage_compliance", "^\:SENS:VOLT\s([-+]?[0-9]*\.?[0-9]+)$"), - Cmd("get_voltage_compliance", "^\:SENS:VOLT\?$"), + Cmd("set_current_compliance", "^\:SENS:CURR:PROT\s([-+]?[0-9]*\.?[0-9]+)$"), + Cmd("get_current_compliance", "^\:SENS:CURR:PROT\?$"), + Cmd("set_voltage_compliance", "^\:SENS:VOLT:PROT\s([-+]?[0-9]*\.?[0-9]+)$"), + Cmd("get_voltage_compliance", "^\:SENS:VOLT:PROT\?$"), } # Private control commands that can be used as an alternative to the lewis backdoor From c7de66b411b467c2f0dda30fdd5cef7c3b2a0eae Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:20:21 +0000 Subject: [PATCH 0119/1466] Output value not dependent on output mode --- lewis_emulators/keithley_2400/device.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 4cf1a961..790c0054 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -59,12 +59,9 @@ def _format_power_output(self, value, as_string, offset=0.0): Some properties like output mode and offset compensation affect the output without affecting the underlying model. Those adjustments are applied here. """ - if self._output_mode == OutputMode.OFF: - output_value = 0.0 - elif self._offset_compensation_mode == OffsetCompensationMode.ON: - output_value = value - offset - else: - output_value = value + output_value = value + if self._offset_compensation_mode == OffsetCompensationMode.ON: + output_value -= offset return format_value(output_value, as_string) def set_voltage(self, value): From d89828937a1a9b4b81d8df748e8a9025fea53d5c Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 23 Jan 2017 12:21:25 +0000 Subject: [PATCH 0120/1466] Change of temperature because of the heater in control state --- lewis_emulators/neocera_ltc21/device.py | 16 ++++++++++++++-- .../neocera_ltc21/interfaces/stream_interface.py | 15 +++++++-------- lewis_emulators/neocera_ltc21/states.py | 8 +++++++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 0dee205c..d8321fc3 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -5,6 +5,7 @@ from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState + class SimulatedNeocera(StateMachineDevice): """ Simulated Neocera LTG21 temperature controller @@ -18,11 +19,22 @@ def _initialize_data(self): """ + # desired current state of the system self.current_state = self._get_initial_state() - self.temperatures = [(0, "C"), (1, "K")] - self.setpoints = [(2, "C"), (3, "K")] + # number of sensors + self.sensor_count = 2 + + # temperature of the samples measure by sensor n + self.temperatures = [0] * self.sensor_count + + # display units + self.units = ["K"] * self.sensor_count + + # the set points + self.setpoints = [2] * self.sensor_count + # errors created within the device self.error = NeoceraDeviceErrors() def _get_state_handlers(self): diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 5ffdc300..ca545009 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -72,11 +72,11 @@ def get_temperature_and_unit(self, sensor_number): """ return self._get_indexed_value_with_unit(self._device.temperatures, sensor_number) - def _get_indexed_value_with_unit(self, device_temperatures, item_number): + def _get_indexed_value_with_unit(self, device_values, item_number): """ Get a temperature like value back from device temperatures in the format produced by the device Args: - device_temperatures: device temperatures list + device_values: device value, e.g. temperatures list item_number: item to return Returns: temp and units; e.g. setpoint 1.2K @@ -84,15 +84,15 @@ def _get_indexed_value_with_unit(self, device_temperatures, item_number): """ try: sensor_index = int(item_number) - 1 - temperature_combined = device_temperatures[sensor_index] + device_value = device_values[sensor_index] # for temperatures it can not read - if temperature_combined is None: + if device_value is None: return " ------ " - temp, unit = temperature_combined + unit = self._device.units[sensor_index] - return "{0:8f}{1:1s}".format(temp, unit) + return "{0:8f}{1:1s}".format(device_value, unit) except (IndexError, ValueError, TypeError): print "Error: invalid sensor number requested '{0}'".format(item_number) @@ -124,8 +124,7 @@ def set_setpoint(self, set_point_number, value): sensor_number = int(set_point_number) - 1 setpoint = float(value) - temp, unit = self._device.setpoints[sensor_number] - self._device.setpoints[sensor_number] = (setpoint, unit) + self._device.setpoints[sensor_number] = setpoint except (IndexError, ValueError, TypeError): print "Error: invalid sensor number, '{0}', or setpoint value, '{1}'".format(set_point_number, value) self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 0533611d..cece650b 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -23,5 +23,11 @@ class ControlState(State): Temperature is being controller and monitored. The device will try to use the heater to make the temperature the same as the set point. """ - NAME = 'control' + + def in_state(self, dt): + device = self._context + for sensor_num in range(device.sensor_count): + temp = device.temperatures[sensor_num] + setpoint = device.setpoints[sensor_num] + device.temperatures[sensor_num] = approaches.linear(temp, setpoint, 0.1, dt) From 43ce5db3a184344a5ebe39bec3846d645b39618d Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:22:29 +0000 Subject: [PATCH 0121/1466] No power output if output mode off --- lewis_emulators/keithley_2400/interfaces/stream_interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index faf42532..62235dbc 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -1,4 +1,5 @@ from lewis.adapters.stream import StreamAdapter, Cmd +from ..control_modes import OutputMode class Keithley2400StreamInterface(StreamAdapter): @@ -49,7 +50,7 @@ def get_values(self): self._device.get_voltage(as_string=True), self._device.get_current(as_string=True), self._device.get_resistance(as_string=True) - ]) + ]) if self._device.get_output_mode() == OutputMode.ON else None def reset(self): """ Resets the device """ From fb9e28833ca0644474cdd92dd2c894c426932855 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:24:00 +0000 Subject: [PATCH 0122/1466] set up some additional mode responses from real device --- lewis_emulators/keithley_2400/device.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 790c0054..0747deb4 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -133,6 +133,8 @@ def get_resistance_mode(self): def set_remote_sensing_mode(self, mode): if SimulatedKeithley2400._check_mode(mode, RemoteSensingMode): self._remote_sensing_mode = mode + # Output switched off when remote sensing mode changed + self._output_mode = OutputMode.OFF def get_remote_sensing_mode(self): return self._remote_sensing_mode @@ -153,6 +155,8 @@ def get_source_mode(self): def set_resistance_range(self, value): self._resistance_range = value + # Resistance range mode set to manual when range set + self._resistance_range_mode = ResistanceRangeMode.MANUAL def get_resistance_range(self): return self._resistance_range From dd308bee3c0feefcd0f9cacd309348fa4396d07f Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:29:15 +0000 Subject: [PATCH 0123/1466] Restrict range of resistance values --- lewis_emulators/keithley_2400/device.py | 9 ++++++++- .../keithley_2400/interfaces/stream_interface.py | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 0747deb4..7f774893 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -154,7 +154,14 @@ def get_source_mode(self): return self._source_mode def set_resistance_range(self, value): - self._resistance_range = value + from math import pow + # Set the resistance range to the smallest value of 2.1En the requested + # value exceeds + self._resistance_range = 2.1 + for r in [2.1*pow(10,i) for i in range(1,8)]: + if value < r: + self._resistance_range = r/10 + break # Resistance range mode set to manual when range set self._resistance_range_mode = ResistanceRangeMode.MANUAL diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 62235dbc..1eee7fd8 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -19,7 +19,7 @@ class Keithley2400StreamInterface(StreamAdapter): Cmd("get_remote_sensing_mode", "^\:SYST:RSEN\?$"), Cmd("set_resistance_range_mode", "^\:SENS:RES:RANG:AUTO\s(0|1)$"), Cmd("get_resistance_range_mode", "^\:SENS:RES:RANG:AUTO\?$"), - Cmd("set_resistance_range", "^\:SENS:RES:RANG\s([2][0]*)$"), + Cmd("set_resistance_range", "^\:SENS:RES:RANG\s([-+]?[0-9]*\.?[0-9]+)$"), Cmd("get_resistance_range", "^\:SENS:RES:RANG\?$"), Cmd("set_source_mode", "^\:SOUR:FUNC\s(CURR|VOLT)$"), Cmd("get_source_mode", "^\:SOUR:FUNC\?$"), @@ -105,7 +105,7 @@ def get_resistance_range_mode(self): return self._device.get_resistance_range_mode() def set_resistance_range(self, value): - return self._device.set_resistance_range(int(value)) + return self._device.set_resistance_range(float(value)) def get_resistance_range(self): return self._device.get_resistance_range() From 7a594a9cc77375f741747978e43bb9fc3afec708 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:30:18 +0000 Subject: [PATCH 0124/1466] Initial output mode is off --- lewis_emulators/keithley_2400/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 7f774893..88e5db59 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -23,7 +23,7 @@ def _initialize_data(self): self._voltage = SimulatedKeithley2400.INITIAL_VOLTAGE # Modes - self._output_mode = OutputMode.ON + self._output_mode = OutputMode.OFF self._offset_compensation_mode = OffsetCompensationMode.OFF self._resistance_mode = ResistanceMode.AUTO self._remote_sensing_mode = RemoteSensingMode.OFF From 9adb26be45f6b4c0d4dffce965d48f06f60b6fd5 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:31:00 +0000 Subject: [PATCH 0125/1466] Initial resistance range is low --- lewis_emulators/keithley_2400/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index 88e5db59..c81c0b43 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -31,7 +31,7 @@ def _initialize_data(self): self._source_mode = SourceMode.CURRENT # Mode settings - self._resistance_range = 200000000 + self._resistance_range = 2.1 self._current_compliance = SimulatedKeithley2400.INITIAL_CURRENT_COMPLIANCE self._voltage_compliance = SimulatedKeithley2400.INITIAL_VOLTAGE_COMPLIANCE From 3257a7fdcf601b9c1e0512173184df9336ddaed1 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 23 Jan 2017 12:32:11 +0000 Subject: [PATCH 0126/1466] Get rid of magic number --- lewis_emulators/keithley_2400/device.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index c81c0b43..fcd94ddb 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -13,6 +13,7 @@ class SimulatedKeithley2400(StateMachineDevice): INITIAL_VOLTAGE = 10.0 INITIAL_VOLTAGE_COMPLIANCE = INITIAL_VOLTAGE MINIMUM_CURRENT = 1.0e-20 + RESISTANCE_RANGE_MULTIPLIER = 2.1 def _initialize_data(self): """ Initialize all of the device's attributes """ @@ -31,7 +32,7 @@ def _initialize_data(self): self._source_mode = SourceMode.CURRENT # Mode settings - self._resistance_range = 2.1 + self._resistance_range = SimulatedKeithley2400.RESISTANCE_RANGE_MULTIPLIER self._current_compliance = SimulatedKeithley2400.INITIAL_CURRENT_COMPLIANCE self._voltage_compliance = SimulatedKeithley2400.INITIAL_VOLTAGE_COMPLIANCE @@ -157,8 +158,8 @@ def set_resistance_range(self, value): from math import pow # Set the resistance range to the smallest value of 2.1En the requested # value exceeds - self._resistance_range = 2.1 - for r in [2.1*pow(10,i) for i in range(1,8)]: + self._resistance_range = SimulatedKeithley2400.RESISTANCE_RANGE_MULTIPLIER + for r in [SimulatedKeithley2400.RESISTANCE_RANGE_MULTIPLIER*pow(10, i) for i in range(1, 8)]: if value < r: self._resistance_range = r/10 break From 370b42ebc6520bc2d9d225d3b227a0ef3b152432 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 23 Jan 2017 14:08:59 +0000 Subject: [PATCH 0127/1466] Output configuration reading. Also connect sensor and output together instead of assuming 1->1 and 2->2 --- lewis_emulators/neocera_ltc21/device.py | 18 +++++-- .../interfaces/stream_interface.py | 47 +++++++++++++++---- lewis_emulators/neocera_ltc21/states.py | 10 ++-- 3 files changed, 59 insertions(+), 16 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index d8321fc3..18e57e03 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -22,18 +22,28 @@ def _initialize_data(self): # desired current state of the system self.current_state = self._get_initial_state() - # number of sensors + # number of sensors or outputs self.sensor_count = 2 - # temperature of the samples measure by sensor n + # temperature of the samples measure by sensor n (this index is different to the setpoints) self.temperatures = [0] * self.sensor_count - # display units + # display units (this is for sensor n and reading setpoint n) self.units = ["K"] * self.sensor_count - # the set points + # the set points (the setpoint in the units of the sensor connected to it) self.setpoints = [2] * self.sensor_count + # sensor source for the heater/analogue (initially sensor 1 is on heater output 1 and sensor 2 is on analogue) + # 3 is no connected + self.sensor_source = range(1, self.sensor_count + 1) + + # output control method {0 = AUTO P, 1 = AUTO PI, 2 = AUTO PID, 3 = PID, 4 = TABLE, 5 = DEFAULT, 6 = MONITOR} + self.control = [4] * self.sensor_count + + # heater range {0 = Off, 1 = 0.05W, 2=0.5W, 3=5W, 4=50W} + self.heater_range = 4 + # errors created within the device self.error = NeoceraDeviceErrors() diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index ca545009..65eca715 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -42,6 +42,7 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("get_temperature_and_unit", get_regex("QSAMP?", "\d")), Cmd("get_setpoint_and_unit", get_regex("QSETP?", "\d")), Cmd("set_setpoint", get_regex("SETP", "\d", "[+-]?\d+\.?\d+")), + Cmd("get_output_config", get_regex("QOUT?", "\d")), } in_terminator = ";" @@ -99,37 +100,67 @@ def _get_indexed_value_with_unit(self, device_values, item_number): self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) return "" - def get_setpoint_and_unit(self, set_point_number): + def get_setpoint_and_unit(self, output_number): """ Args: - set_point_number: the number of set point top return; 1=HEATER, 2=ANALOG + output_number: the number of set point top return; 1=HEATER, 2=ANALOG Returns: setpoint with unit """ - return self._get_indexed_value_with_unit(self._device.setpoints, set_point_number) + return self._get_indexed_value_with_unit(self._device.setpoints, output_number) - def set_setpoint(self, set_point_number, value): + def set_setpoint(self, output_number, value): """ Set the setpoint. Args: - set_point_number: set point number; 1=HEATER, 2=ANALOG + output_number: output number; 1=HEATER, 2=ANALOG value: value to set it to Returns: blank """ try: - sensor_number = int(set_point_number) - 1 + output_index = int(output_number) - 1 setpoint = float(value) - self._device.setpoints[sensor_number] = setpoint + self._device.setpoints[output_index] = setpoint except (IndexError, ValueError, TypeError): - print "Error: invalid sensor number, '{0}', or setpoint value, '{1}'".format(set_point_number, value) + print "Error: invalid output number, '{0}', or setpoint value, '{1}'".format(output_number, value) self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) return "" + def get_output_config(self, output_number): + """ + Reply to output configuration query. + # Example QOUT?1; produces -> 2;4;3; + # Example QOUT?2; produces -> 3;5; + + Args: + output_number: The output number being querries; 1 HEATER, 2 Analogue + + Returns: configuration as a string; sensor source;control;heater_range + + """ + + device = self._device + try: + output_index = int(output_number) - 1 + + output_config = "{sensor_source};{control}".format( + sensor_source=device.sensor_source[output_index], control=device.control[output_index]) + + if output_index == 1: + output_config += ";{heater_range}".format(heater_range=device.heater_range) + + return output_config + + except (IndexError, ValueError, TypeError): + print "Error: invalid output number, '{0}'".format(output_number) + device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + return "" + def handle_error(self, request, error): """ diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index cece650b..7d35800f 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -27,7 +27,9 @@ class ControlState(State): def in_state(self, dt): device = self._context - for sensor_num in range(device.sensor_count): - temp = device.temperatures[sensor_num] - setpoint = device.setpoints[sensor_num] - device.temperatures[sensor_num] = approaches.linear(temp, setpoint, 0.1, dt) + for setpoint_index in range(device.sensor_count): + sensor_source = device.sensor_source[setpoint_index] - 1 # sensor source is 1 indexed + if sensor_source != 3: + temp = device.temperatures[sensor_source] + setpoint = device.setpoints[setpoint_index] + device.temperatures[sensor_source] = approaches.linear(temp, setpoint, 0.1, dt) From 41d9a4192843ff04823db5aa5dffa40c2df7269f Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 23 Jan 2017 14:40:34 +0000 Subject: [PATCH 0128/1466] Add setting of control types for output --- lewis_emulators/neocera_ltc21/device.py | 11 +++- .../interfaces/stream_interface.py | 53 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 18e57e03..b205cf8a 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -11,6 +11,15 @@ class SimulatedNeocera(StateMachineDevice): Simulated Neocera LTG21 temperature controller """ + # Index in the arrays of the heater output + HEATER_INDEX = 0 + # Index in the arrays of the analog output + ANALOG_INDEX = 1 + # Minimum allowed output control type for the output index (see self.control) + CONTROL_TYPE_MIN = [0, 3] + # Maximum allowed output control type for the output index (see self.control) + CONTROL_TYPE_MAX = [5, 6] + def _initialize_data(self): """ @@ -38,7 +47,7 @@ def _initialize_data(self): # 3 is no connected self.sensor_source = range(1, self.sensor_count + 1) - # output control method {0 = AUTO P, 1 = AUTO PI, 2 = AUTO PID, 3 = PID, 4 = TABLE, 5 = DEFAULT, 6 = MONITOR} + # output control method {0 = AUTO P, 1 = AUTO PI, 2 = AUTO PID, 3 = PID, 4 = TABLE, 5 = DEFAULT, 6 = MONITOR (only analogue)} self.control = [4] * self.sensor_count # heater range {0 = Off, 1 = 0.05W, 2=0.5W, 3=5W, 4=50W} diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 65eca715..36f777d3 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -2,6 +2,7 @@ from lewis.adapters.stream import StreamAdapter, Cmd +from lewis_emulators.neocera_ltc21 import SimulatedNeocera from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState @@ -43,6 +44,8 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("get_setpoint_and_unit", get_regex("QSETP?", "\d")), Cmd("set_setpoint", get_regex("SETP", "\d", "[+-]?\d+\.?\d+")), Cmd("get_output_config", get_regex("QOUT?", "\d")), + Cmd("set_heater_control", get_regex("SHCONT", "\d")), + Cmd("set_analog_control", get_regex("SACONT", "\d")), } in_terminator = ";" @@ -151,7 +154,7 @@ def get_output_config(self, output_number): output_config = "{sensor_source};{control}".format( sensor_source=device.sensor_source[output_index], control=device.control[output_index]) - if output_index == 1: + if output_index == SimulatedNeocera.HEATER_INDEX: output_config += ";{heater_range}".format(heater_range=device.heater_range) return output_config @@ -161,6 +164,54 @@ def get_output_config(self, output_number): device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) return "" + def set_heater_control(self, control_type_number): + """ + Set the heater output control + Args: + control_type_number: control type to set the heater to + + Returns: None + + """ + + self._set_output_control(SimulatedNeocera.HEATER_INDEX, control_type_number) + + def set_analog_control(self, control_type_number): + """ + Set the analog output control + Args: + control_type_number: control type to set the heater to + + Returns: None + + """ + + self._set_output_control(SimulatedNeocera.ANALOG_INDEX, control_type_number) + + def _set_output_control(self, output_index, control_type_number): + """ + Set the output control for either the heater or the analog output + Args: + output_index: output index + control_type_number: control type to set + + Returns: None + + """ + device = self._device + try: + control_type = int(control_type_number) + + if control_type < SimulatedNeocera.CONTROL_TYPE_MIN[output_index] or \ + control_type > SimulatedNeocera.CONTROL_TYPE_MAX[output_index]: + raise ValueError("Bad control type number") + + self._device.control[output_index] = control_type + + except (IndexError, ValueError, TypeError): + print "Error: invalid control type number for output {output}, '{0}'".format( + control_type_number, output=output_index) + device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) def handle_error(self, request, error): """ From 3f26560a30b789794bf6635e020aa214cb937cd2 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 23 Jan 2017 15:04:40 +0000 Subject: [PATCH 0129/1466] Add heater output --- lewis_emulators/neocera_ltc21/constants.py | 15 ++++++++++++ lewis_emulators/neocera_ltc21/device.py | 14 ++++------- .../interfaces/stream_interface.py | 23 +++++++++++++------ lewis_emulators/neocera_ltc21/states.py | 23 ++++++++++++++++--- 4 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 lewis_emulators/neocera_ltc21/constants.py diff --git a/lewis_emulators/neocera_ltc21/constants.py b/lewis_emulators/neocera_ltc21/constants.py new file mode 100644 index 00000000..8d07a3a8 --- /dev/null +++ b/lewis_emulators/neocera_ltc21/constants.py @@ -0,0 +1,15 @@ +""" +Constants associated with the NEOCERA +""" + +# Index in the arrays of the heater output +HEATER_INDEX = 0 + +# Index in the arrays of the analog output +ANALOG_INDEX = 1 + +# Minimum allowed output control type for the output index (see self.control) +CONTROL_TYPE_MIN = [0, 3] + +# Maximum allowed output control type for the output index (see self.control) +CONTROL_TYPE_MAX = [5, 6] diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index b205cf8a..603ae7aa 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -11,15 +11,6 @@ class SimulatedNeocera(StateMachineDevice): Simulated Neocera LTG21 temperature controller """ - # Index in the arrays of the heater output - HEATER_INDEX = 0 - # Index in the arrays of the analog output - ANALOG_INDEX = 1 - # Minimum allowed output control type for the output index (see self.control) - CONTROL_TYPE_MIN = [0, 3] - # Maximum allowed output control type for the output index (see self.control) - CONTROL_TYPE_MAX = [5, 6] - def _initialize_data(self): """ @@ -47,12 +38,15 @@ def _initialize_data(self): # 3 is no connected self.sensor_source = range(1, self.sensor_count + 1) - # output control method {0 = AUTO P, 1 = AUTO PI, 2 = AUTO PID, 3 = PID, 4 = TABLE, 5 = DEFAULT, 6 = MONITOR (only analogue)} + # output control method {0 = AUTO P, 1 = AUTO PI, 2 = AUTO PID, 3 = PID, 4 = TABLE, 5 = DEFAULT, 6 = MONITOR} self.control = [4] * self.sensor_count # heater range {0 = Off, 1 = 0.05W, 2=0.5W, 3=5W, 4=50W} self.heater_range = 4 + # heater setting 0 - off 100.0 full scale + self.heater = 0 + # errors created within the device self.error = NeoceraDeviceErrors() diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 36f777d3..25d54c4e 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -2,7 +2,7 @@ from lewis.adapters.stream import StreamAdapter, Cmd -from lewis_emulators.neocera_ltc21 import SimulatedNeocera +from lewis_emulators.neocera_ltc21.constants import HEATER_INDEX, CONTROL_TYPE_MAX, CONTROL_TYPE_MIN, ANALOG_INDEX from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState @@ -42,10 +42,11 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("set_state_control", get_regex("SCONT")), Cmd("get_temperature_and_unit", get_regex("QSAMP?", "\d")), Cmd("get_setpoint_and_unit", get_regex("QSETP?", "\d")), - Cmd("set_setpoint", get_regex("SETP", "\d", "[+-]?\d+\.?\d+")), + Cmd("set_setpoint", get_regex("SETP", "\d", "[+-]?\d+\.?\d*")), Cmd("get_output_config", get_regex("QOUT?", "\d")), Cmd("set_heater_control", get_regex("SHCONT", "\d")), Cmd("set_analog_control", get_regex("SACONT", "\d")), + Cmd("get_heater", get_regex("QHEAT?")), } in_terminator = ";" @@ -154,7 +155,7 @@ def get_output_config(self, output_number): output_config = "{sensor_source};{control}".format( sensor_source=device.sensor_source[output_index], control=device.control[output_index]) - if output_index == SimulatedNeocera.HEATER_INDEX: + if output_index == HEATER_INDEX: output_config += ";{heater_range}".format(heater_range=device.heater_range) return output_config @@ -174,7 +175,7 @@ def set_heater_control(self, control_type_number): """ - self._set_output_control(SimulatedNeocera.HEATER_INDEX, control_type_number) + self._set_output_control(HEATER_INDEX, control_type_number) def set_analog_control(self, control_type_number): """ @@ -186,7 +187,7 @@ def set_analog_control(self, control_type_number): """ - self._set_output_control(SimulatedNeocera.ANALOG_INDEX, control_type_number) + self._set_output_control(ANALOG_INDEX, control_type_number) def _set_output_control(self, output_index, control_type_number): """ @@ -202,8 +203,8 @@ def _set_output_control(self, output_index, control_type_number): try: control_type = int(control_type_number) - if control_type < SimulatedNeocera.CONTROL_TYPE_MIN[output_index] or \ - control_type > SimulatedNeocera.CONTROL_TYPE_MAX[output_index]: + if control_type < CONTROL_TYPE_MIN[output_index] or \ + control_type > CONTROL_TYPE_MAX[output_index]: raise ValueError("Bad control type number") self._device.control[output_index] = control_type @@ -213,6 +214,14 @@ def _set_output_control(self, output_index, control_type_number): control_type_number, output=output_index) device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + def get_heater(self): + """ + + Returns: Heater output + + """ + return "{0:5.1f}".format(self._device.heater) + def handle_error(self, request, error): """ diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 7d35800f..c9b4df97 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -1,6 +1,8 @@ from lewis.core.statemachine import State from lewis.core import approaches +from constants import HEATER_INDEX + class OffState(State): """ @@ -17,6 +19,10 @@ class MonitorState(State): """ NAME = 'monitor' + def in_state(self, dt): + # heater is off because we are in monitor mode + self._context.heater = 0 + class ControlState(State): """ @@ -27,9 +33,20 @@ class ControlState(State): def in_state(self, dt): device = self._context - for setpoint_index in range(device.sensor_count): - sensor_source = device.sensor_source[setpoint_index] - 1 # sensor source is 1 indexed + for output_index in range(device.sensor_count): + sensor_source = device.sensor_source[output_index] - 1 # sensor source is 1 indexed if sensor_source != 3: temp = device.temperatures[sensor_source] - setpoint = device.setpoints[setpoint_index] + setpoint = device.setpoints[output_index] device.temperatures[sensor_source] = approaches.linear(temp, setpoint, 0.1, dt) + + heater_sensor_source = device.sensor_source[HEATER_INDEX] - 1 + if heater_sensor_source != 2: + # set heater between 0 and 100% proportional to diff in temp * 10 + temp = device.temperatures[heater_sensor_source] + setpoint = device.setpoints[HEATER_INDEX] + diff_in_temp = setpoint - temp + device.heater = max(0, min(diff_in_temp*10.0, 100)) + else: + # heater is no connected to a sensor so it is off + device.heater = 0 From 6d4a25238fa8ea7856cec6c043af69aacf81c5ef Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 23 Jan 2017 15:43:47 +0000 Subject: [PATCH 0130/1466] Add read pid config --- lewis_emulators/neocera_ltc21/device.py | 9 +++-- .../interfaces/stream_interface.py | 33 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 603ae7aa..fbd8f3dc 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -2,8 +2,9 @@ from lewis.devices import StateMachineDevice -from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors -from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState +from .constants import HEATER_INDEX, ANALOG_INDEX +from .device_errors import NeoceraDeviceErrors +from .states import MonitorState, ControlState class SimulatedNeocera(StateMachineDevice): @@ -47,6 +48,10 @@ def _initialize_data(self): # heater setting 0 - off 100.0 full scale self.heater = 0 + self.pid = [{}] * self.sensor_count + self.pid[HEATER_INDEX] = {"P": 10.0, "I": 11.0, "D": 12.0, "fixed_power": 13.0, "limit": 100.0} + self.pid[ANALOG_INDEX] = {"P": 10.0, "I": 11.0, "D": 12.0, "fixed_power": 13.0, "gain": 1.0, "offset": 2.0} + # errors created within the device self.error = NeoceraDeviceErrors() diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 25d54c4e..2f6c7023 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -47,6 +47,7 @@ class NeoceraStreamInterface(StreamAdapter): Cmd("set_heater_control", get_regex("SHCONT", "\d")), Cmd("set_analog_control", get_regex("SACONT", "\d")), Cmd("get_heater", get_regex("QHEAT?")), + Cmd("get_pid", get_regex("QPID?", "\d")), } in_terminator = ";" @@ -222,6 +223,38 @@ def get_heater(self): """ return "{0:5.1f}".format(self._device.heater) + def get_pid(self, output_number): + """ + Get the PID and other info of the output. Information is + P, I, D, fixed power settting, + for heater: power limit + for analog: gain and offset + + Exmaples: + QPID?1; -> 24.999;32.;8.;0.0;100.; + QPID?2; -> 99.999;10.;0.0;0.0;1.;0.0; + + Args: + output_number: output number; + + Returns: various info as a string + + """ + device = self._device + try: + output_index = int(output_number) - 1 + + pid_output = "{P};{I};{D};{fixed_power}".format(**device.pid[output_index]) + + if output_index == HEATER_INDEX: + return "{pid_output};{limit}".format(pid_output=pid_output, **device.pid[output_index]) + else: + return "{pid_output};{gain};{offset}".format(pid_output=pid_output, **device.pid[output_index]) + + except (IndexError, ValueError, TypeError): + print "Error: invalid output number, '{output}'".format(output=output_number) + device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + def handle_error(self, request, error): """ From 8e82528d620c98b229cd00eff4f4500abd1eda69 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 23 Jan 2017 17:04:27 +0000 Subject: [PATCH 0131/1466] PID output configuration --- .../interfaces/stream_interface.py | 185 +++++++++++++++--- lewis_emulators/neocera_ltc21/states.py | 2 +- 2 files changed, 157 insertions(+), 30 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 2f6c7023..499df124 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -7,28 +7,88 @@ from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState -def get_regex(command, *args): - +class CmdBuilder(object): + """ + Build a command for the stream adapter """ - Takes a command and optional arguments and turns then into a regex for lewis + def __init__(self, target_method, arg_sep=",", ignore=""): + """ + Create a builder. Use build to create the final objecy + Args: + target_method: name of the method target to call when the reg ex matches + arg_sep: seperators between the arguments + ignore: set of characters to ignore between text and arguments - Args: - command: the command to turn into a regex + Returns: - Returns: a regex for lewis + """ + self._target_method = target_method + self._arg_sep = arg_sep + self._current_sep = "" + self._ignore = "[{0}]*".format(ignore) + self._reg_ex = self._ignore - """ + def escape(self, text): + """ + Add some text to the regex which is esacped + Args: + text: text to add + + Returns: builder + + """ + self._reg_ex += re.escape(text) + self._ignore + return self + + def arg(self, arg_regex): + """ + Add an argument to the command + Args: + arg_regex: regex for the argument (capture group will be added) + + Returns: builder + + """ + self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore + self._current_sep = self._arg_sep + return self + + def float(self): + """ + Add a float argument + Returns: builder + + """ + return self.arg(r"[+-]?\d+\.?\d*") + + def digit(self): + """ + Add a single digit argument + Returns: builder + + """ + return self.arg(r"\d") + + def int(self): + """ + Add an integer argument + Returns: builder + + """ + return self.arg(r"\d*") - command = re.escape(command) - args_regex = "" - comma = "" - for arg in args: - args_regex += "{stripped}{comma}({arg})".format(arg=arg, stripped=r"[\r\n\s]*", comma=comma) - comma = "," + def build(self, *args, **kwargs): + """ + Builds the CMd object based on the target and regular expression + Args: + *args: arguments to pass to Cmd constructor + **kwargs: key word arguments to pass to Cmd constructor - return "{stripped}{command}{args_regex}{stripped}".format( - stripped=r"[\r\n\s]*", args_regex=args_regex, command=command) + Returns: Cmd object + + """ + return Cmd(self._target_method, self._reg_ex, *args, **kwargs) class NeoceraStreamInterface(StreamAdapter): @@ -37,17 +97,19 @@ class NeoceraStreamInterface(StreamAdapter): """ commands = { - Cmd("get_state", get_regex("QISTATE?")), - Cmd("set_state_monitor", get_regex("SMON")), - Cmd("set_state_control", get_regex("SCONT")), - Cmd("get_temperature_and_unit", get_regex("QSAMP?", "\d")), - Cmd("get_setpoint_and_unit", get_regex("QSETP?", "\d")), - Cmd("set_setpoint", get_regex("SETP", "\d", "[+-]?\d+\.?\d*")), - Cmd("get_output_config", get_regex("QOUT?", "\d")), - Cmd("set_heater_control", get_regex("SHCONT", "\d")), - Cmd("set_analog_control", get_regex("SACONT", "\d")), - Cmd("get_heater", get_regex("QHEAT?")), - Cmd("get_pid", get_regex("QPID?", "\d")), + CmdBuilder("get_state", arg_sep=",", ignore=r"\r\n\s").escape("QISTATE?").build(), + CmdBuilder("set_state_monitor", arg_sep=",", ignore=r"\r\n\s").escape("SMON").build(), + CmdBuilder("set_state_control", arg_sep=",", ignore=r"\r\n\s").escape("SCONT").build(), + CmdBuilder("get_temperature_and_unit", arg_sep=",", ignore=r"\r\n\s").escape("QSAMP?").digit().build(), + CmdBuilder("get_setpoint_and_unit", arg_sep=",", ignore=r"\r\n\s").escape("QSETP?").digit().build(), + CmdBuilder("set_setpoint", arg_sep=",", ignore=r"\r\n\s").escape("SETP").digit().float().build(), + CmdBuilder("get_output_config", arg_sep=",", ignore=r"\r\n\s").escape("QOUT?").digit().build(), + CmdBuilder("set_heater_control", arg_sep=",", ignore=r"\r\n\s").escape("SHCONT").digit().build(), + CmdBuilder("set_analog_control", arg_sep=",", ignore=r"\r\n\s").escape("SACONT").digit().build(), + CmdBuilder("get_heater", arg_sep=",", ignore=r"\r\n\s").escape("QHEAT?").build(), + CmdBuilder("get_pid", arg_sep=",", ignore=r"\r\n\s").escape("QPID?").digit().build(), + CmdBuilder("set_pid_heater", arg_sep=",", ignore=r"\r\n\s").escape("SPID1,").int().int().int().float().float().build(), + CmdBuilder("set_pid_analog", arg_sep=",", ignore=r"\r\n\s").escape("SPID2,").int().int().int().float().float().float().build() } in_terminator = ";" @@ -244,17 +306,82 @@ def get_pid(self, output_number): try: output_index = int(output_number) - 1 - pid_output = "{P};{I};{D};{fixed_power}".format(**device.pid[output_index]) + pid_output = "{P:f};{I:f};{D:f};{fixed_power:f}".format(**device.pid[output_index]) if output_index == HEATER_INDEX: - return "{pid_output};{limit}".format(pid_output=pid_output, **device.pid[output_index]) + return "{pid_output};{limit:f}".format(pid_output=pid_output, **device.pid[output_index]) else: - return "{pid_output};{gain};{offset}".format(pid_output=pid_output, **device.pid[output_index]) + return "{pid_output};{gain:f};{offset:f}".format(pid_output=pid_output, **device.pid[output_index]) except (IndexError, ValueError, TypeError): print "Error: invalid output number, '{output}'".format(output=output_number) device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + def set_pid_heater(self, p, i, d, fixed_power, limit): + """ + Set the pid settings for the heater + Args: + p: p + i: i + d: d + fixed_power: fixed power + limit: limit of the heater + + Returns: None + + """ + pid_settings = self._device.pid[HEATER_INDEX] + try: + self._set_pid(p, i, d, fixed_power, pid_settings) + pid_settings["limit"] = float(limit) + + except (IndexError, ValueError, TypeError): + print "Error: in pid settings for heater" + self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + + def set_pid_analog(self, p, i, d, fixed_power, gain, offset): + """ + Set the pid settings for the analog output + Args: + p: p + i: i + d: d + fixed_power: fixed power + gain: gain of the output + offset: offset for the output + + Returns: None + + """ + pid_settings = self._device.pid[ANALOG_INDEX] + try: + self._set_pid(p, i, d, fixed_power, pid_settings) + pid_settings["gain"] = float(gain) + pid_settings["offset"] = float(offset) + + except (IndexError, ValueError, TypeError): + print "Error: in pid settings for analog" + self._device.error = NeoceraDeviceErrors(NeoceraDeviceErrors.BAD_PARAMETER) + + def _set_pid(self, p, i, d, fixed_power, pid_settings): + """ + Common function to set p,i,d and power + Args: + Args: + p: p + i: i + d: d + fixed_power: fixed power + pid_settings: in which to set them + + Returns: None + + """ + pid_settings["P"] = int(p) + pid_settings["I"] = int(i) + pid_settings["D"] = int(d) + pid_settings["fixed_power"] = float(fixed_power) + def handle_error(self, request, error): """ diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index c9b4df97..0adf2f02 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -41,7 +41,7 @@ def in_state(self, dt): device.temperatures[sensor_source] = approaches.linear(temp, setpoint, 0.1, dt) heater_sensor_source = device.sensor_source[HEATER_INDEX] - 1 - if heater_sensor_source != 2: + if heater_sensor_source != 2: # set heater between 0 and 100% proportional to diff in temp * 10 temp = device.temperatures[heater_sensor_source] setpoint = device.setpoints[HEATER_INDEX] From bc1ee499732c35478186b77e01a4d5dfa6c889e6 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Fri, 27 Jan 2017 13:45:57 +0000 Subject: [PATCH 0132/1466] Added emulator --- lewis_emulators/CCD100/__init__.py | 8 +++ lewis_emulators/CCD100/device.py | 10 ++++ lewis_emulators/CCD100/interfaces/__init__.py | 3 ++ .../CCD100/interfaces/stream_interface.py | 53 +++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 lewis_emulators/CCD100/__init__.py create mode 100644 lewis_emulators/CCD100/device.py create mode 100644 lewis_emulators/CCD100/interfaces/__init__.py create mode 100644 lewis_emulators/CCD100/interfaces/stream_interface.py diff --git a/lewis_emulators/CCD100/__init__.py b/lewis_emulators/CCD100/__init__.py new file mode 100644 index 00000000..f3f88c34 --- /dev/null +++ b/lewis_emulators/CCD100/__init__.py @@ -0,0 +1,8 @@ +from .device import SimulatedCCD100 + +__all__ = ['SimulatedCCD100'] + + + + + diff --git a/lewis_emulators/CCD100/device.py b/lewis_emulators/CCD100/device.py new file mode 100644 index 00000000..6c41a2a2 --- /dev/null +++ b/lewis_emulators/CCD100/device.py @@ -0,0 +1,10 @@ +from lewis.devices import Device + + +class SimulatedCCD100(Device): + address = "a" + setpoint = 0.00 + units = "" + setpoint_mode = 1 + + diff --git a/lewis_emulators/CCD100/interfaces/__init__.py b/lewis_emulators/CCD100/interfaces/__init__.py new file mode 100644 index 00000000..e5db7365 --- /dev/null +++ b/lewis_emulators/CCD100/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import CCD100StreamInterface + +__all__ = ['CCD100StreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/CCD100/interfaces/stream_interface.py b/lewis_emulators/CCD100/interfaces/stream_interface.py new file mode 100644 index 00000000..1bdbadf3 --- /dev/null +++ b/lewis_emulators/CCD100/interfaces/stream_interface.py @@ -0,0 +1,53 @@ +from lewis.adapters.stream import StreamAdapter, Cmd +import random + +SP_COMM = "spv" +UNITS_COMM = "uiu" +READING_COMM = "r" + + +class CCD100StreamInterface(StreamAdapter): + + commands = { + Cmd("get_sp", "^[a-h]" + SP_COMM + "\?$"), + Cmd("set_sp", "^[a-h]" + SP_COMM + " ([\-0-9.]+)$", argument_mappings=[float]), + Cmd("get_units", "^[a-h]" + UNITS_COMM + "\?$"), + Cmd("set_units", "^[a-h]" + UNITS_COMM + " ([a-z]+)$"), + Cmd("get_reading", "^[a-h]" + READING_COMM + "$"), + } + + in_terminator = "\r\n" + out_terminator = "\r\r\n" + + out_echo = "*{}*:{};{}" + out_response = "!{}!o!" + + def create_response(self, command, params=" ", data=None): + out = self.out_echo.format(self._device.address, command, params) + self.out_terminator + if data: + out += data + self.out_terminator + out += self.out_response.format(self._device.address) + return out + + def get_sp(self): + return self.create_response(SP_COMM + "?", data="SP VALUE: " + str(self._device.setpoint) + " ") + + def set_sp(self, new_sp): + self._device.setpoint = new_sp + return self.create_response(SP_COMM) + + def get_units(self): + return self.create_response(UNITS_COMM + "?", data="INPUT UNITS STR: " + str(self._device.units)) + + def set_units(self, new_units): + self._device.units = new_units + return self.create_response(UNITS_COMM) + + def get_reading(self): + rand = random.random() * 10.0 + data_str = "READ: {:0.3f}".format(rand) + ";" + str(self._device.setpoint_mode) + return self.create_response(READING_COMM + " ", data=data_str) + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + From 3025ee5ccaf8af295ffa76869fac853b83d0e0e1 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 30 Jan 2017 17:23:59 +0000 Subject: [PATCH 0133/1466] Add new lines --- lewis_emulators/keithley_2400/control_modes.py | 2 +- lewis_emulators/keithley_2400/interfaces/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/keithley_2400/control_modes.py b/lewis_emulators/keithley_2400/control_modes.py index 3778ddc3..254ecd7e 100644 --- a/lewis_emulators/keithley_2400/control_modes.py +++ b/lewis_emulators/keithley_2400/control_modes.py @@ -45,4 +45,4 @@ class OffsetCompensationMode(OnOffMode): class OutputMode(OnOffMode): - pass \ No newline at end of file + pass diff --git a/lewis_emulators/keithley_2400/interfaces/__init__.py b/lewis_emulators/keithley_2400/interfaces/__init__.py index a38f0cc6..33de16dd 100644 --- a/lewis_emulators/keithley_2400/interfaces/__init__.py +++ b/lewis_emulators/keithley_2400/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import Keithley2400StreamInterface -__all__ = ['Keithley2400StreamInterface'] \ No newline at end of file +__all__ = ['Keithley2400StreamInterface'] From cc1d3d036dc4bd19c1e8f9c35f70736457ddb1fd Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 31 Jan 2017 10:31:59 +0000 Subject: [PATCH 0134/1466] corrections --- lewis_emulators/neocera_ltc21/device.py | 2 +- .../neocera_ltc21/interfaces/stream_interface.py | 14 +++++++------- lewis_emulators/neocera_ltc21/states.py | 13 ++++++++----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index fbd8f3dc..4dff01ee 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -53,7 +53,7 @@ def _initialize_data(self): self.pid[ANALOG_INDEX] = {"P": 10.0, "I": 11.0, "D": 12.0, "fixed_power": 13.0, "gain": 1.0, "offset": 2.0} # errors created within the device - self.error = NeoceraDeviceErrors() + self._error = NeoceraDeviceErrors() def _get_state_handlers(self): diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 499df124..8ecb7d48 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -76,7 +76,7 @@ def int(self): Returns: builder """ - return self.arg(r"\d*") + return self.arg(r"\d+") def build(self, *args, **kwargs): """ @@ -108,8 +108,8 @@ class NeoceraStreamInterface(StreamAdapter): CmdBuilder("set_analog_control", arg_sep=",", ignore=r"\r\n\s").escape("SACONT").digit().build(), CmdBuilder("get_heater", arg_sep=",", ignore=r"\r\n\s").escape("QHEAT?").build(), CmdBuilder("get_pid", arg_sep=",", ignore=r"\r\n\s").escape("QPID?").digit().build(), - CmdBuilder("set_pid_heater", arg_sep=",", ignore=r"\r\n\s").escape("SPID1,").int().int().int().float().float().build(), - CmdBuilder("set_pid_analog", arg_sep=",", ignore=r"\r\n\s").escape("SPID2,").int().int().int().float().float().float().build() + CmdBuilder("set_pid_heater", arg_sep=",", ignore=r"\r\n\s").escape("SPID1,").float().float().float().float().float().build(), + CmdBuilder("set_pid_analog", arg_sep=",", ignore=r"\r\n\s").escape("SPID2,").float().float().float().float().float().float().build() } in_terminator = ";" @@ -205,7 +205,7 @@ def get_output_config(self, output_number): # Example QOUT?2; produces -> 3;5; Args: - output_number: The output number being querries; 1 HEATER, 2 Analogue + output_number: The output number being queries; 1 HEATER, 2 Analogue Returns: configuration as a string; sensor source;control;heater_range @@ -377,9 +377,9 @@ def _set_pid(self, p, i, d, fixed_power, pid_settings): Returns: None """ - pid_settings["P"] = int(p) - pid_settings["I"] = int(i) - pid_settings["D"] = int(d) + pid_settings["P"] = float(p) + pid_settings["I"] = float(i) + pid_settings["D"] = float(d) pid_settings["fixed_power"] = float(fixed_power) def handle_error(self, request, error): diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 0adf2f02..62d67767 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -35,18 +35,21 @@ def in_state(self, dt): device = self._context for output_index in range(device.sensor_count): sensor_source = device.sensor_source[output_index] - 1 # sensor source is 1 indexed - if sensor_source != 3: + try: temp = device.temperatures[sensor_source] setpoint = device.setpoints[output_index] device.temperatures[sensor_source] = approaches.linear(temp, setpoint, 0.1, dt) + except IndexError: + # sensor source is out of range (probably 3) + pass - heater_sensor_source = device.sensor_source[HEATER_INDEX] - 1 - if heater_sensor_source != 2: + try: + heater_sensor_source = device.sensor_source[HEATER_INDEX] - 1 # set heater between 0 and 100% proportional to diff in temp * 10 temp = device.temperatures[heater_sensor_source] setpoint = device.setpoints[HEATER_INDEX] diff_in_temp = setpoint - temp device.heater = max(0, min(diff_in_temp*10.0, 100)) - else: - # heater is no connected to a sensor so it is off + except IndexError: + # heater is not connected to a sensor so it is off device.heater = 0 From 2e4885c34a0b0b59236b37c04626813098003500 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 6 Feb 2017 15:36:20 +0000 Subject: [PATCH 0135/1466] Fix typo --- lewis_emulators/neocera_ltc21/states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 62d67767..2639f88c 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -26,7 +26,7 @@ def in_state(self, dt): class ControlState(State): """ - Temperature is being controller and monitored. The device will try to use the heater to make + Temperature is being controlled and monitored. The device will try to use the heater to make the temperature the same as the set point. """ NAME = 'control' From 3d0d7e4ec7b439ee5aa7a4b6d7ee6a3c91164356 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 7 Feb 2017 17:14:19 +0000 Subject: [PATCH 0136/1466] Limit heater range on set and on use --- lewis_emulators/neocera_ltc21/interfaces/stream_interface.py | 5 ++++- lewis_emulators/neocera_ltc21/states.py | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 8ecb7d48..c216b304 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -333,7 +333,10 @@ def set_pid_heater(self, p, i, d, fixed_power, limit): pid_settings = self._device.pid[HEATER_INDEX] try: self._set_pid(p, i, d, fixed_power, pid_settings) - pid_settings["limit"] = float(limit) + limit_as_float = float(limit) + if limit_as_float < 0.0 or limit_as_float > 100.0: + raise ValueError("Outside allowed heater range") + pid_settings["limit"] = limit_as_float except (IndexError, ValueError, TypeError): print "Error: in pid settings for heater" diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 62d67767..94250fa2 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -49,7 +49,8 @@ def in_state(self, dt): temp = device.temperatures[heater_sensor_source] setpoint = device.setpoints[HEATER_INDEX] diff_in_temp = setpoint - temp - device.heater = max(0, min(diff_in_temp*10.0, 100)) + heater_limit = device.pid[HEATER_INDEX]["limit"] + device.heater = max(0, min(diff_in_temp * 10.0, heater_limit)) except IndexError: # heater is not connected to a sensor so it is off device.heater = 0 From c27e747126c6fa96ea6718517b410715851bf0a5 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 16 Mar 2017 16:26:07 +0000 Subject: [PATCH 0137/1466] Added script to translate between COM port and TCP connection --- com2tcp/com2tcp.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 com2tcp/com2tcp.py diff --git a/com2tcp/com2tcp.py b/com2tcp/com2tcp.py new file mode 100644 index 00000000..f59346fd --- /dev/null +++ b/com2tcp/com2tcp.py @@ -0,0 +1,41 @@ +import serial +import time +import socket +import threading +import argparse + + +def listen_to_tcp(tcp_conn, ser_conn): + while 1: + tcp_data = tcp_conn.recv(1024) + if len(data) > 0: + ser_conn.writelines(tcp_data) + print "Data on tcp: " + str(tcp_data) + time.sleep(0.1) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Transfers the data being sent to a COM port to be sent to a TCP port.") + parser.add_argument("tcp_port", help="The port to send TCP messages to. (e.g. 57677)", type=int) + parser.add_argument("com_port", help="The COM port to send serial messages to. (e.g. COM2)") + parser.add_argument('-b', "--baud", help="The baud rate to communicate on the COM port.", default=9600) + + args = parser.parse_args() + + tcp = socket.create_connection(('localhost', args.tcp_port)) + ser = serial.Serial(args.com_port, args.baud) + + listen_thread = threading.Thread(target=listen_to_tcp, args=(tcp, ser)) + listen_thread.start() + + print "Listening on " + args.com_port + " and localhost:" + args.tcp_port + print "Press Ctrl+C+Break to stop" + + while True: + if ser.inWaiting(): + data = ser.readline() + print "Data on serial: " + data + tcp.sendall(data) + + time.sleep(0.1) + From f34188ce22805ac82a0c9f11ba32370c834bd9d7 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 16 Mar 2017 18:19:32 +0000 Subject: [PATCH 0138/1466] Fixed bug --- com2tcp/com2tcp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com2tcp/com2tcp.py b/com2tcp/com2tcp.py index f59346fd..8d50731b 100644 --- a/com2tcp/com2tcp.py +++ b/com2tcp/com2tcp.py @@ -28,7 +28,7 @@ def listen_to_tcp(tcp_conn, ser_conn): listen_thread = threading.Thread(target=listen_to_tcp, args=(tcp, ser)) listen_thread.start() - print "Listening on " + args.com_port + " and localhost:" + args.tcp_port + print "Listening on " + str(args.com_port) + " and localhost:" + str(args.tcp_port) print "Press Ctrl+C+Break to stop" while True: From 4666fa04f0c7a83a1069df168a4bd3f452380f16 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 16 Mar 2017 18:29:23 +0000 Subject: [PATCH 0139/1466] Don't need to read a whole line --- com2tcp/com2tcp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com2tcp/com2tcp.py b/com2tcp/com2tcp.py index 8d50731b..974ff665 100644 --- a/com2tcp/com2tcp.py +++ b/com2tcp/com2tcp.py @@ -33,7 +33,7 @@ def listen_to_tcp(tcp_conn, ser_conn): while True: if ser.inWaiting(): - data = ser.readline() + data = ser.read() print "Data on serial: " + data tcp.sendall(data) From 1c0e6c7a59540e18db447ec52bc6dab924af9d3f Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 21 Mar 2017 15:00:33 +0000 Subject: [PATCH 0140/1466] Added basic (non-stateful) emulator --- .../HRPD_Sample_changer/__init__.py | 3 + lewis_emulators/HRPD_Sample_changer/device.py | 64 +++++++++++++++++ .../interfaces/__init__.py | 3 + .../interfaces/stream_interface.py | 68 +++++++++++++++++++ 4 files changed, 138 insertions(+) create mode 100644 lewis_emulators/HRPD_Sample_changer/__init__.py create mode 100644 lewis_emulators/HRPD_Sample_changer/device.py create mode 100644 lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py create mode 100644 lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py diff --git a/lewis_emulators/HRPD_Sample_changer/__init__.py b/lewis_emulators/HRPD_Sample_changer/__init__.py new file mode 100644 index 00000000..fad472bc --- /dev/null +++ b/lewis_emulators/HRPD_Sample_changer/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedHRPDSampleChanger + +__all__ = ['SimulatedHRPDSampleChanger'] diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/HRPD_Sample_changer/device.py new file mode 100644 index 00000000..fd076f1c --- /dev/null +++ b/lewis_emulators/HRPD_Sample_changer/device.py @@ -0,0 +1,64 @@ +from lewis.devices import Device + + +class SimulatedHRPDSampleChanger(Device): + NO_ERR = 0 + ERR_INV_DEST = 5 + ERR_ARM_DROPPED = 7 + ERR_ARM_UP = 8 + ERR_CANT_ROT_IF_NOT_UP = 10 + + MIN_CAROUSEL = 1 + MAX_CAROUSEL = 20 + + carousel_position = MIN_CAROUSEL + arm_lowered = False + current_err = NO_ERR + + def get_status(self): + # Based on the labview VI, appears to be different than doc + return_string = "00000{0:b}01{1:b}{2:b}{3:b}00000 000" + car_at_one = (self.carousel_position == self.MIN_CAROUSEL) + return_string = return_string.format(not self.arm_lowered, car_at_one, not self.arm_lowered, self.arm_lowered) + return_string += " %02d" % self.current_err + return_string += " %02d" % self.carousel_position + return return_string + + def go_forward(self): + if self.arm_lowered: + return self.ERR_CANT_ROT_IF_NOT_UP + self._device.carousel_position += 1 + if self._device.carousel_position > self.MAX_CAROUSEL: + self._device.carousel_position = self.MIN_CAROUSEL + return self.NO_ERR + + def go_backward(self): + if self.arm_lowered: + return self.ERR_CANT_ROT_IF_NOT_UP + self._device.carousel_position -= 1 + if self._device.carousel_position < self.MIN_CAROUSEL: + self._device.carousel_position = self.MAX_CAROUSEL + return self.NO_ERR + + def move_to(self, position, lower_arm): + if (position < self.MIN_CAROUSEL) or (position > self.MAX_CAROUSEL): + return self.ERR_INV_DEST + else: + self.carousel_position = position + self.arm_lowered = lower_arm + return self.NO_ERR + + def set_arm(self, lowered): + if lowered != self.arm_lowered: + if lowered: + return self.ERR_ARM_DROPPED + else: + return self.ERR_ARM_UP + self.arm_lowered = lowered + return self.NO_ERR + + def init(self): + self.arm_lowered = False + self.carousel_position = self.MIN_CAROUSEL + self.current_err = self.NO_ERR + return self.NO_ERR diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py b/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py new file mode 100644 index 00000000..7bd11ab5 --- /dev/null +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import HRPDSampleChangerStreamInterface + +__all__ = ['HRPDSampleChangerStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py new file mode 100644 index 00000000..03a87323 --- /dev/null +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py @@ -0,0 +1,68 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + + +class HRPDSampleChangerStreamInterface(StreamAdapter): + + commands = { + Cmd("get_id", "^id$"), + Cmd("get_position", "^po$"), + Cmd("get_status", "^st$"), + Cmd("go_back", "^bk$"), + Cmd("go_fwd", "^fw$"), + Cmd("halt", "^ht$"), + Cmd("initialise", "^in$"), + Cmd("lower_arm", "^lo$"), + Cmd("move_to", "^ma([0-9]{2})$", argument_mappings=[int]), + Cmd("move_to_without_lowering", "^mn([0-9]{2})$", argument_mappings=[int]), + Cmd("raise_arm", "^ra$"), + Cmd("read_variable", "^vr([0-9]{4})$", argument_mappings=[int]) + } + + in_terminator = "\r" + out_terminator = "\r\n" + + def _check_error_code(self, code): + if code == self._device.NO_ERR: + return "ok" + else: + return "rf-%02d" % code + + def get_id(self): + return "0001 0001 ISIS HRPD Sample Changer V1.00" + + def get_position(self): + return "Position = " + str(self._device.carousel_position) + + def get_status(self): + return str(self._device.get_status()) + + def go_back(self): + return self._check_error_code(self._device.go_backward()) + + def go_fwd(self): + return self._check_error_code(self._device.go_forward()) + + def read_variable(self, variable): + return "READ VARIABLE " + str(variable) + + def halt(self): + return "ok" + + def initialise(self): + return self._check_error_code(self._device.init()) + + def move_to(self, position): + return self._check_error_code(self._device.move_to(position, True)) + + def move_to_without_lowering(self, position): + return self._check_error_code(self._device.move_to(position, False)) + + def lower_arm(self): + return self._check_error_code(self._device.set_arm(True)) + + def raise_arm(self): + return self._check_error_code(self._device.set_arm(False)) + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + From b8aab28cbe08b0708ca84ed18785779a08642d22 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 29 Mar 2017 16:02:12 +0100 Subject: [PATCH 0141/1466] MK2 chopper preliminary work as of session on 29/3 --- __init__.py | 0 lewis_emulators/mk2_chopper/__init__.py | 3 ++ lewis_emulators/mk2_chopper/device.py | 31 +++++++++++++++++++ .../mk2_chopper/interfaces/__init__.py | 3 ++ .../interfaces/stream_interface.py | 19 ++++++++++++ lewis_emulators/mk2_chopper/states.py | 10 ++++++ 6 files changed, 66 insertions(+) create mode 100644 __init__.py create mode 100644 lewis_emulators/mk2_chopper/__init__.py create mode 100644 lewis_emulators/mk2_chopper/device.py create mode 100644 lewis_emulators/mk2_chopper/interfaces/__init__.py create mode 100644 lewis_emulators/mk2_chopper/interfaces/stream_interface.py create mode 100644 lewis_emulators/mk2_chopper/states.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lewis_emulators/mk2_chopper/__init__.py b/lewis_emulators/mk2_chopper/__init__.py new file mode 100644 index 00000000..37fe266c --- /dev/null +++ b/lewis_emulators/mk2_chopper/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedMk2Chopper + +__all__ = ['SimulatedMk2Chopper2400'] diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py new file mode 100644 index 00000000..88f87bba --- /dev/null +++ b/lewis_emulators/mk2_chopper/device.py @@ -0,0 +1,31 @@ +from collections import OrderedDict +from states import DefaultInitState, DefaultRunningState +from lewis.devices import StateMachineDevice + + +class SimulatedMk2Chopper(StateMachineDevice): + + def _initialize_data(self): + """ Initialize all of the device's attributes """ + self.serial_command_mode = True + self.demanded_frequency = 50 + + def _get_state_handlers(self): + return { + 'init': DefaultInitState(), + 'running': DefaultRunningState(), + } + + def _get_initial_state(self): + return 'init' + + def _get_transition_handlers(self): + return OrderedDict([ + (('init', 'running'), lambda: self.serial_command_mode), + ]) + + def update(self, dt): + pass + + def get_demanded_frequency(self): + return self.demanded_frequency diff --git a/lewis_emulators/mk2_chopper/interfaces/__init__.py b/lewis_emulators/mk2_chopper/interfaces/__init__.py new file mode 100644 index 00000000..a4e1a1c3 --- /dev/null +++ b/lewis_emulators/mk2_chopper/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Mk2ChopperStreamInterface + +__all__ = ['Mk2ChopperStreamInterface'] diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py new file mode 100644 index 00000000..91326e1a --- /dev/null +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -0,0 +1,19 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + + +class Mk2ChopperStreamInterface(StreamAdapter): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("get_demanded_frequency", "^RG$"), + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + return str(error) + + def get_demanded_frequency(self): + return self._device.get_demanded_frequency() \ No newline at end of file diff --git a/lewis_emulators/mk2_chopper/states.py b/lewis_emulators/mk2_chopper/states.py new file mode 100644 index 00000000..7b9c6af7 --- /dev/null +++ b/lewis_emulators/mk2_chopper/states.py @@ -0,0 +1,10 @@ +from lewis.core.statemachine import State + + +class DefaultInitState(State): + pass + + +class DefaultRunningState(State): + def in_state(self, dt): + self._context.update(dt) From 550b91fbd26a6d777368bde394b6ee1014628391 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 30 Mar 2017 16:46:14 +0100 Subject: [PATCH 0142/1466] Add interface and basic frequency logic --- lewis_emulators/mk2_chopper/chopper_type.py | 14 ++++ lewis_emulators/mk2_chopper/device.py | 80 ++++++++++++++++--- .../interfaces/stream_interface.py | 60 +++++++++++++- lewis_emulators/mk2_chopper/states.py | 20 ++++- 4 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 lewis_emulators/mk2_chopper/chopper_type.py diff --git a/lewis_emulators/mk2_chopper/chopper_type.py b/lewis_emulators/mk2_chopper/chopper_type.py new file mode 100644 index 00000000..90e8bc36 --- /dev/null +++ b/lewis_emulators/mk2_chopper/chopper_type.py @@ -0,0 +1,14 @@ +class ChopperType(object): + + FREQUENCY_TO_MAX_PHASE_CONVERSIONS = {5: 99995, 10: 99995, 12.5: 79995, 16.67: 59995, + 25: 39995, 50: 19995, 100: 9995} + """ + A type of chopper that the system can represent. Typically MK2 choppers come in 50Hz and 100Hz varieties. + + Valid states are given as tuples with the first value being the frequency, the second value being the maximum + phase delay. + """ + def __init__(self, system_frequency, valid_frequencies): + self.system_frequency = system_frequency + self.valid_states = [(f, ChopperType.FREQUENCY_TO_MAX_PHASE_CONVERSIONS[f]) for f in valid_frequencies + if f in ChopperType.FREQUENCY_TO_MAX_PHASE_CONVERSIONS.keys()] diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index 88f87bba..99188d08 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -1,19 +1,37 @@ from collections import OrderedDict -from states import DefaultInitState, DefaultRunningState +from states import DefaultInitState, DefaultStoppedState, DefaultStartedState from lewis.devices import StateMachineDevice +from chopper_type import ChopperType class SimulatedMk2Chopper(StateMachineDevice): + # Dictionary of valid (frequencies, max phase delay) for each chopper type: 50Hz/100Hz + CHOPPER_TYPES = [ChopperType(50, [5, 10, 12.5, 16.67, 25, 50]), ChopperType(100, [12.5, 25, 50, 100])] + def _initialize_data(self): """ Initialize all of the device's attributes """ - self.serial_command_mode = True - self.demanded_frequency = 50 + self._type = SimulatedMk2Chopper.CHOPPER_TYPES[0] + + self._demanded_frequency, self._max_phase_delay = self._type.valid_states[-1] + self._true_frequency = 0 + + self._demanded_phase_delay = 0 + self._true_phase_delay = 0 + + self._demanded_phase_error_window = 0 + self._true_phase_error = 0 + + self._started = False + + # When initialisation is complete, this is set to true and the device will enter a running state + self.ready = True def _get_state_handlers(self): return { 'init': DefaultInitState(), - 'running': DefaultRunningState(), + 'stopped': DefaultStoppedState(), + 'started': DefaultStartedState(), } def _get_initial_state(self): @@ -21,11 +39,55 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict([ - (('init', 'running'), lambda: self.serial_command_mode), + (('init', 'stopped'), lambda: self.ready), + (('stopped', 'started'), lambda: self._started is True), + (('started', 'stopped'), lambda: self._started is False), ]) - def update(self, dt): - pass - def get_demanded_frequency(self): - return self.demanded_frequency + return self._demanded_frequency + + def get_true_frequency(self): + return self._true_frequency + + def get_demanded_phase_delay(self): + return self._demanded_phase_delay + + def get_true_phase_delay(self): + return self._true_phase_delay + + def get_demanded_phase_error_window(self): + return self._demanded_phase_error_window + + def get_true_phase_error(self): + return self._true_phase_error + + def set_demanded_frequency(self, new_frequency_int): + try: + self._demanded_frequency, self._max_phase_delay = next((f, p) for f, p in self._type.valid_states if + int(f)==int(new_frequency_int)) + except StopIteration: + # No value found, do nothing + pass + + def set_demanded_phase_delay(self, new_phase_delay): + self._demanded_phase_delay = min(new_phase_delay, self._max_phase_delay) + + def set_true_frequency(self, new_frequency): + self._true_frequency = new_frequency + + def set_chopper_type(self, frequency): + try: + self._type = next(ct for ct in SimulatedMk2Chopper.CHOPPER_TYPES if ct.system_frequency == frequency) + except StopIteration: + # The new type wasn't valid, do nothing + pass + else: + # Make sure the demanded frequency is a valid frequency for the new type + self.set_frequency(min(self._type.valid_frequencies, key=lambda x:abs(x - self._demanded_frequency))) + + def start(self): + self._started = True + + def stop(self): + self._started = False \ No newline at end of file diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index 91326e1a..2383df17 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -5,7 +5,16 @@ class Mk2ChopperStreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation commands = { + Cmd("get_true_frequency", "^RF$"), Cmd("get_demanded_frequency", "^RG$"), + Cmd("get_true_phase_delay", "^RP$"), + Cmd("get_demanded_phase_delay", "^RQ$"), + Cmd("get_true_phase_error", "^RE$"), + Cmd("get_demanded_phase_error_window", "^RW$"), + Cmd("set_chopper_started", "^WS([0-9]+)$"), + Cmd("set_demanded_frequency", "^WM([0-9]+)$"), + Cmd("set_demanded_phase_delay", "^WP([0-9]+)$"), + Cmd("set_demanded_phase_error_window", "^WR([0-9]+)$") } in_terminator = "\r\n" @@ -16,4 +25,53 @@ def handle_error(self, request, error): return str(error) def get_demanded_frequency(self): - return self._device.get_demanded_frequency() \ No newline at end of file + return "RG{0:03d}".format(int(self._device.get_demanded_frequency())) + + def get_true_frequency(self): + return "RF{0:03d}".format(int(self._device.get_true_frequency())) + + def get_demanded_phase_delay(self): + return "RQ{0:05d}".format(self._device.get_demanded_phase_delay()) + + def get_true_phase_delay(self): + return "RP{0:05d}".format(int(self._device.get_true_phase_delay())) + + def get_demanded_phase_error_window(self): + return "RW{0:03d}".format(self._device.get_demanded_phase_error_window()) + + def get_true_phase_error(self): + return "RE{0:03d}".format(int(self._device.get_true_phase_error())) + + def set_chopper_started(self, start_flag_raw): + try: + start_flag = int(start_flag_raw) + except ValueError: + pass + else: + if start_flag == 1: + self._device.start() + elif start_flag == 2: + self._device.stop() + return + + def set_demanded_frequency(self, new_frequency_raw): + return Mk2ChopperStreamInterface._set(new_frequency_raw, self.get_demanded_frequency, + self._device.set_demanded_frequency) + + def set_demanded_phase_delay(self, new_phase_delay_raw): + return Mk2ChopperStreamInterface._set(new_phase_delay_raw, self.get_demanded_phase_delay, + self._device.set_demanded_phase_delay) + + def set_demanded_phase_error_window(self, new_phase_error_window_raw): + return Mk2ChopperStreamInterface._set(new_phase_error_window_raw, self.get_demanded_phase_error_window, + self._device.set_demanded_phase_error_window) + + @staticmethod + def _set(raw, device_get, device_set): + try: + int_value = int(raw) + except ValueError: + pass + else: + device_set(int_value) + return device_get() diff --git a/lewis_emulators/mk2_chopper/states.py b/lewis_emulators/mk2_chopper/states.py index 7b9c6af7..5cc6ced3 100644 --- a/lewis_emulators/mk2_chopper/states.py +++ b/lewis_emulators/mk2_chopper/states.py @@ -1,10 +1,26 @@ from lewis.core.statemachine import State +from lewis.core import approaches +def output_current_state(device, state_name): + print "{0}: Freq {1}, Phase {2}, Error {3}".format(state_name.upper(), + device.get_true_frequency(), + device.get_true_phase_delay(), + device.get_true_phase_error()) class DefaultInitState(State): pass -class DefaultRunningState(State): +class DefaultStoppedState(State): def in_state(self, dt): - self._context.update(dt) + device = self._context + output_current_state(self._context, "stopped") + device.set_true_frequency(approaches.linear(device.get_true_frequency(), 0, 1, dt)) + + +class DefaultStartedState(State): + def in_state(self, dt): + device = self._context + output_current_state(self._context, "started") + device.set_true_frequency(approaches.linear(device.get_true_frequency(), + device.get_demanded_frequency(), 1, dt)) From d3960d0cfa4bcca1d8a7ea57aeefce70e952d4f3 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 31 Mar 2017 09:38:49 +0100 Subject: [PATCH 0143/1466] Little bit of logic rework for type handling --- lewis_emulators/mk2_chopper/chopper_type.py | 32 ++++++++++++++------- lewis_emulators/mk2_chopper/device.py | 24 ++++------------ 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/lewis_emulators/mk2_chopper/chopper_type.py b/lewis_emulators/mk2_chopper/chopper_type.py index 90e8bc36..905f5871 100644 --- a/lewis_emulators/mk2_chopper/chopper_type.py +++ b/lewis_emulators/mk2_chopper/chopper_type.py @@ -1,14 +1,26 @@ class ChopperType(object): - - FREQUENCY_TO_MAX_PHASE_CONVERSIONS = {5: 99995, 10: 99995, 12.5: 79995, 16.67: 59995, - 25: 39995, 50: 19995, 100: 9995} """ A type of chopper that the system can represent. Typically MK2 choppers come in 50Hz and 100Hz varieties. - - Valid states are given as tuples with the first value being the frequency, the second value being the maximum - phase delay. """ - def __init__(self, system_frequency, valid_frequencies): - self.system_frequency = system_frequency - self.valid_states = [(f, ChopperType.FREQUENCY_TO_MAX_PHASE_CONVERSIONS[f]) for f in valid_frequencies - if f in ChopperType.FREQUENCY_TO_MAX_PHASE_CONVERSIONS.keys()] + + # A dictionary of states the system can be in {Maximum frequency: [(Actual frequency, Max phase delay), ...]} + VALID_SYSTEM_STATES = { + 50: [(5, 99995), (10,99995), (12.5, 79995), (16.67, 59995), (25, 39995), (50, 19995)], + 100: [(12.5, 79995), (25, 39995), (50, 19995), (100, 9995)] + } + + def __init__(self, max_frequency): + possible_max_frequencies = ChopperType.VALID_SYSTEM_STATES.keys() + if max_frequency in possible_max_frequencies: + self.max_frequency = max_frequency + else: + self.max_frequency = min(possible_max_frequencies) + + def get_closest_valid_frequency(self, frequency): + return self._get_frequency_and_phase_closest_to_frequency(frequency)[0] + + def get_max_phase_for_closest_frequency(self, frequency): + return self._get_frequency_and_phase_closest_to_frequency(frequency)[1] + + def _get_frequency_and_phase_closest_to_frequency(self, frequency): + return min(ChopperType.VALID_SYSTEM_STATES[self.max_frequency], key=lambda x: abs(x - frequency)) diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index 99188d08..81ac25c8 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -6,9 +6,6 @@ class SimulatedMk2Chopper(StateMachineDevice): - # Dictionary of valid (frequencies, max phase delay) for each chopper type: 50Hz/100Hz - CHOPPER_TYPES = [ChopperType(50, [5, 10, 12.5, 16.67, 25, 50]), ChopperType(100, [12.5, 25, 50, 100])] - def _initialize_data(self): """ Initialize all of the device's attributes """ self._type = SimulatedMk2Chopper.CHOPPER_TYPES[0] @@ -63,12 +60,8 @@ def get_true_phase_error(self): return self._true_phase_error def set_demanded_frequency(self, new_frequency_int): - try: - self._demanded_frequency, self._max_phase_delay = next((f, p) for f, p in self._type.valid_states if - int(f)==int(new_frequency_int)) - except StopIteration: - # No value found, do nothing - pass + self._demanded_frequency = self._type.get_closest_valid_frequency(new_frequency_int) + self._max_phase_delay = self._type.get_max_phase_for_closest_frequency(new_frequency_int) def set_demanded_phase_delay(self, new_phase_delay): self._demanded_phase_delay = min(new_phase_delay, self._max_phase_delay) @@ -77,17 +70,12 @@ def set_true_frequency(self, new_frequency): self._true_frequency = new_frequency def set_chopper_type(self, frequency): - try: - self._type = next(ct for ct in SimulatedMk2Chopper.CHOPPER_TYPES if ct.system_frequency == frequency) - except StopIteration: - # The new type wasn't valid, do nothing - pass - else: - # Make sure the demanded frequency is a valid frequency for the new type - self.set_frequency(min(self._type.valid_frequencies, key=lambda x:abs(x - self._demanded_frequency))) + self._type = ChopperType(frequency) + # Do this in case the current demanded frequency is invalid for the new type + self.set_demanded_frequency(self._demanded_frequency) def start(self): self._started = True def stop(self): - self._started = False \ No newline at end of file + self._started = False From b2666416038ba4f901d09be2752fd179e459f1c0 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 31 Mar 2017 11:04:57 +0100 Subject: [PATCH 0144/1466] Add error and interlock logic --- lewis_emulators/mk2_chopper/chopper_type.py | 25 +++++-- lewis_emulators/mk2_chopper/device.py | 69 +++++++++++++++++-- .../interfaces/stream_interface.py | 55 +++++++++++++-- 3 files changed, 133 insertions(+), 16 deletions(-) diff --git a/lewis_emulators/mk2_chopper/chopper_type.py b/lewis_emulators/mk2_chopper/chopper_type.py index 905f5871..5472a555 100644 --- a/lewis_emulators/mk2_chopper/chopper_type.py +++ b/lewis_emulators/mk2_chopper/chopper_type.py @@ -9,12 +9,19 @@ class ChopperType(object): 100: [(12.5, 79995), (25, 39995), (50, 19995), (100, 9995)] } - def __init__(self, max_frequency): + CORTINA = "cortina" + INDRAMAT = "indramat" + SPECTRAL = "spectral" + MANUFACTURERS = [CORTINA, INDRAMAT, SPECTRAL] + + def __init__(self, max_frequency, manufacturer): possible_max_frequencies = ChopperType.VALID_SYSTEM_STATES.keys() - if max_frequency in possible_max_frequencies: - self.max_frequency = max_frequency - else: - self.max_frequency = min(possible_max_frequencies) + self._max_frequency = max_frequency if max_frequency in possible_max_frequencies\ + else min(possible_max_frequencies) + + manufacturer_low = manufacturer.lower() + self._manufacturer = manufacturer_low if manufacturer_low in ChopperType.MANUFACTURERS else ChopperType.INDRAMAT + def get_closest_valid_frequency(self, frequency): return self._get_frequency_and_phase_closest_to_frequency(frequency)[0] @@ -23,4 +30,10 @@ def get_max_phase_for_closest_frequency(self, frequency): return self._get_frequency_and_phase_closest_to_frequency(frequency)[1] def _get_frequency_and_phase_closest_to_frequency(self, frequency): - return min(ChopperType.VALID_SYSTEM_STATES[self.max_frequency], key=lambda x: abs(x - frequency)) + return min(ChopperType.VALID_SYSTEM_STATES[self._max_frequency], key=lambda x: abs(x[0] - frequency)) + + def get_manufacturer(self): + return self._manufacturer + + def get_frequency(self): + return self._max_frequency diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index 81ac25c8..bb599d5a 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -6,11 +6,14 @@ class SimulatedMk2Chopper(StateMachineDevice): + MAX_TEMPERATURE = 1 + def _initialize_data(self): """ Initialize all of the device's attributes """ - self._type = SimulatedMk2Chopper.CHOPPER_TYPES[0] + self._type = ChopperType(50, ChopperType.INDRAMAT) - self._demanded_frequency, self._max_phase_delay = self._type.valid_states[-1] + self._demanded_frequency = self._type.get_frequency() + self._max_phase_delay = self._type.get_max_phase_for_closest_frequency(self._demanded_frequency) self._true_frequency = 0 self._demanded_phase_delay = 0 @@ -20,6 +23,11 @@ def _initialize_data(self): self._true_phase_error = 0 self._started = False + self._fault = False + + self._phase_delay_error = False + self._phase_delay_correction_error = False + self._phase_accuracy_window_error = False # When initialisation is complete, this is set to true and the device will enter a running state self.ready = True @@ -41,6 +49,12 @@ def _get_transition_handlers(self): (('started', 'stopped'), lambda: self._started is False), ]) + def get_system_frequency(self): + return self._type.get_frequency() + + def get_manufacturer(self): + return self._type.get_manufacturer() + def get_demanded_frequency(self): return self._demanded_frequency @@ -59,18 +73,65 @@ def get_demanded_phase_error_window(self): def get_true_phase_error(self): return self._true_phase_error + def inverter_ready(self): + return self._type.get_manufacturer() in [ChopperType.CORTINA] + + def motor_running(self): + return self._true_frequency > 0 + + def in_sync(self): + tolerance = 0.001*self._type.get_frequency() + return abs(self._true_frequency - self._demanded_frequency) < tolerance + + def reg_mode(self): + return False + + def external_fault(self): + return self._fault + + def clock_loss(self): + return False + + def bearing_1_overheat(self): + return self._overheat() + + def bearing_2_overheat(self): + return self._overheat() + + def motor_overheat(self): + return self._overheat() + + def _overheat(self): + return self._temperature > SimulatedMk2Chopper.MAX_TEMPERATURE + + def chopper_overspeed(self): + return self._true_frequency > self._demanded_frequency + + def phase_delay_error(self): + return self._phase_delay_error + + def phase_delay_correction_error(self): + return self._phase_delay_correction_error + + def phase_accuracy_window_error(self): + return self._phase_accuracy_window_error + def set_demanded_frequency(self, new_frequency_int): self._demanded_frequency = self._type.get_closest_valid_frequency(new_frequency_int) self._max_phase_delay = self._type.get_max_phase_for_closest_frequency(new_frequency_int) def set_demanded_phase_delay(self, new_phase_delay): self._demanded_phase_delay = min(new_phase_delay, self._max_phase_delay) + self._phase_delay_error = self._demanded_phase_delay != new_phase_delay + + def set_demanded_phase_error_window(self, new_phase_window): + self._demanded_phase_error_window = new_phase_window def set_true_frequency(self, new_frequency): self._true_frequency = new_frequency - def set_chopper_type(self, frequency): - self._type = ChopperType(frequency) + def set_chopper_type(self, frequency, manufacturer): + self._type = ChopperType(frequency, manufacturer) # Do this in case the current demanded frequency is invalid for the new type self.set_demanded_frequency(self._demanded_frequency) diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index 2383df17..468a64cb 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -1,4 +1,5 @@ from lewis.adapters.stream import StreamAdapter, Cmd +from ..chopper_type import ChopperType class Mk2ChopperStreamInterface(StreamAdapter): @@ -11,6 +12,9 @@ class Mk2ChopperStreamInterface(StreamAdapter): Cmd("get_demanded_phase_delay", "^RQ$"), Cmd("get_true_phase_error", "^RE$"), Cmd("get_demanded_phase_error_window", "^RW$"), + Cmd("get_chopper_interlocks", "^RC$"), + Cmd("get_spectral_interlocks", "^RS$"), + Cmd("get_error_flags", "^RX$"), Cmd("set_chopper_started", "^WS([0-9]+)$"), Cmd("set_demanded_frequency", "^WM([0-9]+)$"), Cmd("set_demanded_phase_delay", "^WP([0-9]+)$"), @@ -25,22 +29,61 @@ def handle_error(self, request, error): return str(error) def get_demanded_frequency(self): - return "RG{0:03d}".format(int(self._device.get_demanded_frequency())) + return "RG{0:3d}".format(int(self._device.get_demanded_frequency())) def get_true_frequency(self): - return "RF{0:03d}".format(int(self._device.get_true_frequency())) + return "RF{0:3d}".format(int(self._device.get_true_frequency())) def get_demanded_phase_delay(self): - return "RQ{0:05d}".format(self._device.get_demanded_phase_delay()) + return "RQ{0:5d}".format(self._device.get_demanded_phase_delay()) def get_true_phase_delay(self): - return "RP{0:05d}".format(int(self._device.get_true_phase_delay())) + return "RP{0:5d}".format(int(self._device.get_true_phase_delay())) def get_demanded_phase_error_window(self): - return "RW{0:03d}".format(self._device.get_demanded_phase_error_window()) + return "RW{0:3d}".format(self._device.get_demanded_phase_error_window()) def get_true_phase_error(self): - return "RE{0:03d}".format(int(self._device.get_true_phase_error())) + return "RE{0:3d}".format(int(self._device.get_true_phase_error())) + + def get_spectral_interlocks(self): + bits = [0]*8 + if self._device.get_manufacturer() == ChopperType.CORTINA: + bits[0] = 1 if self._device.inverter_ready() else 0 + bits[1] = 1 if self._device.motor_running() else 0 + bits[2] = 1 if self._device.in_sync() else 0 + elif self._device.get_manufacturer() == ChopperType.INDRAMAT: + bits[0] = 1 if self._device.motor_running() else 0 + bits[1] = 1 if self._device.reg_mode() else 0 + bits[2] = 1 if self._device.in_sync() else 0 + elif self._device.get_manufacturer() == ChopperType.SPECTRAL: + bits[2] = 1 if self._device.external_fault() else 0 + + return "RC{0:8s}".format("".join(str(n) for n in bits)) + + def get_chopper_interlocks(self): + bits = [ + 1 if self._device.get_system_frequency() is 100 else 0, + 1 if self._device.clock_loss() else 0, + 1 if self._device.bearing_1_overheat() else 0, + 1 if self._device.bearing_2_overheat() else 0, + 1 if self._device.motor_overheat() else 0, + 1 if self._device.chopper_overspeed() else 0, + ] + bits += [0]*2 + return "RS{0:8s}".format("".join(str(n) for n in bits)) + + def get_error_flags(self): + bits = [ + 1 if self._device.phase_delay_error() else 0, + 1 if self._device.phase_delay_correction_error() else 0, + 1 if self._device.phase_accuracy_window_error else 0, + ] + bits += [0]*5 + return "RX{0:8s}".format("".join(str(n) for n in bits)) + + def get_manufacturer(self): + return self._type.get_manufacturer() def set_chopper_started(self, start_flag_raw): try: From d6679b6036426ec4eb0684234b28d7b780c9dd73 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 31 Mar 2017 13:50:39 +0100 Subject: [PATCH 0145/1466] Few corrections after interface first pass testing --- lewis_emulators/mk2_chopper/device.py | 15 +++++--- .../interfaces/stream_interface.py | 36 ++++++++++++++----- lewis_emulators/mk2_chopper/states.py | 19 +++++++--- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index bb599d5a..15527397 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -1,13 +1,10 @@ from collections import OrderedDict -from states import DefaultInitState, DefaultStoppedState, DefaultStartedState +from states import DefaultInitState, DefaultStoppedState, DefaultStartedState, MAX_TEMPERATURE from lewis.devices import StateMachineDevice from chopper_type import ChopperType - class SimulatedMk2Chopper(StateMachineDevice): - MAX_TEMPERATURE = 1 - def _initialize_data(self): """ Initialize all of the device's attributes """ self._type = ChopperType(50, ChopperType.INDRAMAT) @@ -29,6 +26,8 @@ def _initialize_data(self): self._phase_delay_correction_error = False self._phase_accuracy_window_error = False + self._temperature = 0 + # When initialisation is complete, this is set to true and the device will enter a running state self.ready = True @@ -73,6 +72,9 @@ def get_demanded_phase_error_window(self): def get_true_phase_error(self): return self._true_phase_error + def get_temperature(self): + return self._temperature + def inverter_ready(self): return self._type.get_manufacturer() in [ChopperType.CORTINA] @@ -102,7 +104,7 @@ def motor_overheat(self): return self._overheat() def _overheat(self): - return self._temperature > SimulatedMk2Chopper.MAX_TEMPERATURE + return self._temperature > MAX_TEMPERATURE def chopper_overspeed(self): return self._true_frequency > self._demanded_frequency @@ -135,6 +137,9 @@ def set_chopper_type(self, frequency, manufacturer): # Do this in case the current demanded frequency is invalid for the new type self.set_demanded_frequency(self._demanded_frequency) + def set_temperature(self, temperature): + self._temperatrue = temperature + def start(self): self._started = True diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index 468a64cb..0581144c 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -1,6 +1,20 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..chopper_type import ChopperType +def filled_int(val, length): + """ + Takes a value and returns a zero padded representation of the integer component + :param val: The original value. + :param length: Minimum length of the returned string + :return: Zero-padded integer representation (if possible) of string. Original string used if integer conversion + fails + """ + try: + converted_val = int(val) + except ValueError: + converted_val = val + return str(converted_val).zfill(length) + class Mk2ChopperStreamInterface(StreamAdapter): @@ -15,6 +29,7 @@ class Mk2ChopperStreamInterface(StreamAdapter): Cmd("get_chopper_interlocks", "^RC$"), Cmd("get_spectral_interlocks", "^RS$"), Cmd("get_error_flags", "^RX$"), + Cmd("read_all", "^RA$"), Cmd("set_chopper_started", "^WS([0-9]+)$"), Cmd("set_demanded_frequency", "^WM([0-9]+)$"), Cmd("set_demanded_phase_delay", "^WP([0-9]+)$"), @@ -29,22 +44,22 @@ def handle_error(self, request, error): return str(error) def get_demanded_frequency(self): - return "RG{0:3d}".format(int(self._device.get_demanded_frequency())) + return "RG{0}".format(filled_int(self._device.get_demanded_frequency(), 3)) def get_true_frequency(self): - return "RF{0:3d}".format(int(self._device.get_true_frequency())) + return "RF{0}".format(filled_int(self._device.get_true_frequency(), 3)) def get_demanded_phase_delay(self): - return "RQ{0:5d}".format(self._device.get_demanded_phase_delay()) + return "RQ{0}".format(filled_int(self._device.get_demanded_phase_delay(), 5)) def get_true_phase_delay(self): - return "RP{0:5d}".format(int(self._device.get_true_phase_delay())) + return "RP{0}".format(filled_int(self._device.get_true_phase_delay(), 5)) def get_demanded_phase_error_window(self): - return "RW{0:3d}".format(self._device.get_demanded_phase_error_window()) + return "RW{0}".format(filled_int(self._device.get_demanded_phase_error_window(), 3)) def get_true_phase_error(self): - return "RE{0:3d}".format(int(self._device.get_true_phase_error())) + return "RE{0}".format(filled_int(self._device.get_true_phase_error(), 3)) def get_spectral_interlocks(self): bits = [0]*8 @@ -59,7 +74,7 @@ def get_spectral_interlocks(self): elif self._device.get_manufacturer() == ChopperType.SPECTRAL: bits[2] = 1 if self._device.external_fault() else 0 - return "RC{0:8s}".format("".join(str(n) for n in bits)) + return "RS{0:8s}".format("".join(str(n) for n in bits)) def get_chopper_interlocks(self): bits = [ @@ -71,13 +86,13 @@ def get_chopper_interlocks(self): 1 if self._device.chopper_overspeed() else 0, ] bits += [0]*2 - return "RS{0:8s}".format("".join(str(n) for n in bits)) + return "RC{0:8s}".format("".join(str(n) for n in bits)) def get_error_flags(self): bits = [ 1 if self._device.phase_delay_error() else 0, 1 if self._device.phase_delay_correction_error() else 0, - 1 if self._device.phase_accuracy_window_error else 0, + 1 if self._device.phase_accuracy_window_error() else 0, ] bits += [0]*5 return "RX{0:8s}".format("".join(str(n) for n in bits)) @@ -109,6 +124,9 @@ def set_demanded_phase_error_window(self, new_phase_error_window_raw): return Mk2ChopperStreamInterface._set(new_phase_error_window_raw, self.get_demanded_phase_error_window, self._device.set_demanded_phase_error_window) + def read_all(self): + return "RA:Don't use, it causes the driver to lock up" + @staticmethod def _set(raw, device_get, device_set): try: diff --git a/lewis_emulators/mk2_chopper/states.py b/lewis_emulators/mk2_chopper/states.py index 5cc6ced3..089a82da 100644 --- a/lewis_emulators/mk2_chopper/states.py +++ b/lewis_emulators/mk2_chopper/states.py @@ -1,11 +1,17 @@ from lewis.core.statemachine import State from lewis.core import approaches +# Would rather this were in device but causes Lewis to fail +MAX_TEMPERATURE = 1 + def output_current_state(device, state_name): - print "{0}: Freq {1}, Phase {2}, Error {3}".format(state_name.upper(), - device.get_true_frequency(), - device.get_true_phase_delay(), - device.get_true_phase_error()) + print "{0}: Freq {1:.2f}, Phase {2}, Error {3}, Temperature {4:.2f}".format( + state_name.upper(), + device.get_true_frequency(), + device.get_true_phase_delay(), + device.get_true_phase_error(), + device.get_temperature(), + ) class DefaultInitState(State): pass @@ -16,6 +22,7 @@ def in_state(self, dt): device = self._context output_current_state(self._context, "stopped") device.set_true_frequency(approaches.linear(device.get_true_frequency(), 0, 1, dt)) + device.set_temperature(approaches.linear(device.get_temperature(), 0, 0.1, dt)) class DefaultStartedState(State): @@ -24,3 +31,7 @@ def in_state(self, dt): output_current_state(self._context, "started") device.set_true_frequency(approaches.linear(device.get_true_frequency(), device.get_demanded_frequency(), 1, dt)) + equilibrium_frequency_temperature = 2*MAX_TEMPERATURE*device.get_true_frequency()/device.get_system_frequency() + device.set_temperature(approaches.linear(device.get_temperature(),equilibrium_frequency_temperature, + device.get_true_frequency()*0.01, + dt)) From 4e929edcd359fbdd3b303800d068eaf65a8a29db Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 31 Mar 2017 14:02:40 +0100 Subject: [PATCH 0146/1466] Add phase delay update logic --- lewis_emulators/mk2_chopper/device.py | 8 +++++--- lewis_emulators/mk2_chopper/states.py | 7 +++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index 15527397..ce8ca640 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -17,7 +17,6 @@ def _initialize_data(self): self._true_phase_delay = 0 self._demanded_phase_error_window = 0 - self._true_phase_error = 0 self._started = False self._fault = False @@ -70,7 +69,7 @@ def get_demanded_phase_error_window(self): return self._demanded_phase_error_window def get_true_phase_error(self): - return self._true_phase_error + return abs(self._true_phase_delay - self._demanded_phase_delay) def get_temperature(self): return self._temperature @@ -132,13 +131,16 @@ def set_demanded_phase_error_window(self, new_phase_window): def set_true_frequency(self, new_frequency): self._true_frequency = new_frequency + def set_true_phase_delay(self, new_delay): + self._true_phase_delay = new_delay + def set_chopper_type(self, frequency, manufacturer): self._type = ChopperType(frequency, manufacturer) # Do this in case the current demanded frequency is invalid for the new type self.set_demanded_frequency(self._demanded_frequency) def set_temperature(self, temperature): - self._temperatrue = temperature + self._temperature = temperature def start(self): self._started = True diff --git a/lewis_emulators/mk2_chopper/states.py b/lewis_emulators/mk2_chopper/states.py index 089a82da..5576b7cf 100644 --- a/lewis_emulators/mk2_chopper/states.py +++ b/lewis_emulators/mk2_chopper/states.py @@ -5,7 +5,7 @@ MAX_TEMPERATURE = 1 def output_current_state(device, state_name): - print "{0}: Freq {1:.2f}, Phase {2}, Error {3}, Temperature {4:.2f}".format( + print "{0}: Freq {1:.2f}, Phase {2:.2f}, Error {3:.2f}, Temperature {4:.2f}".format( state_name.upper(), device.get_true_frequency(), device.get_true_phase_delay(), @@ -23,6 +23,7 @@ def in_state(self, dt): output_current_state(self._context, "stopped") device.set_true_frequency(approaches.linear(device.get_true_frequency(), 0, 1, dt)) device.set_temperature(approaches.linear(device.get_temperature(), 0, 0.1, dt)) + device.set_true_phase_delay(approaches.linear(device.get_true_phase_delay(), 0, 1, dt)) class DefaultStartedState(State): @@ -32,6 +33,8 @@ def in_state(self, dt): device.set_true_frequency(approaches.linear(device.get_true_frequency(), device.get_demanded_frequency(), 1, dt)) equilibrium_frequency_temperature = 2*MAX_TEMPERATURE*device.get_true_frequency()/device.get_system_frequency() - device.set_temperature(approaches.linear(device.get_temperature(),equilibrium_frequency_temperature, + device.set_temperature(approaches.linear(device.get_temperature(), equilibrium_frequency_temperature, device.get_true_frequency()*0.01, dt)) + device.set_true_phase_delay(approaches.linear(device.get_true_phase_delay(), device.get_demanded_phase_delay(), + 1, dt)) From e9976af8ab4c3c9fbd759dece64e317d723ec0a4 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 31 Mar 2017 14:06:41 +0100 Subject: [PATCH 0147/1466] Tune temperature response --- lewis_emulators/mk2_chopper/states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/mk2_chopper/states.py b/lewis_emulators/mk2_chopper/states.py index 5576b7cf..056db4b6 100644 --- a/lewis_emulators/mk2_chopper/states.py +++ b/lewis_emulators/mk2_chopper/states.py @@ -34,7 +34,7 @@ def in_state(self, dt): device.get_demanded_frequency(), 1, dt)) equilibrium_frequency_temperature = 2*MAX_TEMPERATURE*device.get_true_frequency()/device.get_system_frequency() device.set_temperature(approaches.linear(device.get_temperature(), equilibrium_frequency_temperature, - device.get_true_frequency()*0.01, + device.get_true_frequency()*0.001, dt)) device.set_true_phase_delay(approaches.linear(device.get_true_phase_delay(), device.get_demanded_phase_delay(), 1, dt)) From 20f6a7268c3104b547d29a288d573f4abc177d54 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 31 Mar 2017 17:04:03 +0100 Subject: [PATCH 0148/1466] Change line endings to fit ioc --- lewis_emulators/mk2_chopper/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index 0581144c..a0459413 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -36,8 +36,8 @@ class Mk2ChopperStreamInterface(StreamAdapter): Cmd("set_demanded_phase_error_window", "^WR([0-9]+)$") } - in_terminator = "\r\n" - out_terminator = "\r\n" + in_terminator = "\r" + out_terminator = "\r" def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) From 182d2d68adf206692edb65c0816729a2544d35c5 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 3 Apr 2017 10:21:10 +0100 Subject: [PATCH 0149/1466] MSB at wrong end --- .../interfaces/stream_interface.py | 37 ++++++++++--------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index a0459413..ae5e8eb7 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -74,28 +74,25 @@ def get_spectral_interlocks(self): elif self._device.get_manufacturer() == ChopperType.SPECTRAL: bits[2] = 1 if self._device.external_fault() else 0 - return "RS{0:8s}".format("".join(str(n) for n in bits)) + return "RS{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_chopper_interlocks(self): - bits = [ - 1 if self._device.get_system_frequency() is 100 else 0, - 1 if self._device.clock_loss() else 0, - 1 if self._device.bearing_1_overheat() else 0, - 1 if self._device.bearing_2_overheat() else 0, - 1 if self._device.motor_overheat() else 0, - 1 if self._device.chopper_overspeed() else 0, - ] - bits += [0]*2 - return "RC{0:8s}".format("".join(str(n) for n in bits)) + bits = [0]*8 + bits[0] = 1 if self._device.get_system_frequency() is 50 else 0 + bits[1] = 1 if self._device.clock_loss() else 0 + bits[2] = 1 if self._device.bearing_1_overheat() else 0 + bits[3] = 1 if self._device.bearing_2_overheat() else 0 + bits[4] = 1 if self._device.motor_overheat() else 0 + bits[5] = 1 if self._device.chopper_overspeed() else 0 + + return "RC{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_error_flags(self): - bits = [ - 1 if self._device.phase_delay_error() else 0, - 1 if self._device.phase_delay_correction_error() else 0, - 1 if self._device.phase_accuracy_window_error() else 0, - ] - bits += [0]*5 - return "RX{0:8s}".format("".join(str(n) for n in bits)) + bits = [0]*8 + bits[0] = 1 if self._device.phase_delay_error() else 0 + bits[1] = 1 if self._device.phase_delay_correction_error() else 0 + bits[2] = 1 if self._device.phase_accuracy_window_error() else 0 + return "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_manufacturer(self): return self._type.get_manufacturer() @@ -136,3 +133,7 @@ def _set(raw, device_get, device_set): else: device_set(int_value) return device_get() + + @staticmethod + def _string_from_bits(bits): + return "".join(str(n) for n in reversed(bits)) From 187bc26e23e43197c0c6a2bac77b71d6f04bf7b2 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 3 Apr 2017 10:46:34 +0100 Subject: [PATCH 0150/1466] Add more realistic behaviours for phase errors --- lewis_emulators/mk2_chopper/device.py | 9 ++++----- .../mk2_chopper/interfaces/stream_interface.py | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index ce8ca640..b73b497a 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -16,14 +16,12 @@ def _initialize_data(self): self._demanded_phase_delay = 0 self._true_phase_delay = 0 - self._demanded_phase_error_window = 0 + self._demanded_phase_error_window = 1 self._started = False self._fault = False self._phase_delay_error = False - self._phase_delay_correction_error = False - self._phase_accuracy_window_error = False self._temperature = 0 @@ -112,10 +110,11 @@ def phase_delay_error(self): return self._phase_delay_error def phase_delay_correction_error(self): - return self._phase_delay_correction_error + tolerance = 0.001*self._demanded_phase_delay + return abs(self._true_phase_delay - self._demanded_phase_delay) > tolerance def phase_accuracy_window_error(self): - return self._phase_accuracy_window_error + return abs(self._true_phase_delay - self._demanded_phase_delay) > self._demanded_phase_error_window def set_demanded_frequency(self, new_frequency_int): self._demanded_frequency = self._type.get_closest_valid_frequency(new_frequency_int) diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index ae5e8eb7..d170d1d5 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -92,6 +92,7 @@ def get_error_flags(self): bits[0] = 1 if self._device.phase_delay_error() else 0 bits[1] = 1 if self._device.phase_delay_correction_error() else 0 bits[2] = 1 if self._device.phase_accuracy_window_error() else 0 + print "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) return "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_manufacturer(self): From 061d238919deddfda828143ab45d886c18b75c6b Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 3 Apr 2017 10:48:51 +0100 Subject: [PATCH 0151/1466] Remove debug output --- lewis_emulators/mk2_chopper/interfaces/stream_interface.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index d170d1d5..635561c4 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -73,7 +73,6 @@ def get_spectral_interlocks(self): bits[2] = 1 if self._device.in_sync() else 0 elif self._device.get_manufacturer() == ChopperType.SPECTRAL: bits[2] = 1 if self._device.external_fault() else 0 - return "RS{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_chopper_interlocks(self): @@ -84,7 +83,6 @@ def get_chopper_interlocks(self): bits[3] = 1 if self._device.bearing_2_overheat() else 0 bits[4] = 1 if self._device.motor_overheat() else 0 bits[5] = 1 if self._device.chopper_overspeed() else 0 - return "RC{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_error_flags(self): @@ -92,7 +90,6 @@ def get_error_flags(self): bits[0] = 1 if self._device.phase_delay_error() else 0 bits[1] = 1 if self._device.phase_delay_correction_error() else 0 bits[2] = 1 if self._device.phase_accuracy_window_error() else 0 - print "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) return "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) def get_manufacturer(self): From 94dfdbe963cb388b2521dbdd48dbcff612886d6f Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 3 Apr 2017 14:01:47 +0100 Subject: [PATCH 0152/1466] Add overspeed setting. Set drive running to started state to differentiate from IOC on/off --- lewis_emulators/mk2_chopper/chopper_type.py | 7 +++++-- lewis_emulators/mk2_chopper/device.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/mk2_chopper/chopper_type.py b/lewis_emulators/mk2_chopper/chopper_type.py index 5472a555..f124b687 100644 --- a/lewis_emulators/mk2_chopper/chopper_type.py +++ b/lewis_emulators/mk2_chopper/chopper_type.py @@ -3,10 +3,13 @@ class ChopperType(object): A type of chopper that the system can represent. Typically MK2 choppers come in 50Hz and 100Hz varieties. """ + # An overspeed system state used to check the overspeed error flag + OVERSPEED = (999, 95) + # A dictionary of states the system can be in {Maximum frequency: [(Actual frequency, Max phase delay), ...]} VALID_SYSTEM_STATES = { - 50: [(5, 99995), (10,99995), (12.5, 79995), (16.67, 59995), (25, 39995), (50, 19995)], - 100: [(12.5, 79995), (25, 39995), (50, 19995), (100, 9995)] + 50: [(5, 99995), (10,99995), (12.5, 79995), (16.67, 59995), (25, 39995), (50, 19995), OVERSPEED], + 100: [(12.5, 79995), (25, 39995), (50, 19995), (100, 9995), OVERSPEED] } CORTINA = "cortina" diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index b73b497a..915ebe77 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -76,7 +76,7 @@ def inverter_ready(self): return self._type.get_manufacturer() in [ChopperType.CORTINA] def motor_running(self): - return self._true_frequency > 0 + return self._started def in_sync(self): tolerance = 0.001*self._type.get_frequency() @@ -104,7 +104,7 @@ def _overheat(self): return self._temperature > MAX_TEMPERATURE def chopper_overspeed(self): - return self._true_frequency > self._demanded_frequency + return self._true_frequency > self._type.get_frequency() def phase_delay_error(self): return self._phase_delay_error From 04d672b9c737560b548448f2f646b3bbd96af544 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 6 Apr 2017 14:09:00 +0100 Subject: [PATCH 0153/1466] Made the emulator stateful --- lewis_emulators/HRPD_Sample_changer/device.py | 86 +++++++++++++++---- .../interfaces/stream_interface.py | 5 +- 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/HRPD_Sample_changer/device.py index fd076f1c..336cf61b 100644 --- a/lewis_emulators/HRPD_Sample_changer/device.py +++ b/lewis_emulators/HRPD_Sample_changer/device.py @@ -1,9 +1,27 @@ -from lewis.devices import Device +from lewis.devices import StateMachineDevice +from lewis.core.statemachine import State +from lewis.core import approaches +from collections import OrderedDict -class SimulatedHRPDSampleChanger(Device): + +class MovingState(State): + def on_entry(self, dt): + self._context.arm_lowered = False + + def in_state(self, dt): + old_position = self._context.car_pos + self._context.car_pos = approaches.linear(old_position, self._context.car_target, + self._context.SPEED, dt) + + def on_exit(self, dt): + self._context.arm_lowered = True + + +class SimulatedHRPDSampleChanger(StateMachineDevice): NO_ERR = 0 ERR_INV_DEST = 5 + ERR_NOT_INITIALISED = 6 ERR_ARM_DROPPED = 7 ERR_ARM_UP = 8 ERR_CANT_ROT_IF_NOT_UP = 10 @@ -11,45 +29,79 @@ class SimulatedHRPDSampleChanger(Device): MIN_CAROUSEL = 1 MAX_CAROUSEL = 20 - carousel_position = MIN_CAROUSEL - arm_lowered = False - current_err = NO_ERR + SPEED = 1 + + def _initialize_data(self): + self.car_pos = 0 + self.car_target = 0 + self.arm_lowered = False + self.current_err = self.NO_ERR + + def _get_state_handlers(self): + return { + 'init': State(), + 'initialising': MovingState(), + 'idle': State(), + 'moving': MovingState() + } + + def _get_initial_state(self): + return 'init' + + def _get_transition_handlers(self): + return OrderedDict([ + (('init', 'initialising'), lambda: self.car_target != 0), + (('initialising', 'idle'), lambda: self.car_pos == 1), + (('idle', 'moving'), lambda: self.car_target != self.car_pos), + (('moving', 'idle'), lambda: self.car_pos == self.car_target)]) def get_status(self): # Based on the labview VI, appears to be different than doc - return_string = "00000{0:b}01{1:b}{2:b}{3:b}00000 000" - car_at_one = (self.carousel_position == self.MIN_CAROUSEL) + return_string = "01000{0:b}01{1:b}{2:b}{3:b}00000" + car_at_one = (self.car_pos == self.MIN_CAROUSEL) return_string = return_string.format(not self.arm_lowered, car_at_one, not self.arm_lowered, self.arm_lowered) + + return_string += " 0{0:b}1".format(self._csm.state == 'moving') + return_string += " %02d" % self.current_err - return_string += " %02d" % self.carousel_position + return_string += " %02d" % self.car_pos + return return_string def go_forward(self): + if self._csm.state == 'init': + return self.ERR_NOT_INITIALISED if self.arm_lowered: return self.ERR_CANT_ROT_IF_NOT_UP - self._device.carousel_position += 1 - if self._device.carousel_position > self.MAX_CAROUSEL: - self._device.carousel_position = self.MIN_CAROUSEL + self.car_target += 1 + if self.car_target > self.MAX_CAROUSEL: + self.car_target = self.MIN_CAROUSEL return self.NO_ERR def go_backward(self): + if self._csm.state == 'init': + return self.ERR_NOT_INITIALISED if self.arm_lowered: return self.ERR_CANT_ROT_IF_NOT_UP - self._device.carousel_position -= 1 - if self._device.carousel_position < self.MIN_CAROUSEL: - self._device.carousel_position = self.MAX_CAROUSEL + self.car_target -= 1 + if self.car_target < self.MIN_CAROUSEL: + self.car_target = self.MAX_CAROUSEL return self.NO_ERR def move_to(self, position, lower_arm): + if self._csm.state == 'init': + return self.ERR_NOT_INITIALISED if (position < self.MIN_CAROUSEL) or (position > self.MAX_CAROUSEL): return self.ERR_INV_DEST else: - self.carousel_position = position + self.car_target = position self.arm_lowered = lower_arm return self.NO_ERR def set_arm(self, lowered): - if lowered != self.arm_lowered: + if self._csm.state == 'init': + return self.ERR_NOT_INITIALISED + if lowered == self.arm_lowered: if lowered: return self.ERR_ARM_DROPPED else: @@ -59,6 +111,6 @@ def set_arm(self, lowered): def init(self): self.arm_lowered = False - self.carousel_position = self.MIN_CAROUSEL + self.car_target = self.MIN_CAROUSEL self.current_err = self.NO_ERR return self.NO_ERR diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py index 03a87323..a75ba775 100644 --- a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py @@ -25,13 +25,14 @@ def _check_error_code(self, code): if code == self._device.NO_ERR: return "ok" else: + self._device.current_err = code return "rf-%02d" % code def get_id(self): return "0001 0001 ISIS HRPD Sample Changer V1.00" def get_position(self): - return "Position = " + str(self._device.carousel_position) + return "Position = " + str(self._device.car_pos) def get_status(self): return str(self._device.get_status()) @@ -43,7 +44,7 @@ def go_fwd(self): return self._check_error_code(self._device.go_forward()) def read_variable(self, variable): - return "READ VARIABLE " + str(variable) + return "- VR " + str(variable) + " = 17 hx 11" def halt(self): return "ok" From 750ed452c8b7cf0bbe283f1ce3fbcf5538544e3e Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 20 Apr 2017 11:15:04 +0100 Subject: [PATCH 0154/1466] Better error handling as per reviewer's comments --- com2tcp/com2tcp.py | 51 ++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/com2tcp/com2tcp.py b/com2tcp/com2tcp.py index 974ff665..047ccc5f 100644 --- a/com2tcp/com2tcp.py +++ b/com2tcp/com2tcp.py @@ -3,39 +3,56 @@ import socket import threading import argparse +import sys -def listen_to_tcp(tcp_conn, ser_conn): - while 1: +def listen_to_tcp(tcp_conn, serial_conn): + while True: tcp_data = tcp_conn.recv(1024) if len(data) > 0: - ser_conn.writelines(tcp_data) + serial_conn.writelines(tcp_data) print "Data on tcp: " + str(tcp_data) time.sleep(0.1) if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Transfers the data being sent to a COM port to be sent to a TCP port.") + parser = argparse.ArgumentParser(description="Transfers data between a COM port and a TCP port.") parser.add_argument("tcp_port", help="The port to send TCP messages to. (e.g. 57677)", type=int) parser.add_argument("com_port", help="The COM port to send serial messages to. (e.g. COM2)") parser.add_argument('-b', "--baud", help="The baud rate to communicate on the COM port.", default=9600) args = parser.parse_args() - tcp = socket.create_connection(('localhost', args.tcp_port)) - ser = serial.Serial(args.com_port, args.baud) + try: + tcp_conn = socket.create_connection(('localhost', args.tcp_port)) + except Exception as e: + print "Failed to connect to tcp port: " + str(e) + sys.exit() + + try: + serial_conn = serial.Serial(args.com_port, args.baud) + except Exception as e: + print "Failed to connect to serial port: " + str(e) + sys.exit() - listen_thread = threading.Thread(target=listen_to_tcp, args=(tcp, ser)) + listen_thread = threading.Thread(target=listen_to_tcp, args=(tcp_conn, serial_conn)) + listen_thread.daemon = True listen_thread.start() print "Listening on " + str(args.com_port) + " and localhost:" + str(args.tcp_port) - print "Press Ctrl+C+Break to stop" - - while True: - if ser.inWaiting(): - data = ser.read() - print "Data on serial: " + data - tcp.sendall(data) - - time.sleep(0.1) - + print "Press Ctrl+C to stop" + + try: + while True: + if serial_conn.inWaiting(): + data = serial_conn.read() + print "Data on serial: " + data + tcp_conn.sendall(data) + + time.sleep(0.1) + except (KeyboardInterrupt, SystemExit) as e: + sys.exit() + raise + finally: + tcp_conn.close() + serial_conn.close() From c715ef1656e6ea7d895f21e56c9b1d059849fbad Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 20 Apr 2017 11:17:40 +0100 Subject: [PATCH 0155/1466] Moved state into its own file --- lewis_emulators/HRPD_Sample_changer/device.py | 15 +-------------- lewis_emulators/HRPD_Sample_changer/states.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 lewis_emulators/HRPD_Sample_changer/states.py diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/HRPD_Sample_changer/device.py index 336cf61b..8e953e85 100644 --- a/lewis_emulators/HRPD_Sample_changer/device.py +++ b/lewis_emulators/HRPD_Sample_changer/device.py @@ -1,23 +1,10 @@ from lewis.devices import StateMachineDevice from lewis.core.statemachine import State -from lewis.core import approaches +from states import MovingState from collections import OrderedDict -class MovingState(State): - def on_entry(self, dt): - self._context.arm_lowered = False - - def in_state(self, dt): - old_position = self._context.car_pos - self._context.car_pos = approaches.linear(old_position, self._context.car_target, - self._context.SPEED, dt) - - def on_exit(self, dt): - self._context.arm_lowered = True - - class SimulatedHRPDSampleChanger(StateMachineDevice): NO_ERR = 0 ERR_INV_DEST = 5 diff --git a/lewis_emulators/HRPD_Sample_changer/states.py b/lewis_emulators/HRPD_Sample_changer/states.py new file mode 100644 index 00000000..8453c3f0 --- /dev/null +++ b/lewis_emulators/HRPD_Sample_changer/states.py @@ -0,0 +1,15 @@ +from lewis.core import approaches +from lewis.core.statemachine import State + + +class MovingState(State): + def on_entry(self, dt): + self._context.arm_lowered = False + + def in_state(self, dt): + old_position = self._context.car_pos + self._context.car_pos = approaches.linear(old_position, self._context.car_target, + self._context.SPEED, dt) + + def on_exit(self, dt): + self._context.arm_lowered = True \ No newline at end of file From 5535a13a6e260cd7638b09d17d8cadb7a6595e01 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 20 Apr 2017 11:42:40 +0100 Subject: [PATCH 0156/1466] Moved status string formatting into stream interface --- lewis_emulators/HRPD_Sample_changer/device.py | 18 +++++++----------- .../interfaces/stream_interface.py | 14 +++++++++++++- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/HRPD_Sample_changer/device.py index 8e953e85..17b8f50b 100644 --- a/lewis_emulators/HRPD_Sample_changer/device.py +++ b/lewis_emulators/HRPD_Sample_changer/device.py @@ -42,18 +42,11 @@ def _get_transition_handlers(self): (('idle', 'moving'), lambda: self.car_target != self.car_pos), (('moving', 'idle'), lambda: self.car_pos == self.car_target)]) - def get_status(self): - # Based on the labview VI, appears to be different than doc - return_string = "01000{0:b}01{1:b}{2:b}{3:b}00000" - car_at_one = (self.car_pos == self.MIN_CAROUSEL) - return_string = return_string.format(not self.arm_lowered, car_at_one, not self.arm_lowered, self.arm_lowered) + def is_car_at_one(self): + return self.car_pos == self.MIN_CAROUSEL - return_string += " 0{0:b}1".format(self._csm.state == 'moving') - - return_string += " %02d" % self.current_err - return_string += " %02d" % self.car_pos - - return return_string + def is_moving(self): + return self._csm.state == 'moving' def go_forward(self): if self._csm.state == 'init': @@ -96,6 +89,9 @@ def set_arm(self, lowered): self.arm_lowered = lowered return self.NO_ERR + def get_arm_lowered(self): + return self.arm_lowered + def init(self): self.arm_lowered = False self.car_target = self.MIN_CAROUSEL diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py index a75ba775..2d6300eb 100644 --- a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py @@ -35,7 +35,19 @@ def get_position(self): return "Position = " + str(self._device.car_pos) def get_status(self): - return str(self._device.get_status()) + lowered = self._device.get_arm_lowered() + + # Based on the labview VI, appears to be different than doc + return_string = "01000{0:b}01{1:b}{2:b}{3:b}00000" + return_string = return_string.format(not lowered, self._device.is_car_at_one(), not lowered, + lowered) + + return_string += " 0{0:b}1".format(self._device.is_moving()) + + return_string += " %02d" % self._device.current_err + return_string += " %02d" % self._device.car_pos + + return return_string def go_back(self): return self._check_error_code(self._device.go_backward()) From 1b838a9ee05cb3f538ccc78afbbcd93ca40b46cc Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 20 Apr 2017 13:27:22 +0100 Subject: [PATCH 0157/1466] Updated status string based on actual device --- .../interfaces/stream_interface.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py index 2d6300eb..4b17b925 100644 --- a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py @@ -18,6 +18,7 @@ class HRPDSampleChangerStreamInterface(StreamAdapter): Cmd("read_variable", "^vr([0-9]{4})$", argument_mappings=[int]) } + # Labview VI expects a \r\n for out_terminator in_terminator = "\r" out_terminator = "\r\n" @@ -32,20 +33,20 @@ def get_id(self): return "0001 0001 ISIS HRPD Sample Changer V1.00" def get_position(self): - return "Position = " + str(self._device.car_pos) + return "Position = {:2d}".format(int(self._device.car_pos)) def get_status(self): lowered = self._device.get_arm_lowered() - # Based on the labview VI, appears to be different than doc + # Based on testing with actual device, appears to be different than doc return_string = "01000{0:b}01{1:b}{2:b}{3:b}00000" return_string = return_string.format(not lowered, self._device.is_car_at_one(), not lowered, lowered) - return_string += " 0{0:b}1".format(self._device.is_moving()) + return_string += " 0 {:b}".format(self._device.is_moving()) - return_string += " %02d" % self._device.current_err - return_string += " %02d" % self._device.car_pos + return_string += " {:2d}".format(int(self._device.current_err)) + return_string += " {:2d}".format(int(self._device.car_pos)) return return_string From a8a9d7e275e0897f92fcb848365f1cfe504c07bb Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 24 Apr 2017 16:16:29 +0100 Subject: [PATCH 0158/1466] Updated to be more realistic --- lewis_emulators/HRPD_Sample_changer/device.py | 32 +++++++++++-------- lewis_emulators/HRPD_Sample_changer/states.py | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/HRPD_Sample_changer/device.py index 17b8f50b..22278ff7 100644 --- a/lewis_emulators/HRPD_Sample_changer/device.py +++ b/lewis_emulators/HRPD_Sample_changer/device.py @@ -16,11 +16,12 @@ class SimulatedHRPDSampleChanger(StateMachineDevice): MIN_CAROUSEL = 1 MAX_CAROUSEL = 20 - SPEED = 1 + ARM_SPEED = 1.0/25.0 # Arm takes 25s to raise/lower (measured on actual device) + CAR_SPEED = 1.0/6.0 # Carousel takes 6 seconds per position (measured on actual device) def _initialize_data(self): - self.car_pos = 0 - self.car_target = 0 + self.car_pos = -1 + self.car_target = -1 self.arm_lowered = False self.current_err = self.NO_ERR @@ -29,7 +30,7 @@ def _get_state_handlers(self): 'init': State(), 'initialising': MovingState(), 'idle': State(), - 'moving': MovingState() + 'car_moving': MovingState() } def _get_initial_state(self): @@ -37,32 +38,37 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict([ - (('init', 'initialising'), lambda: self.car_target != 0), + (('init', 'initialising'), lambda: self.car_target > 0), (('initialising', 'idle'), lambda: self.car_pos == 1), - (('idle', 'moving'), lambda: self.car_target != self.car_pos), - (('moving', 'idle'), lambda: self.car_pos == self.car_target)]) + (('idle', 'car_moving'), lambda: self.car_target != self.car_pos), + (('car_moving', 'idle'), lambda: self.car_pos == self.car_target)]) def is_car_at_one(self): return self.car_pos == self.MIN_CAROUSEL def is_moving(self): - return self._csm.state == 'moving' + return self._csm.state == 'car_moving' - def go_forward(self): + def _check_can_move(self): if self._csm.state == 'init': return self.ERR_NOT_INITIALISED if self.arm_lowered: return self.ERR_CANT_ROT_IF_NOT_UP + return self.ERR_OK + + def go_forward(self): + err_state = self._check_can_move() + if err_state: + return err_state self.car_target += 1 if self.car_target > self.MAX_CAROUSEL: self.car_target = self.MIN_CAROUSEL return self.NO_ERR def go_backward(self): - if self._csm.state == 'init': - return self.ERR_NOT_INITIALISED - if self.arm_lowered: - return self.ERR_CANT_ROT_IF_NOT_UP + err_state = self._check_can_move() + if err_state: + return err_state self.car_target -= 1 if self.car_target < self.MIN_CAROUSEL: self.car_target = self.MAX_CAROUSEL diff --git a/lewis_emulators/HRPD_Sample_changer/states.py b/lewis_emulators/HRPD_Sample_changer/states.py index 8453c3f0..c404a367 100644 --- a/lewis_emulators/HRPD_Sample_changer/states.py +++ b/lewis_emulators/HRPD_Sample_changer/states.py @@ -9,7 +9,7 @@ def on_entry(self, dt): def in_state(self, dt): old_position = self._context.car_pos self._context.car_pos = approaches.linear(old_position, self._context.car_target, - self._context.SPEED, dt) + self._context.CAR_SPEED, dt) def on_exit(self, dt): self._context.arm_lowered = True \ No newline at end of file From e23e348dad661e55f9b5deef10d3cc40dcd2a238 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 25 Apr 2017 16:44:02 +0100 Subject: [PATCH 0159/1466] Added seperate protocol for POLARIS --- .../HRPD_Sample_changer/__init__.py | 4 +- lewis_emulators/HRPD_Sample_changer/device.py | 42 ++++------ ..._interface.py => HRPD_stream_interface.py} | 18 ++++- .../interfaces/POLARIS_stream_interface.py | 78 +++++++++++++++++++ .../interfaces/__init__.py | 3 - lewis_emulators/HRPD_Sample_changer/states.py | 9 +++ 6 files changed, 120 insertions(+), 34 deletions(-) rename lewis_emulators/HRPD_Sample_changer/interfaces/{stream_interface.py => HRPD_stream_interface.py} (82%) create mode 100644 lewis_emulators/HRPD_Sample_changer/interfaces/POLARIS_stream_interface.py diff --git a/lewis_emulators/HRPD_Sample_changer/__init__.py b/lewis_emulators/HRPD_Sample_changer/__init__.py index fad472bc..9408c5b3 100644 --- a/lewis_emulators/HRPD_Sample_changer/__init__.py +++ b/lewis_emulators/HRPD_Sample_changer/__init__.py @@ -1,3 +1,3 @@ -from .device import SimulatedHRPDSampleChanger +from .device import SimulatedSampleChanger -__all__ = ['SimulatedHRPDSampleChanger'] +__all__ = ['SimulatedSampleChanger'] diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/HRPD_Sample_changer/device.py index 22278ff7..906578b5 100644 --- a/lewis_emulators/HRPD_Sample_changer/device.py +++ b/lewis_emulators/HRPD_Sample_changer/device.py @@ -1,18 +1,10 @@ from lewis.devices import StateMachineDevice from lewis.core.statemachine import State -from states import MovingState - +from states import MovingState, Errors from collections import OrderedDict -class SimulatedHRPDSampleChanger(StateMachineDevice): - NO_ERR = 0 - ERR_INV_DEST = 5 - ERR_NOT_INITIALISED = 6 - ERR_ARM_DROPPED = 7 - ERR_ARM_UP = 8 - ERR_CANT_ROT_IF_NOT_UP = 10 - +class SimulatedSampleChanger(StateMachineDevice): MIN_CAROUSEL = 1 MAX_CAROUSEL = 20 @@ -23,7 +15,7 @@ def _initialize_data(self): self.car_pos = -1 self.car_target = -1 self.arm_lowered = False - self.current_err = self.NO_ERR + self.current_err = Errors.NO_ERR def _get_state_handlers(self): return { @@ -51,10 +43,10 @@ def is_moving(self): def _check_can_move(self): if self._csm.state == 'init': - return self.ERR_NOT_INITIALISED + return Errors.ERR_NOT_INITIALISED if self.arm_lowered: - return self.ERR_CANT_ROT_IF_NOT_UP - return self.ERR_OK + return Errors.ERR_CANT_ROT_IF_NOT_UP + return Errors.NO_ERR def go_forward(self): err_state = self._check_can_move() @@ -63,7 +55,7 @@ def go_forward(self): self.car_target += 1 if self.car_target > self.MAX_CAROUSEL: self.car_target = self.MIN_CAROUSEL - return self.NO_ERR + return Errors.NO_ERR def go_backward(self): err_state = self._check_can_move() @@ -72,28 +64,28 @@ def go_backward(self): self.car_target -= 1 if self.car_target < self.MIN_CAROUSEL: self.car_target = self.MAX_CAROUSEL - return self.NO_ERR + return Errors.NO_ERR def move_to(self, position, lower_arm): if self._csm.state == 'init': - return self.ERR_NOT_INITIALISED + return Errors.ERR_NOT_INITIALISED if (position < self.MIN_CAROUSEL) or (position > self.MAX_CAROUSEL): - return self.ERR_INV_DEST + return Errors.ERR_INV_DEST else: self.car_target = position self.arm_lowered = lower_arm - return self.NO_ERR + return Errors.NO_ERR def set_arm(self, lowered): if self._csm.state == 'init': - return self.ERR_NOT_INITIALISED + return Errors.ERR_NOT_INITIALISED if lowered == self.arm_lowered: if lowered: - return self.ERR_ARM_DROPPED + return Errors.ERR_ARM_DROPPED else: - return self.ERR_ARM_UP + return Errors.ERR_ARM_UP self.arm_lowered = lowered - return self.NO_ERR + return Errors.NO_ERR def get_arm_lowered(self): return self.arm_lowered @@ -101,5 +93,5 @@ def get_arm_lowered(self): def init(self): self.arm_lowered = False self.car_target = self.MIN_CAROUSEL - self.current_err = self.NO_ERR - return self.NO_ERR + self.current_err = Errors.NO_ERR + return Errors.NO_ERR diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py b/lewis_emulators/HRPD_Sample_changer/interfaces/HRPD_stream_interface.py similarity index 82% rename from lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py rename to lewis_emulators/HRPD_Sample_changer/interfaces/HRPD_stream_interface.py index 4b17b925..1fdfbb07 100644 --- a/lewis_emulators/HRPD_Sample_changer/interfaces/stream_interface.py +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/HRPD_stream_interface.py @@ -1,7 +1,9 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import Cmd, StreamAdapter +from ..states import Errors class HRPDSampleChangerStreamInterface(StreamAdapter): + protocol = "HRPD" commands = { Cmd("get_id", "^id$"), @@ -18,12 +20,18 @@ class HRPDSampleChangerStreamInterface(StreamAdapter): Cmd("read_variable", "^vr([0-9]{4})$", argument_mappings=[int]) } - # Labview VI expects a \r\n for out_terminator + error_codes = {Errors.NO_ERR: 0, + Errors.ERR_INV_DEST: 5, + Errors.ERR_NOT_INITIALISED: 6, + Errors.ERR_ARM_DROPPED: 7, + Errors.ERR_ARM_UP: 8, + Errors.ERR_CANT_ROT_IF_NOT_UP: 7} + in_terminator = "\r" out_terminator = "\r\n" def _check_error_code(self, code): - if code == self._device.NO_ERR: + if code == Errors.NO_ERR: return "ok" else: self._device.current_err = code @@ -45,7 +53,9 @@ def get_status(self): return_string += " 0 {:b}".format(self._device.is_moving()) - return_string += " {:2d}".format(int(self._device.current_err)) + return_error = int(self.error_codes[self._device.current_err]) + + return_string += " {:2d}".format(return_error) return_string += " {:2d}".format(int(self._device.car_pos)) return return_string diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/POLARIS_stream_interface.py b/lewis_emulators/HRPD_Sample_changer/interfaces/POLARIS_stream_interface.py new file mode 100644 index 00000000..710cf8ef --- /dev/null +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/POLARIS_stream_interface.py @@ -0,0 +1,78 @@ +from lewis.adapters.stream import Cmd, StreamAdapter +from ..states import Errors + + +class POLARISSampleChangerStreamInterface(StreamAdapter): + protocol = "POLARIS" + + commands = { + Cmd("get_id", "^id$"), + Cmd("get_position", "^po$"), + Cmd("get_status", "^st$"), + Cmd("go_back", "^bk$"), + Cmd("go_fwd", "^fw$"), + Cmd("halt", "^ht$"), + Cmd("initialise", "^in$"), + Cmd("lower_arm", "^lo$"), + Cmd("move_to", "^ma([0-9]{2})$", argument_mappings=[int]), + Cmd("move_to_without_lowering", "^mn([0-9]{2})$", argument_mappings=[int]), + Cmd("raise_arm", "^ra$"), + } + + error_codes = {Errors.NO_ERR: 0, + Errors.ERR_INV_DEST: 5, + Errors.ERR_NOT_INITIALISED: 6, + Errors.ERR_ARM_DROPPED: 7, + Errors.ERR_ARM_UP: 8, + Errors.ERR_CANT_ROT_IF_NOT_UP: 10} + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def get_id(self): + return "" + + def get_position(self): + return "Position = {:2d}".format(int(self._device.car_pos)) + + def get_status(self): + lowered = self._device.get_arm_lowered() + + # Based on testing with actual device, appears to be different than doc + return_string = "{0:b}01{1:b}{2:b}{3:b}0{4:b}" + return_string = return_string.format(not lowered, self._device.is_car_at_one(), not lowered, + lowered, self._device.is_moving()) + + return_string += "{:1d}".format(int(self._device.current_err)) + return_string += " {:2d}".format(int(self._device.car_pos)) + + return return_string + + def go_back(self): + self._device.go_backward() + + def go_fwd(self): + self._device.go_forward() + + def halt(self): + return "" + + def initialise(self): + self._device.init() + + def move_to(self, position): + self._device.move_to(position, True) + + def move_to_without_lowering(self, position): + self._device.move_to(position, False) + + def lower_arm(self): + self._device.set_arm(True) + + def raise_arm(self): + self._device.set_arm(False) + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + return "??" + diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py b/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py index 7bd11ab5..e69de29b 100644 --- a/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py +++ b/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py @@ -1,3 +0,0 @@ -from .stream_interface import HRPDSampleChangerStreamInterface - -__all__ = ['HRPDSampleChangerStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/HRPD_Sample_changer/states.py b/lewis_emulators/HRPD_Sample_changer/states.py index c404a367..e0af1f0e 100644 --- a/lewis_emulators/HRPD_Sample_changer/states.py +++ b/lewis_emulators/HRPD_Sample_changer/states.py @@ -2,6 +2,15 @@ from lewis.core.statemachine import State +class Errors(): + NO_ERR = 0 + ERR_INV_DEST = 5 + ERR_NOT_INITIALISED = 6 + ERR_ARM_DROPPED = 7 + ERR_ARM_UP = 8 + ERR_CANT_ROT_IF_NOT_UP = 10 + + class MovingState(State): def on_entry(self, dt): self._context.arm_lowered = False From 833aee4de54540d74293a522afca389a4ed59ebf Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Tue, 25 Apr 2017 16:44:48 +0100 Subject: [PATCH 0160/1466] Renamed rotating sample changer --- .../{HRPD_Sample_changer => rotating_sample_changer}/__init__.py | 0 .../{HRPD_Sample_changer => rotating_sample_changer}/device.py | 0 .../interfaces/HRPD_stream_interface.py | 0 .../interfaces/POLARIS_stream_interface.py | 0 .../interfaces/__init__.py | 0 .../{HRPD_Sample_changer => rotating_sample_changer}/states.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename lewis_emulators/{HRPD_Sample_changer => rotating_sample_changer}/__init__.py (100%) rename lewis_emulators/{HRPD_Sample_changer => rotating_sample_changer}/device.py (100%) rename lewis_emulators/{HRPD_Sample_changer => rotating_sample_changer}/interfaces/HRPD_stream_interface.py (100%) rename lewis_emulators/{HRPD_Sample_changer => rotating_sample_changer}/interfaces/POLARIS_stream_interface.py (100%) rename lewis_emulators/{HRPD_Sample_changer => rotating_sample_changer}/interfaces/__init__.py (100%) rename lewis_emulators/{HRPD_Sample_changer => rotating_sample_changer}/states.py (100%) diff --git a/lewis_emulators/HRPD_Sample_changer/__init__.py b/lewis_emulators/rotating_sample_changer/__init__.py similarity index 100% rename from lewis_emulators/HRPD_Sample_changer/__init__.py rename to lewis_emulators/rotating_sample_changer/__init__.py diff --git a/lewis_emulators/HRPD_Sample_changer/device.py b/lewis_emulators/rotating_sample_changer/device.py similarity index 100% rename from lewis_emulators/HRPD_Sample_changer/device.py rename to lewis_emulators/rotating_sample_changer/device.py diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/HRPD_stream_interface.py b/lewis_emulators/rotating_sample_changer/interfaces/HRPD_stream_interface.py similarity index 100% rename from lewis_emulators/HRPD_Sample_changer/interfaces/HRPD_stream_interface.py rename to lewis_emulators/rotating_sample_changer/interfaces/HRPD_stream_interface.py diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/POLARIS_stream_interface.py b/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py similarity index 100% rename from lewis_emulators/HRPD_Sample_changer/interfaces/POLARIS_stream_interface.py rename to lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py diff --git a/lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py b/lewis_emulators/rotating_sample_changer/interfaces/__init__.py similarity index 100% rename from lewis_emulators/HRPD_Sample_changer/interfaces/__init__.py rename to lewis_emulators/rotating_sample_changer/interfaces/__init__.py diff --git a/lewis_emulators/HRPD_Sample_changer/states.py b/lewis_emulators/rotating_sample_changer/states.py similarity index 100% rename from lewis_emulators/HRPD_Sample_changer/states.py rename to lewis_emulators/rotating_sample_changer/states.py From 5aab927febfac752d3212ebf63585be62d6df320 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Wed, 26 Apr 2017 14:00:26 +0100 Subject: [PATCH 0161/1466] Updated to better reflect POLARIS --- lewis_emulators/rotating_sample_changer/device.py | 3 ++- .../interfaces/POLARIS_stream_interface.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/rotating_sample_changer/device.py b/lewis_emulators/rotating_sample_changer/device.py index 906578b5..ebd99fda 100644 --- a/lewis_emulators/rotating_sample_changer/device.py +++ b/lewis_emulators/rotating_sample_changer/device.py @@ -8,7 +8,8 @@ class SimulatedSampleChanger(StateMachineDevice): MIN_CAROUSEL = 1 MAX_CAROUSEL = 20 - ARM_SPEED = 1.0/25.0 # Arm takes 25s to raise/lower (measured on actual device) + # ARM_SPEED = 1.0/25.0 Arm takes 25s to raise/lower (measured on HRPD) + # ARM_SPEED = 1.0/100.0 # Arm takes 100s to raise/lower (measured on POLARIS) CAR_SPEED = 1.0/6.0 # Carousel takes 6 seconds per position (measured on actual device) def _initialize_data(self): diff --git a/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py b/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py index 710cf8ef..58ad32cf 100644 --- a/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py +++ b/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py @@ -30,7 +30,7 @@ class POLARISSampleChangerStreamInterface(StreamAdapter): out_terminator = "\r\n" def get_id(self): - return "" + return "0001 0001 ISIS Polaris Sample Changer V" def get_position(self): return "Position = {:2d}".format(int(self._device.car_pos)) From 9eb03a49e2d6bf6492e6183256090f7604740caa Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Thu, 4 May 2017 17:51:37 +0100 Subject: [PATCH 0162/1466] Fixed exiting --- com2tcp/com2tcp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/com2tcp/com2tcp.py b/com2tcp/com2tcp.py index 047ccc5f..7ce7cd7f 100644 --- a/com2tcp/com2tcp.py +++ b/com2tcp/com2tcp.py @@ -51,8 +51,7 @@ def listen_to_tcp(tcp_conn, serial_conn): time.sleep(0.1) except (KeyboardInterrupt, SystemExit) as e: - sys.exit() - raise + pass finally: tcp_conn.close() serial_conn.close() From 9626c615c0e8c3f701001f19b96269163fb6f2b8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 10 May 2017 10:11:09 +0100 Subject: [PATCH 0163/1466] Add very basic emulator only supporting Q300 and C300 --- .../instron_stress_rig/__init__.py | 3 ++ lewis_emulators/instron_stress_rig/device.py | 43 +++++++++++++++++++ .../instron_stress_rig/interfaces/__init__.py | 3 ++ .../interfaces/stream_interface.py | 23 ++++++++++ lewis_emulators/instron_stress_rig/states.py | 18 ++++++++ 5 files changed, 90 insertions(+) create mode 100644 lewis_emulators/instron_stress_rig/__init__.py create mode 100644 lewis_emulators/instron_stress_rig/device.py create mode 100644 lewis_emulators/instron_stress_rig/interfaces/__init__.py create mode 100644 lewis_emulators/instron_stress_rig/interfaces/stream_interface.py create mode 100644 lewis_emulators/instron_stress_rig/states.py diff --git a/lewis_emulators/instron_stress_rig/__init__.py b/lewis_emulators/instron_stress_rig/__init__.py new file mode 100644 index 00000000..68fd3cac --- /dev/null +++ b/lewis_emulators/instron_stress_rig/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedInstron + +__all__ = ['SimulatedInstron'] diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py new file mode 100644 index 00000000..ba398288 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/device.py @@ -0,0 +1,43 @@ +from collections import OrderedDict +from states import DefaultInitState, DefaultStoppedState, DefaultStartedState +from lewis.devices import StateMachineDevice + + +class SimulatedInstron(StateMachineDevice): + + def _initialize_data(self): + """ Initialize all of the device's attributes """ + + + # When initialisation is complete, this is set to true and the device will enter a running state + self.ready = True + self._control_channel = 0 + + def _get_state_handlers(self): + return { + 'init': DefaultInitState(), + 'stopped': DefaultStoppedState(), + 'started': DefaultStartedState(), + } + + def _get_initial_state(self): + return 'init' + + def _get_transition_handlers(self): + return OrderedDict([ + (('init', 'stopped'), lambda: self.ready), + (('stopped', 'started'), lambda: self._started is True), + (('started', 'stopped'), lambda: self._started is False), + ]) + + def get_control_channel(self): + return self._control_channel + + def set_control_channel(self, channel): + self._control_channel = channel + + def start(self): + self._started = True + + def stop(self): + self._started = False diff --git a/lewis_emulators/instron_stress_rig/interfaces/__init__.py b/lewis_emulators/instron_stress_rig/interfaces/__init__.py new file mode 100644 index 00000000..f4d03ace --- /dev/null +++ b/lewis_emulators/instron_stress_rig/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import InstronStreamInterface + +__all__ = ['InstronStreamInterface'] diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py new file mode 100644 index 00000000..c1d0ad57 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -0,0 +1,23 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + + +class InstronStreamInterface(StreamAdapter): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("get_control_channel", "^Q300$"), + Cmd("set_control_channel", "^C300,([1-3])$"), + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + return str(error) + + def get_control_channel(self): + return self._device.get_control_channel() + + def set_control_channel(self, channel): + self._device.set_control_channel(channel) diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py new file mode 100644 index 00000000..edf5b101 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/states.py @@ -0,0 +1,18 @@ +from lewis.core.statemachine import State +from lewis.core import approaches + + +class DefaultInitState(State): + pass + + +class DefaultStoppedState(State): + def in_state(self, dt): + device = self._context + device.stop() + + +class DefaultStartedState(State): + def in_state(self, dt): + device = self._context + device.start() From a1d438ca7e7756924cb381f76585d96826c92ff6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 10 May 2017 16:03:09 +0100 Subject: [PATCH 0164/1466] Add watchdog status --- lewis_emulators/instron_stress_rig/device.py | 10 ++++++++-- .../instron_stress_rig/interfaces/stream_interface.py | 9 +++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index ba398288..80e82633 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -8,10 +8,10 @@ class SimulatedInstron(StateMachineDevice): def _initialize_data(self): """ Initialize all of the device's attributes """ - # When initialisation is complete, this is set to true and the device will enter a running state self.ready = True self._control_channel = 0 + self._watchdog_status = (0, 0) def _get_state_handlers(self): return { @@ -32,10 +32,16 @@ def _get_transition_handlers(self): def get_control_channel(self): return self._control_channel - + def set_control_channel(self, channel): self._control_channel = channel + def get_watchdog_status(self): + return self._watchdog_status + + def set_watchdog_status(self, enabled, status): + self._watchdog_status = (enabled, status) + def start(self): self._started = True diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index c1d0ad57..3ea2e93e 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -7,6 +7,8 @@ class InstronStreamInterface(StreamAdapter): commands = { Cmd("get_control_channel", "^Q300$"), Cmd("set_control_channel", "^C300,([1-3])$"), + Cmd("get_watchdog_status", "^Q904$"), + Cmd("set_watchdog_status", "^C904,([0-2]),([0-3])$"), } in_terminator = "\r\n" @@ -21,3 +23,10 @@ def get_control_channel(self): def set_control_channel(self, channel): self._device.set_control_channel(channel) + + def get_watchdog_status(self): + enabled, status = self._device.get_watchdog_status() + return "{},{}".format(enabled, status) + + def set_watchdog_status(self, cv1, cv2): + self._device.set_watchdog_status(cv1, cv2) From c688bdec7d4924ce543efecc64911fe806d2d7a6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 10 May 2017 17:01:41 +0100 Subject: [PATCH 0165/1466] Add control mode --- lewis_emulators/instron_stress_rig/device.py | 7 +++++++ .../instron_stress_rig/interfaces/stream_interface.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 80e82633..59b71b5d 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -12,6 +12,7 @@ def _initialize_data(self): self.ready = True self._control_channel = 0 self._watchdog_status = (0, 0) + self._control_mode = 0 def _get_state_handlers(self): return { @@ -42,6 +43,12 @@ def get_watchdog_status(self): def set_watchdog_status(self, enabled, status): self._watchdog_status = (enabled, status) + def get_control_mode(self): + return self._control_mode + + def set_control_mode(self, mode): + self._control_mode = mode + def start(self): self._started = True diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 3ea2e93e..1b26ab33 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -9,6 +9,8 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_control_channel", "^C300,([1-3])$"), Cmd("get_watchdog_status", "^Q904$"), Cmd("set_watchdog_status", "^C904,([0-2]),([0-3])$"), + Cmd("get_control_mode", "^Q909$"), + Cmd("set_control_mode", "^P909,([0-1])$"), } in_terminator = "\r\n" @@ -30,3 +32,9 @@ def get_watchdog_status(self): def set_watchdog_status(self, cv1, cv2): self._device.set_watchdog_status(cv1, cv2) + + def get_control_mode(self): + return self._device.get_control_mode() + + def set_control_mode(self, mode): + self._device.set_control_mode(mode) From e805bbcc581a3bbb036e278971cf502e65cf7ea0 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 12 May 2017 11:20:49 +0100 Subject: [PATCH 0166/1466] Add status command, always return SYSTEM OK for now --- .../instron_stress_rig/interfaces/stream_interface.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 1b26ab33..e77be039 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -11,6 +11,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_watchdog_status", "^C904,([0-2]),([0-3])$"), Cmd("get_control_mode", "^Q909$"), Cmd("set_control_mode", "^P909,([0-1])$"), + Cmd("get_status", "^Q22$"), } in_terminator = "\r\n" @@ -38,3 +39,6 @@ def get_control_mode(self): def set_control_mode(self, mode): self._device.set_control_mode(mode) + + def get_status(self): + return 7680 From 5171ca04792f3ec513b2c3d1e90a6402aa6c2c34 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 12 May 2017 17:31:47 +0100 Subject: [PATCH 0167/1466] Add panic stop --- lewis_emulators/instron_stress_rig/device.py | 9 +++++++++ .../interfaces/stream_interface.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 59b71b5d..bc4284a8 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -13,6 +13,7 @@ def _initialize_data(self): self._control_channel = 0 self._watchdog_status = (0, 0) self._control_mode = 0 + self._actuator_status = 0 def _get_state_handlers(self): return { @@ -54,3 +55,11 @@ def start(self): def stop(self): self._started = False + + def get_actuator_status(self): + return self._actuator_status + + def set_actuator_status(self, status): + print "Actuator status was " + str(self._actuator_status) + self._actuator_status = int(status) + print "Now it is " + str(status) diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index e77be039..35483eb6 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -12,6 +12,9 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_control_mode", "^Q909$"), Cmd("set_control_mode", "^P909,([0-1])$"), Cmd("get_status", "^Q22$"), + Cmd("arbitrary_command", "^(_[a-zA-Z0-9]*)$"), + Cmd("get_actuator_status", "^Q23$"), + Cmd("set_actuator_status", "^C23,([0-1])$"), } in_terminator = "\r\n" @@ -42,3 +45,15 @@ def set_control_mode(self, mode): def get_status(self): return 7680 + + def arbitrary_command(self, command): + if command.startswith("Q"): + return "Arb_com_response_" + str(command) + else: + return + + def get_actuator_status(self): + return self._device.get_actuator_status() + + def set_actuator_status(self, mode): + self._device.set_actuator_status(mode) From d118faf290daf3e9013b9df529e9500fa1fdec7a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 15 May 2017 17:07:56 +0100 Subject: [PATCH 0168/1466] Add stuff to emulator --- lewis_emulators/instron_stress_rig/device.py | 15 +++++++++++++-- .../interfaces/stream_interface.py | 15 ++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index bc4284a8..dcbda595 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -14,6 +14,11 @@ def _initialize_data(self): self._watchdog_status = (0, 0) self._control_mode = 0 self._actuator_status = 0 + self._movement_type = 2 + + def raise_exception_if_cannot_write(self): + if int(self._control_mode) != int(1): + raise Exception("Not in the correct control mode to execute that command! Current control mode is " + str(self._control_mode)) def _get_state_handlers(self): return { @@ -60,6 +65,12 @@ def get_actuator_status(self): return self._actuator_status def set_actuator_status(self, status): - print "Actuator status was " + str(self._actuator_status) + self.raise_exception_if_cannot_write() self._actuator_status = int(status) - print "Now it is " + str(status) + + def get_movement_type(self): + return self._movement_type + + def set_movement_type(self, mov_type): + self.raise_exception_if_cannot_write() + self._movement_type = mov_type diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 35483eb6..4c170010 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -12,9 +12,11 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_control_mode", "^Q909$"), Cmd("set_control_mode", "^P909,([0-1])$"), Cmd("get_status", "^Q22$"), - Cmd("arbitrary_command", "^(_[a-zA-Z0-9]*)$"), + Cmd("arbitrary_command", "^([a-z]*)$"), Cmd("get_actuator_status", "^Q23$"), Cmd("set_actuator_status", "^C23,([0-1])$"), + Cmd("get_movement_type", "^Q1$"), + Cmd("set_movement_type", "^C1,([0-3])$"), } in_terminator = "\r\n" @@ -47,13 +49,16 @@ def get_status(self): return 7680 def arbitrary_command(self, command): - if command.startswith("Q"): - return "Arb_com_response_" + str(command) - else: - return + return "Arb_com_response_" + str(command) def get_actuator_status(self): return self._device.get_actuator_status() def set_actuator_status(self, mode): self._device.set_actuator_status(mode) + + def get_movement_type(self): + return self._device.get_movement_type() + + def set_movement_type(self, mov_type): + self._device.set_movement_type(mov_type) From 728efc708993b3f9b61cd6fb6b66ac626e85404f Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 16 May 2017 14:25:28 +0100 Subject: [PATCH 0169/1466] Add AMInt2-L emmulator and factor out command builder. --- lewis_emulators/amint2l/__init__.py | 3 + lewis_emulators/amint2l/constants.py | 8 ++ lewis_emulators/amint2l/device.py | 68 +++++++++++ .../amint2l/interfaces/__init__.py | 3 + .../amint2l/interfaces/stream_interface.py | 49 ++++++++ lewis_emulators/amint2l/states.py | 12 ++ .../interfaces/stream_interface.py | 87 +------------- lewis_emulators/utils/__init__.py | 0 lewis_emulators/utils/command_builder.py | 111 ++++++++++++++++++ 9 files changed, 256 insertions(+), 85 deletions(-) create mode 100644 lewis_emulators/amint2l/__init__.py create mode 100644 lewis_emulators/amint2l/constants.py create mode 100644 lewis_emulators/amint2l/device.py create mode 100644 lewis_emulators/amint2l/interfaces/__init__.py create mode 100644 lewis_emulators/amint2l/interfaces/stream_interface.py create mode 100644 lewis_emulators/amint2l/states.py create mode 100644 lewis_emulators/utils/__init__.py create mode 100644 lewis_emulators/utils/command_builder.py diff --git a/lewis_emulators/amint2l/__init__.py b/lewis_emulators/amint2l/__init__.py new file mode 100644 index 00000000..87d46bf2 --- /dev/null +++ b/lewis_emulators/amint2l/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedAmint2l + +__all__ = ['SimulatedAmint2l'] diff --git a/lewis_emulators/amint2l/constants.py b/lewis_emulators/amint2l/constants.py new file mode 100644 index 00000000..b48d98b2 --- /dev/null +++ b/lewis_emulators/amint2l/constants.py @@ -0,0 +1,8 @@ +""" +Constants associated with device +""" + +# Address of the AM Int2-l +ADDRESS_HIGH = "A" +ADDRESS_LOW = "B" +ADDRESS = "{0}{1}".format(ADDRESS_HIGH, ADDRESS_LOW) diff --git a/lewis_emulators/amint2l/device.py b/lewis_emulators/amint2l/device.py new file mode 100644 index 00000000..4f869c5d --- /dev/null +++ b/lewis_emulators/amint2l/device.py @@ -0,0 +1,68 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedAmint2l(StateMachineDevice): + """ + Simulated AM Int2-L pressure transducer + """ + + def _initialize_data(self): + + """ + + Sets the initial state of the device + + """ + + self._pressure = 2.0 + + def _get_state_handlers(self): + + """ + + Returns: states and their names + + """ + return { + DefaultState.NAME: DefaultState(), + } + + def _get_initial_state(self): + """ + + Returns: the name of the initial state + + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + + """ + + Returns: the state transitions + + """ + + return OrderedDict([ + ]) + + @property + def pressure(self): + """ + + Returns: the pressure + + """ + return self._pressure + + @pressure.setter + def pressure(self, pressure): + """ + + :param pressure: set the pressure + :return: + """ + self._pressure = pressure diff --git a/lewis_emulators/amint2l/interfaces/__init__.py b/lewis_emulators/amint2l/interfaces/__init__.py new file mode 100644 index 00000000..3108e653 --- /dev/null +++ b/lewis_emulators/amint2l/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Amint2lStreamInterface + +__all__ = ['Amint2lStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py new file mode 100644 index 00000000..f6705918 --- /dev/null +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -0,0 +1,49 @@ +import re + +from lewis.adapters.stream import StreamAdapter, Cmd + +from lewis_emulators.amint2l.constants import ADDRESS_HIGH, ADDRESS_LOW +from lewis_emulators.utils.command_builder import CmdBuilder + + +class Amint2lStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + CmdBuilder("get_pressure").stx(). + escape("{address_high}{address_low}r".format(address_high=ADDRESS_HIGH, address_low=ADDRESS_LOW)).build() + } + + in_terminator = chr(3) + out_terminator = chr(3) + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def get_pressure(self): + + """ + Gets the current pressure + + Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected + + """ + + if self._device.pressure is None: + return None + else: + try: + return "{stx}{pressure:+8.3f}".format(stx=chr(2), pressure=self._device.pressure) + except ValueError: + # pressure contains string probably OR (over range) or UR (under range) + return "{stx}{pressure:8s}".format(stx=chr(2), pressure=self._device.pressure) diff --git a/lewis_emulators/amint2l/states.py b/lewis_emulators/amint2l/states.py new file mode 100644 index 00000000..d9e8716b --- /dev/null +++ b/lewis_emulators/amint2l/states.py @@ -0,0 +1,12 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + + """ + NAME = 'Default' + + + diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index c216b304..0517b517 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -1,94 +1,11 @@ import re -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamAdapter from lewis_emulators.neocera_ltc21.constants import HEATER_INDEX, CONTROL_TYPE_MAX, CONTROL_TYPE_MIN, ANALOG_INDEX from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState - - -class CmdBuilder(object): - """ - Build a command for the stream adapter - """ - - def __init__(self, target_method, arg_sep=",", ignore=""): - """ - Create a builder. Use build to create the final objecy - Args: - target_method: name of the method target to call when the reg ex matches - arg_sep: seperators between the arguments - ignore: set of characters to ignore between text and arguments - - Returns: - - """ - self._target_method = target_method - self._arg_sep = arg_sep - self._current_sep = "" - self._ignore = "[{0}]*".format(ignore) - self._reg_ex = self._ignore - - def escape(self, text): - """ - Add some text to the regex which is esacped - Args: - text: text to add - - Returns: builder - - """ - self._reg_ex += re.escape(text) + self._ignore - return self - - def arg(self, arg_regex): - """ - Add an argument to the command - Args: - arg_regex: regex for the argument (capture group will be added) - - Returns: builder - - """ - self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore - self._current_sep = self._arg_sep - return self - - def float(self): - """ - Add a float argument - Returns: builder - - """ - return self.arg(r"[+-]?\d+\.?\d*") - - def digit(self): - """ - Add a single digit argument - Returns: builder - - """ - return self.arg(r"\d") - - def int(self): - """ - Add an integer argument - Returns: builder - - """ - return self.arg(r"\d+") - - def build(self, *args, **kwargs): - """ - Builds the CMd object based on the target and regular expression - Args: - *args: arguments to pass to Cmd constructor - **kwargs: key word arguments to pass to Cmd constructor - - Returns: Cmd object - - """ - return Cmd(self._target_method, self._reg_ex, *args, **kwargs) +from lewis_emulators.utils.command_builder import CmdBuilder class NeoceraStreamInterface(StreamAdapter): diff --git a/lewis_emulators/utils/__init__.py b/lewis_emulators/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py new file mode 100644 index 00000000..65c8eebc --- /dev/null +++ b/lewis_emulators/utils/command_builder.py @@ -0,0 +1,111 @@ +import re + +from lewis.adapters.stream import Cmd + + +class CmdBuilder(object): + """ + Build a command for the stream adapter + """ + + def __init__(self, target_method, arg_sep=",", ignore=""): + """ + Create a builder. Use build to create the final objecy + Args: + target_method: name of the method target to call when the reg ex matches + arg_sep: seperators between the arguments + ignore: set of characters to ignore between text and arguments + + Returns: + + """ + self._target_method = target_method + self._arg_sep = arg_sep + self._current_sep = "" + self._ignore = "[{0}]*".format(ignore) + self._reg_ex = self._ignore + + def escape(self, text): + """ + Add some text to the regex which is esacped + Args: + text: text to add + + Returns: builder + + """ + self._reg_ex += re.escape(text) + self._ignore + return self + + def arg(self, arg_regex): + """ + Add an argument to the command + Args: + arg_regex: regex for the argument (capture group will be added) + + Returns: builder + + """ + self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore + self._current_sep = self._arg_sep + return self + + def float(self): + """ + Add a float argument + Returns: builder + + """ + return self.arg(r"[+-]?\d+\.?\d*") + + def digit(self): + """ + Add a single digit argument + Returns: builder + + """ + return self.arg(r"\d") + + def int(self): + """ + Add an integer argument + Returns: builder + + """ + return self.arg(r"\d+") + + def build(self, *args, **kwargs): + """ + Builds the CMd object based on the target and regular expression + Args: + *args: arguments to pass to Cmd constructor + **kwargs: key word arguments to pass to Cmd constructor + + Returns: Cmd object + + """ + return Cmd(self._target_method, self._reg_ex, *args, **kwargs) + + def add_ascii_character(self, char_number): + """ + Add a single character based on its integer value, e.g. 49 is a + :param char_number: character number + :return: self + """ + self._reg_ex += chr(char_number) + + return self + + def stx(self): + """ + Add the STX character (0x2) to the string + :return: builder + """ + return self.add_ascii_character(2) + + def etx(self): + """ + Add the ETX character (0xe) to the string + :return: builder + """ + return self.add_ascii_character(3) From b0da631697418d04992c25d9e1006845c7a1b395 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 17 May 2017 12:26:20 +0100 Subject: [PATCH 0170/1466] Real reading is left-justified and may be followed by spaces --- lewis_emulators/CCD100/interfaces/stream_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/CCD100/interfaces/stream_interface.py b/lewis_emulators/CCD100/interfaces/stream_interface.py index 1bdbadf3..2bbda146 100644 --- a/lewis_emulators/CCD100/interfaces/stream_interface.py +++ b/lewis_emulators/CCD100/interfaces/stream_interface.py @@ -44,8 +44,9 @@ def set_units(self, new_units): return self.create_response(UNITS_COMM) def get_reading(self): - rand = random.random() * 10.0 - data_str = "READ: {:0.3f}".format(rand) + ";" + str(self._device.setpoint_mode) + rand = random.random() * 100.0 + min_width = 10 + data_str = "READ:" + "{:0.3f}".format(rand).ljust(min_width) + ";" + str(self._device.setpoint_mode) return self.create_response(READING_COMM + " ", data=data_str) def handle_error(self, request, error): From 98b10c485ca03dbafb25f8dc4f4185c384d17fde Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 17 May 2017 14:17:27 +0100 Subject: [PATCH 0171/1466] if ignore is empty do not use it --- lewis_emulators/amint2l/constants.py | 8 -------- lewis_emulators/amint2l/device.py | 1 + .../amint2l/interfaces/stream_interface.py | 17 ++++++++--------- lewis_emulators/utils/command_builder.py | 5 ++++- 4 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 lewis_emulators/amint2l/constants.py diff --git a/lewis_emulators/amint2l/constants.py b/lewis_emulators/amint2l/constants.py deleted file mode 100644 index b48d98b2..00000000 --- a/lewis_emulators/amint2l/constants.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Constants associated with device -""" - -# Address of the AM Int2-l -ADDRESS_HIGH = "A" -ADDRESS_LOW = "B" -ADDRESS = "{0}{1}".format(ADDRESS_HIGH, ADDRESS_LOW) diff --git a/lewis_emulators/amint2l/device.py b/lewis_emulators/amint2l/device.py index 4f869c5d..b4d5dc83 100644 --- a/lewis_emulators/amint2l/device.py +++ b/lewis_emulators/amint2l/device.py @@ -18,6 +18,7 @@ def _initialize_data(self): """ self._pressure = 2.0 + self.address = "AB" def _get_state_handlers(self): diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py index f6705918..4235f880 100644 --- a/lewis_emulators/amint2l/interfaces/stream_interface.py +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -1,8 +1,4 @@ -import re - -from lewis.adapters.stream import StreamAdapter, Cmd - -from lewis_emulators.amint2l.constants import ADDRESS_HIGH, ADDRESS_LOW +from lewis.adapters.stream import StreamAdapter from lewis_emulators.utils.command_builder import CmdBuilder @@ -12,8 +8,7 @@ class Amint2lStreamInterface(StreamAdapter): """ commands = { - CmdBuilder("get_pressure").stx(). - escape("{address_high}{address_low}r".format(address_high=ADDRESS_HIGH, address_low=ADDRESS_LOW)).build() + CmdBuilder("get_pressure").stx().arg("[A-Fa-f0-9]+").escape("r").build() } in_terminator = chr(3) @@ -30,15 +25,19 @@ def handle_error(self, request, error): """ print "An error occurred at request " + repr(request) + ": " + repr(error) - def get_pressure(self): + def get_pressure(self, address): """ Gets the current pressure + :param address: address of request Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected """ - + if address.upper() != self._device.address.upper(): + print "unknown address {0}".format(address) + return None + print str(self._device.pressure) if self._device.pressure is None: return None else: diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 65c8eebc..f851476e 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -22,7 +22,10 @@ def __init__(self, target_method, arg_sep=",", ignore=""): self._target_method = target_method self._arg_sep = arg_sep self._current_sep = "" - self._ignore = "[{0}]*".format(ignore) + if ignore is None or ignore == "": + self._ignore = "" + else: + self._ignore = "[{0}]*".format(ignore) self._reg_ex = self._ignore def escape(self, text): From f37150fd45afcfadb79af4c717581ed2931b73e7 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 16 May 2017 14:25:28 +0100 Subject: [PATCH 0172/1466] Add AMInt2-L emmulator and factor out command builder. --- lewis_emulators/amint2l/__init__.py | 3 + lewis_emulators/amint2l/constants.py | 8 ++ lewis_emulators/amint2l/device.py | 68 +++++++++++ .../amint2l/interfaces/__init__.py | 3 + .../amint2l/interfaces/stream_interface.py | 49 ++++++++ lewis_emulators/amint2l/states.py | 12 ++ .../interfaces/stream_interface.py | 87 +------------- lewis_emulators/utils/__init__.py | 0 lewis_emulators/utils/command_builder.py | 111 ++++++++++++++++++ 9 files changed, 256 insertions(+), 85 deletions(-) create mode 100644 lewis_emulators/amint2l/__init__.py create mode 100644 lewis_emulators/amint2l/constants.py create mode 100644 lewis_emulators/amint2l/device.py create mode 100644 lewis_emulators/amint2l/interfaces/__init__.py create mode 100644 lewis_emulators/amint2l/interfaces/stream_interface.py create mode 100644 lewis_emulators/amint2l/states.py create mode 100644 lewis_emulators/utils/__init__.py create mode 100644 lewis_emulators/utils/command_builder.py diff --git a/lewis_emulators/amint2l/__init__.py b/lewis_emulators/amint2l/__init__.py new file mode 100644 index 00000000..87d46bf2 --- /dev/null +++ b/lewis_emulators/amint2l/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedAmint2l + +__all__ = ['SimulatedAmint2l'] diff --git a/lewis_emulators/amint2l/constants.py b/lewis_emulators/amint2l/constants.py new file mode 100644 index 00000000..b48d98b2 --- /dev/null +++ b/lewis_emulators/amint2l/constants.py @@ -0,0 +1,8 @@ +""" +Constants associated with device +""" + +# Address of the AM Int2-l +ADDRESS_HIGH = "A" +ADDRESS_LOW = "B" +ADDRESS = "{0}{1}".format(ADDRESS_HIGH, ADDRESS_LOW) diff --git a/lewis_emulators/amint2l/device.py b/lewis_emulators/amint2l/device.py new file mode 100644 index 00000000..4f869c5d --- /dev/null +++ b/lewis_emulators/amint2l/device.py @@ -0,0 +1,68 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedAmint2l(StateMachineDevice): + """ + Simulated AM Int2-L pressure transducer + """ + + def _initialize_data(self): + + """ + + Sets the initial state of the device + + """ + + self._pressure = 2.0 + + def _get_state_handlers(self): + + """ + + Returns: states and their names + + """ + return { + DefaultState.NAME: DefaultState(), + } + + def _get_initial_state(self): + """ + + Returns: the name of the initial state + + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + + """ + + Returns: the state transitions + + """ + + return OrderedDict([ + ]) + + @property + def pressure(self): + """ + + Returns: the pressure + + """ + return self._pressure + + @pressure.setter + def pressure(self, pressure): + """ + + :param pressure: set the pressure + :return: + """ + self._pressure = pressure diff --git a/lewis_emulators/amint2l/interfaces/__init__.py b/lewis_emulators/amint2l/interfaces/__init__.py new file mode 100644 index 00000000..3108e653 --- /dev/null +++ b/lewis_emulators/amint2l/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Amint2lStreamInterface + +__all__ = ['Amint2lStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py new file mode 100644 index 00000000..f6705918 --- /dev/null +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -0,0 +1,49 @@ +import re + +from lewis.adapters.stream import StreamAdapter, Cmd + +from lewis_emulators.amint2l.constants import ADDRESS_HIGH, ADDRESS_LOW +from lewis_emulators.utils.command_builder import CmdBuilder + + +class Amint2lStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + CmdBuilder("get_pressure").stx(). + escape("{address_high}{address_low}r".format(address_high=ADDRESS_HIGH, address_low=ADDRESS_LOW)).build() + } + + in_terminator = chr(3) + out_terminator = chr(3) + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def get_pressure(self): + + """ + Gets the current pressure + + Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected + + """ + + if self._device.pressure is None: + return None + else: + try: + return "{stx}{pressure:+8.3f}".format(stx=chr(2), pressure=self._device.pressure) + except ValueError: + # pressure contains string probably OR (over range) or UR (under range) + return "{stx}{pressure:8s}".format(stx=chr(2), pressure=self._device.pressure) diff --git a/lewis_emulators/amint2l/states.py b/lewis_emulators/amint2l/states.py new file mode 100644 index 00000000..d9e8716b --- /dev/null +++ b/lewis_emulators/amint2l/states.py @@ -0,0 +1,12 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + + """ + NAME = 'Default' + + + diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index c216b304..0517b517 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -1,94 +1,11 @@ import re -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamAdapter from lewis_emulators.neocera_ltc21.constants import HEATER_INDEX, CONTROL_TYPE_MAX, CONTROL_TYPE_MIN, ANALOG_INDEX from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors from lewis_emulators.neocera_ltc21.states import MonitorState, ControlState - - -class CmdBuilder(object): - """ - Build a command for the stream adapter - """ - - def __init__(self, target_method, arg_sep=",", ignore=""): - """ - Create a builder. Use build to create the final objecy - Args: - target_method: name of the method target to call when the reg ex matches - arg_sep: seperators between the arguments - ignore: set of characters to ignore between text and arguments - - Returns: - - """ - self._target_method = target_method - self._arg_sep = arg_sep - self._current_sep = "" - self._ignore = "[{0}]*".format(ignore) - self._reg_ex = self._ignore - - def escape(self, text): - """ - Add some text to the regex which is esacped - Args: - text: text to add - - Returns: builder - - """ - self._reg_ex += re.escape(text) + self._ignore - return self - - def arg(self, arg_regex): - """ - Add an argument to the command - Args: - arg_regex: regex for the argument (capture group will be added) - - Returns: builder - - """ - self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore - self._current_sep = self._arg_sep - return self - - def float(self): - """ - Add a float argument - Returns: builder - - """ - return self.arg(r"[+-]?\d+\.?\d*") - - def digit(self): - """ - Add a single digit argument - Returns: builder - - """ - return self.arg(r"\d") - - def int(self): - """ - Add an integer argument - Returns: builder - - """ - return self.arg(r"\d+") - - def build(self, *args, **kwargs): - """ - Builds the CMd object based on the target and regular expression - Args: - *args: arguments to pass to Cmd constructor - **kwargs: key word arguments to pass to Cmd constructor - - Returns: Cmd object - - """ - return Cmd(self._target_method, self._reg_ex, *args, **kwargs) +from lewis_emulators.utils.command_builder import CmdBuilder class NeoceraStreamInterface(StreamAdapter): diff --git a/lewis_emulators/utils/__init__.py b/lewis_emulators/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py new file mode 100644 index 00000000..65c8eebc --- /dev/null +++ b/lewis_emulators/utils/command_builder.py @@ -0,0 +1,111 @@ +import re + +from lewis.adapters.stream import Cmd + + +class CmdBuilder(object): + """ + Build a command for the stream adapter + """ + + def __init__(self, target_method, arg_sep=",", ignore=""): + """ + Create a builder. Use build to create the final objecy + Args: + target_method: name of the method target to call when the reg ex matches + arg_sep: seperators between the arguments + ignore: set of characters to ignore between text and arguments + + Returns: + + """ + self._target_method = target_method + self._arg_sep = arg_sep + self._current_sep = "" + self._ignore = "[{0}]*".format(ignore) + self._reg_ex = self._ignore + + def escape(self, text): + """ + Add some text to the regex which is esacped + Args: + text: text to add + + Returns: builder + + """ + self._reg_ex += re.escape(text) + self._ignore + return self + + def arg(self, arg_regex): + """ + Add an argument to the command + Args: + arg_regex: regex for the argument (capture group will be added) + + Returns: builder + + """ + self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore + self._current_sep = self._arg_sep + return self + + def float(self): + """ + Add a float argument + Returns: builder + + """ + return self.arg(r"[+-]?\d+\.?\d*") + + def digit(self): + """ + Add a single digit argument + Returns: builder + + """ + return self.arg(r"\d") + + def int(self): + """ + Add an integer argument + Returns: builder + + """ + return self.arg(r"\d+") + + def build(self, *args, **kwargs): + """ + Builds the CMd object based on the target and regular expression + Args: + *args: arguments to pass to Cmd constructor + **kwargs: key word arguments to pass to Cmd constructor + + Returns: Cmd object + + """ + return Cmd(self._target_method, self._reg_ex, *args, **kwargs) + + def add_ascii_character(self, char_number): + """ + Add a single character based on its integer value, e.g. 49 is a + :param char_number: character number + :return: self + """ + self._reg_ex += chr(char_number) + + return self + + def stx(self): + """ + Add the STX character (0x2) to the string + :return: builder + """ + return self.add_ascii_character(2) + + def etx(self): + """ + Add the ETX character (0xe) to the string + :return: builder + """ + return self.add_ascii_character(3) From f064fb7a3938d0f472a7d91bab9c5ffd11a07dba Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 17 May 2017 14:17:27 +0100 Subject: [PATCH 0173/1466] if ignore is empty do not use it --- lewis_emulators/amint2l/constants.py | 8 -------- lewis_emulators/amint2l/device.py | 1 + .../amint2l/interfaces/stream_interface.py | 17 ++++++++--------- lewis_emulators/utils/command_builder.py | 5 ++++- 4 files changed, 13 insertions(+), 18 deletions(-) delete mode 100644 lewis_emulators/amint2l/constants.py diff --git a/lewis_emulators/amint2l/constants.py b/lewis_emulators/amint2l/constants.py deleted file mode 100644 index b48d98b2..00000000 --- a/lewis_emulators/amint2l/constants.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Constants associated with device -""" - -# Address of the AM Int2-l -ADDRESS_HIGH = "A" -ADDRESS_LOW = "B" -ADDRESS = "{0}{1}".format(ADDRESS_HIGH, ADDRESS_LOW) diff --git a/lewis_emulators/amint2l/device.py b/lewis_emulators/amint2l/device.py index 4f869c5d..b4d5dc83 100644 --- a/lewis_emulators/amint2l/device.py +++ b/lewis_emulators/amint2l/device.py @@ -18,6 +18,7 @@ def _initialize_data(self): """ self._pressure = 2.0 + self.address = "AB" def _get_state_handlers(self): diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py index f6705918..4235f880 100644 --- a/lewis_emulators/amint2l/interfaces/stream_interface.py +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -1,8 +1,4 @@ -import re - -from lewis.adapters.stream import StreamAdapter, Cmd - -from lewis_emulators.amint2l.constants import ADDRESS_HIGH, ADDRESS_LOW +from lewis.adapters.stream import StreamAdapter from lewis_emulators.utils.command_builder import CmdBuilder @@ -12,8 +8,7 @@ class Amint2lStreamInterface(StreamAdapter): """ commands = { - CmdBuilder("get_pressure").stx(). - escape("{address_high}{address_low}r".format(address_high=ADDRESS_HIGH, address_low=ADDRESS_LOW)).build() + CmdBuilder("get_pressure").stx().arg("[A-Fa-f0-9]+").escape("r").build() } in_terminator = chr(3) @@ -30,15 +25,19 @@ def handle_error(self, request, error): """ print "An error occurred at request " + repr(request) + ": " + repr(error) - def get_pressure(self): + def get_pressure(self, address): """ Gets the current pressure + :param address: address of request Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected """ - + if address.upper() != self._device.address.upper(): + print "unknown address {0}".format(address) + return None + print str(self._device.pressure) if self._device.pressure is None: return None else: diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 65c8eebc..f851476e 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -22,7 +22,10 @@ def __init__(self, target_method, arg_sep=",", ignore=""): self._target_method = target_method self._arg_sep = arg_sep self._current_sep = "" - self._ignore = "[{0}]*".format(ignore) + if ignore is None or ignore == "": + self._ignore = "" + else: + self._ignore = "[{0}]*".format(ignore) self._reg_ex = self._ignore def escape(self, text): From 1fafbe97b47c73eba45464dabc14bb7da82999ea Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 19 May 2017 16:02:37 +0100 Subject: [PATCH 0174/1466] push uncommitted changes --- lewis_emulators/instron_stress_rig/device.py | 34 +++++++++++++------- lewis_emulators/instron_stress_rig/states.py | 17 ++++------ 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index dcbda595..06e1a546 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultInitState, DefaultStoppedState, DefaultStartedState +from states import DefaultState from lewis.devices import StateMachineDevice @@ -15,27 +15,28 @@ def _initialize_data(self): self._control_mode = 0 self._actuator_status = 0 self._movement_type = 2 + self.current_time = 0 + self.watchdog_refresh_time = 0 + + # Mode 0 = Ramp waveform + # Mode 1 = Random waveform + self._waveform_mode = 0 + def raise_exception_if_cannot_write(self): if int(self._control_mode) != int(1): - raise Exception("Not in the correct control mode to execute that command! Current control mode is " + str(self._control_mode)) + raise Exception("Not in the correct control mode to execute that command!") def _get_state_handlers(self): return { - 'init': DefaultInitState(), - 'stopped': DefaultStoppedState(), - 'started': DefaultStartedState(), + 'default': DefaultState(), } def _get_initial_state(self): - return 'init' + return 'default' def _get_transition_handlers(self): - return OrderedDict([ - (('init', 'stopped'), lambda: self.ready), - (('stopped', 'started'), lambda: self._started is True), - (('started', 'stopped'), lambda: self._started is False), - ]) + return OrderedDict([]) def get_control_channel(self): return self._control_channel @@ -48,6 +49,7 @@ def get_watchdog_status(self): def set_watchdog_status(self, enabled, status): self._watchdog_status = (enabled, status) + self.watchdog_refresh_time = self.current_time def get_control_mode(self): return self._control_mode @@ -73,4 +75,12 @@ def get_movement_type(self): def set_movement_type(self, mov_type): self.raise_exception_if_cannot_write() - self._movement_type = mov_type + + if self._waveform_mode == 0: + self._movement_type = mov_type + else: + self._movement_type = mov_type + 3 + + def set_current_time(self, dt): + self.current_time = dt + diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index edf5b101..e71bf2d7 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -1,18 +1,13 @@ from lewis.core.statemachine import State -from lewis.core import approaches -class DefaultInitState(State): - pass - - -class DefaultStoppedState(State): +class DefaultState(State): def in_state(self, dt): device = self._context - device.stop() + device.set_current_time(dt) + + if device.watchdog_refresh_time + 3 < dt: + print "Watchdog time expired, going back to front panel control mode" + device.set_control_mode(0) -class DefaultStartedState(State): - def in_state(self, dt): - device = self._context - device.start() From 87a5fe3304eeec3bee43997388543fabd44e7df8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 22 May 2017 09:45:15 +0100 Subject: [PATCH 0175/1466] Do data formatting in stream_interface rather than device --- lewis_emulators/instron_stress_rig/device.py | 2 +- .../instron_stress_rig/interfaces/stream_interface.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 06e1a546..a0b0638a 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -24,7 +24,7 @@ def _initialize_data(self): def raise_exception_if_cannot_write(self): - if int(self._control_mode) != int(1): + if self._control_mode != 1: raise Exception("Not in the correct control mode to execute that command!") def _get_state_handlers(self): diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 4c170010..1152ff8e 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -30,20 +30,20 @@ def get_control_channel(self): return self._device.get_control_channel() def set_control_channel(self, channel): - self._device.set_control_channel(channel) + self._device.set_control_channel(int(channel)) def get_watchdog_status(self): enabled, status = self._device.get_watchdog_status() return "{},{}".format(enabled, status) def set_watchdog_status(self, cv1, cv2): - self._device.set_watchdog_status(cv1, cv2) + self._device.set_watchdog_status(int(cv1), int(cv2)) def get_control_mode(self): return self._device.get_control_mode() def set_control_mode(self, mode): - self._device.set_control_mode(mode) + self._device.set_control_mode(int(mode)) def get_status(self): return 7680 @@ -55,10 +55,10 @@ def get_actuator_status(self): return self._device.get_actuator_status() def set_actuator_status(self, mode): - self._device.set_actuator_status(mode) + self._device.set_actuator_status(int(mode)) def get_movement_type(self): return self._device.get_movement_type() def set_movement_type(self, mov_type): - self._device.set_movement_type(mov_type) + self._device.set_movement_type(int(mov_type)) From 8c4d88a359944435e02a6a1ebdc68f18d5e51fad Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 23 May 2017 12:02:39 +0100 Subject: [PATCH 0176/1466] Make panic stop actually stop the rig --- lewis_emulators/instron_stress_rig/device.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index a0b0638a..7fd1ca13 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -69,6 +69,8 @@ def get_actuator_status(self): def set_actuator_status(self, status): self.raise_exception_if_cannot_write() self._actuator_status = int(status) + if status == 0: + self._movement_type = 0 def get_movement_type(self): return self._movement_type From da785ac407e7bcc2217c4ff5b7edb5ecb96c82c6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 24 May 2017 17:00:25 +0100 Subject: [PATCH 0177/1466] Fix bug --- lewis_emulators/.idea/vcs.xml | 7 +++++++ lewis_emulators/instron_stress_rig/device.py | 8 +++++--- lewis_emulators/instron_stress_rig/states.py | 5 +++-- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 lewis_emulators/.idea/vcs.xml diff --git a/lewis_emulators/.idea/vcs.xml b/lewis_emulators/.idea/vcs.xml new file mode 100644 index 00000000..ff8c91d8 --- /dev/null +++ b/lewis_emulators/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 7fd1ca13..e8ffe4d0 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -2,6 +2,8 @@ from states import DefaultState from lewis.devices import StateMachineDevice +import time + class SimulatedInstron(StateMachineDevice): @@ -10,7 +12,7 @@ def _initialize_data(self): # When initialisation is complete, this is set to true and the device will enter a running state self.ready = True - self._control_channel = 0 + self._control_channel = 1 self._watchdog_status = (0, 0) self._control_mode = 0 self._actuator_status = 0 @@ -83,6 +85,6 @@ def set_movement_type(self, mov_type): else: self._movement_type = mov_type + 3 - def set_current_time(self, dt): - self.current_time = dt + def set_current_time(self): + self.current_time = time.time() diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index e71bf2d7..387fb406 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -1,13 +1,14 @@ from lewis.core.statemachine import State +import time class DefaultState(State): def in_state(self, dt): device = self._context - device.set_current_time(dt) + device.set_current_time() - if device.watchdog_refresh_time + 3 < dt: + if device.watchdog_refresh_time + 3 < time.time() and device.get_control_mode() != 0: print "Watchdog time expired, going back to front panel control mode" device.set_control_mode(0) From 9823dcf8b00a8cd09f91e23bb6b76af65a3e227d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 30 May 2017 12:20:07 +0100 Subject: [PATCH 0178/1466] Add a status property that can be set via backdoor --- lewis_emulators/instron_stress_rig/device.py | 4 ++++ .../instron_stress_rig/interfaces/stream_interface.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index e8ffe4d0..16ad4091 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -19,6 +19,7 @@ def _initialize_data(self): self._movement_type = 2 self.current_time = 0 self.watchdog_refresh_time = 0 + self.status = 7680 # Mode 0 = Ramp waveform # Mode 1 = Random waveform @@ -68,6 +69,9 @@ def stop(self): def get_actuator_status(self): return self._actuator_status + def get_status(self): + return self.status + def set_actuator_status(self, status): self.raise_exception_if_cannot_write() self._actuator_status = int(status) diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 1152ff8e..e81b71b7 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -46,7 +46,7 @@ def set_control_mode(self, mode): self._device.set_control_mode(int(mode)) def get_status(self): - return 7680 + return int(self._device.get_status()) def arbitrary_command(self, command): return "Arb_com_response_" + str(command) From 3dba5dbd6fa3b1050db5bfc76bd112d361071bcf Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 31 May 2017 12:10:29 +0100 Subject: [PATCH 0179/1466] Add step time functions to emulator --- lewis_emulators/instron_stress_rig/device.py | 19 +++++++++++++++++++ .../interfaces/stream_interface.py | 8 ++++++++ 2 files changed, 27 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 16ad4091..48d18cfb 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -25,6 +25,10 @@ def _initialize_data(self): # Mode 1 = Random waveform self._waveform_mode = 0 + self.position_step_time = 0 + self.stress_step_time = 0 + self.strain_step_time = 0 + def raise_exception_if_cannot_write(self): if self._control_mode != 1: @@ -92,3 +96,18 @@ def set_movement_type(self, mov_type): def set_current_time(self): self.current_time = time.time() + def set_step_time(self, channel, value): + if channel == 1: + self.position_step_time = value + elif channel == 2: + self.stress_step_time = value + elif channel == 3: + self.strain_step_time = value + + def get_step_time(self, channel): + if channel == 1: + return self.position_step_time + elif channel == 2: + return self.stress_step_time + elif channel == 3: + return self.strain_step_time diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index e81b71b7..93ee799e 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -17,6 +17,8 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_actuator_status", "^C23,([0-1])$"), Cmd("get_movement_type", "^Q1$"), Cmd("set_movement_type", "^C1,([0-3])$"), + Cmd("get_step_time", "^Q2,([1-3])$"), + Cmd("set_step_time", "^C2,([1-3]),([0-9]*.[0-9]*)$"), } in_terminator = "\r\n" @@ -62,3 +64,9 @@ def get_movement_type(self): def set_movement_type(self, mov_type): self._device.set_movement_type(int(mov_type)) + + def get_step_time(self, channel): + return float(self._device.get_step_time(int(channel))) + + def set_step_time(self, channel, value): + self._device.set_step_time(int(channel), float(value)) From e63b80d424e53ef44e224c2d8231ffa8978b3e0f Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 31 May 2017 17:30:25 +0100 Subject: [PATCH 0180/1466] Change the emulator --- lewis_emulators/instron_stress_rig/device.py | 41 +++++++++++-------- .../interfaces/stream_interface.py | 21 +++++++++- 2 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 48d18cfb..cd5a8636 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -25,10 +25,10 @@ def _initialize_data(self): # Mode 1 = Random waveform self._waveform_mode = 0 - self.position_step_time = 0 - self.stress_step_time = 0 - self.strain_step_time = 0 - + # Maps a channel number to a channel object + # Usually 1=position, 2=stress, 3=strain but in the + # context of the emulator it doesn't matter as all channels are treated equally. + self._channels = {1 : Channel(), 2 : Channel(), 3 : Channel()} def raise_exception_if_cannot_write(self): if self._control_mode != 1: @@ -97,17 +97,26 @@ def set_current_time(self): self.current_time = time.time() def set_step_time(self, channel, value): - if channel == 1: - self.position_step_time = value - elif channel == 2: - self.stress_step_time = value - elif channel == 3: - self.strain_step_time = value + self._channels[channel].step_time = value def get_step_time(self, channel): - if channel == 1: - return self.position_step_time - elif channel == 2: - return self.stress_step_time - elif channel == 3: - return self.strain_step_time + return self._channels[channel].step_time + + def set_chan_waveform_type(self, channel, value): + self._channels[channel].waveform_type = value + + def get_chan_waveform_type(self, channel): + return self._channels[channel].waveform_type + + def set_ramp_amplitude_setpoint(self, channel, value): + self._channels[channel].ramp_amplitude_setpoint = value + + def get_ramp_amplitude_setpoint(self, channel): + return self._channels[channel].ramp_amplitude_setpoint + +class Channel(object): + def __init__(self): + self.waveform_type = 0 + self.step_time = 0 + self.ramp_amplitude_setpoint = 0 + diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 93ee799e..4caba231 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -17,8 +17,12 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_actuator_status", "^C23,([0-1])$"), Cmd("get_movement_type", "^Q1$"), Cmd("set_movement_type", "^C1,([0-3])$"), - Cmd("get_step_time", "^Q2,([1-3])$"), - Cmd("set_step_time", "^C2,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_step_time", "^Q86,([1-3])$"), + Cmd("set_step_time", "^C86,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_chan_waveform_type", "^Q2,([1-3])$"), + Cmd("set_chan_waveform_type", "^C2,([1-3]),([0-5])$"), + Cmd("get_ramp_amplitude_setpoint", "^Q4,([1-3])$"), + Cmd("set_ramp_amplitude_setpoint", "^C4,([1-3]),([0-9]*.[0-9]*)$"), } in_terminator = "\r\n" @@ -70,3 +74,16 @@ def get_step_time(self, channel): def set_step_time(self, channel, value): self._device.set_step_time(int(channel), float(value)) + + def get_chan_waveform_type(self, channel): + return int(self._device.get_chan_waveform_type(int(channel))) + + def set_chan_waveform_type(self, channel, value): + self._device.set_chan_waveform_type(int(channel), int(value)) + + def get_ramp_amplitude_setpoint(self, channel): + return float(self._device.get_ramp_amplitude_setpoint(int(channel))) + + def set_ramp_amplitude_setpoint(self, channel, value): + self._device.set_ramp_amplitude_setpoint(int(channel), float(value)) + From d54a7954f314c3b2f7163dae0914b172123d1292 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:45:38 +0100 Subject: [PATCH 0181/1466] Update utilities.py --- lewis_emulators/volumetric_rig/utilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/utilities.py b/lewis_emulators/volumetric_rig/utilities.py index 460ad072..2dbf5cb9 100644 --- a/lewis_emulators/volumetric_rig/utilities.py +++ b/lewis_emulators/volumetric_rig/utilities.py @@ -31,4 +31,4 @@ def convert_raw_to_float(raw): def convert_raw_to_bool(raw): - return bool(convert_raw_to_int(raw)) \ No newline at end of file + return bool(convert_raw_to_int(raw)) From 09415922f81551eb41d1a834fb9cedc8024b9d76 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:46:45 +0100 Subject: [PATCH 0182/1466] Update hmi_device.py --- lewis_emulators/volumetric_rig/hmi_device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/hmi_device.py b/lewis_emulators/volumetric_rig/hmi_device.py index 2cf9a50e..6dec2a0c 100644 --- a/lewis_emulators/volumetric_rig/hmi_device.py +++ b/lewis_emulators/volumetric_rig/hmi_device.py @@ -35,4 +35,4 @@ def count(self, as_string, length): return format_int(self._count, as_string, length) def status(self): - return self._status \ No newline at end of file + return self._status From 4eb7689ea6f455d093155c34709f215ec6be6b1f Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:47:05 +0100 Subject: [PATCH 0183/1466] Update pressure_sensor.py --- lewis_emulators/volumetric_rig/pressure_sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/pressure_sensor.py b/lewis_emulators/volumetric_rig/pressure_sensor.py index 0981a9c2..e9862a02 100644 --- a/lewis_emulators/volumetric_rig/pressure_sensor.py +++ b/lewis_emulators/volumetric_rig/pressure_sensor.py @@ -23,4 +23,4 @@ def set_value(self, v, target): elif self._value > target: self._status = SensorStatus.VALUE_TOO_HIGH else: - self._status = SensorStatus.VALUE_IN_RANGE \ No newline at end of file + self._status = SensorStatus.VALUE_IN_RANGE From 50bc4798949d41aed15ebcb51273bcd37c01e0dd Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:47:24 +0100 Subject: [PATCH 0184/1466] Update states.py --- lewis_emulators/volumetric_rig/states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/states.py b/lewis_emulators/volumetric_rig/states.py index aa395ba2..a3c0498d 100644 --- a/lewis_emulators/volumetric_rig/states.py +++ b/lewis_emulators/volumetric_rig/states.py @@ -7,4 +7,4 @@ class DefaultInitState(State): class DefaultRunningState(State): def in_state(self, dt): - self._context.update_pressures(dt) \ No newline at end of file + self._context.update_pressures(dt) From e227bc5974508e0a2ffbb2be3b0ae6faf0d8e5ee Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:47:44 +0100 Subject: [PATCH 0185/1466] Update valve_status.py --- lewis_emulators/volumetric_rig/valve_status.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/valve_status.py b/lewis_emulators/volumetric_rig/valve_status.py index 1decefce..2baf4e62 100644 --- a/lewis_emulators/volumetric_rig/valve_status.py +++ b/lewis_emulators/volumetric_rig/valve_status.py @@ -2,4 +2,4 @@ class ValveStatus(object): """ An enumeration of possible valve states. OPEN_AND_DISABLED should never happen """ - OPEN_AND_ENABLED, CLOSED_AND_ENABLED, OPEN_AND_DISABLED, CLOSED_AND_DISABLED = (i for i in range(4)) \ No newline at end of file + OPEN_AND_ENABLED, CLOSED_AND_ENABLED, OPEN_AND_DISABLED, CLOSED_AND_DISABLED = (i for i in range(4)) From 87468aab0c5e9a18f094ee715a36407629f43380 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:48:29 +0100 Subject: [PATCH 0186/1466] Update sensor.py --- lewis_emulators/volumetric_rig/sensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/sensor.py b/lewis_emulators/volumetric_rig/sensor.py index 23882990..6abad440 100644 --- a/lewis_emulators/volumetric_rig/sensor.py +++ b/lewis_emulators/volumetric_rig/sensor.py @@ -20,4 +20,4 @@ def set_value(self, v): self._value = v def value(self, as_string=False): - return format_float(self._value, as_string) \ No newline at end of file + return format_float(self._value, as_string) From f537386501bcbd55b6379798a83d08ae538aedd6 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:48:47 +0100 Subject: [PATCH 0187/1466] Update error_states.py --- lewis_emulators/volumetric_rig/error_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/error_states.py b/lewis_emulators/volumetric_rig/error_states.py index 622f0350..2bdd1752 100644 --- a/lewis_emulators/volumetric_rig/error_states.py +++ b/lewis_emulators/volumetric_rig/error_states.py @@ -7,4 +7,4 @@ def __init__(self): self.hmi = False self.gauges = False self.comms = False - self.estop = False \ No newline at end of file + self.estop = False From c23a9b183a392bd9c91903b6176bd0fe945172c9 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:49:04 +0100 Subject: [PATCH 0188/1466] Update gas.py --- lewis_emulators/volumetric_rig/gas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/volumetric_rig/gas.py b/lewis_emulators/volumetric_rig/gas.py index d83bec8b..c4702094 100644 --- a/lewis_emulators/volumetric_rig/gas.py +++ b/lewis_emulators/volumetric_rig/gas.py @@ -15,4 +15,4 @@ def name(self, length=None, padding_character=" "): return pad_string(self._name, length, padding_character) def index(self, as_string=False, length=2): - return format_int(self._index, as_string, length) \ No newline at end of file + return format_int(self._index, as_string, length) From 9e115e91c162ad0b103c9e4bccf02d949adc3bd4 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 1 Jun 2017 09:49:18 +0100 Subject: [PATCH 0189/1466] Update __init__.py --- lewis_emulators/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py index 3be6bd29..c3961685 100644 --- a/lewis_emulators/__init__.py +++ b/lewis_emulators/__init__.py @@ -1 +1 @@ -from __future__ import absolute_import \ No newline at end of file +from __future__ import absolute_import From 67a0cd0c963d00da580f11cdd1e35ebcdaf6148c Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Thu, 1 Jun 2017 16:36:39 +0100 Subject: [PATCH 0190/1466] Add device emulator for TPG26x pressure gauge --- lewis_emulators/tpg26x/__init__.py | 3 + lewis_emulators/tpg26x/device.py | 81 +++++++++++++++++++ lewis_emulators/tpg26x/interfaces/__init__.py | 3 + .../tpg26x/interfaces/stream_interface.py | 55 +++++++++++++ lewis_emulators/tpg26x/states.py | 12 +++ lewis_emulators/utils/command_builder.py | 16 +++- 6 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 lewis_emulators/tpg26x/__init__.py create mode 100644 lewis_emulators/tpg26x/device.py create mode 100644 lewis_emulators/tpg26x/interfaces/__init__.py create mode 100644 lewis_emulators/tpg26x/interfaces/stream_interface.py create mode 100644 lewis_emulators/tpg26x/states.py diff --git a/lewis_emulators/tpg26x/__init__.py b/lewis_emulators/tpg26x/__init__.py new file mode 100644 index 00000000..962e63fb --- /dev/null +++ b/lewis_emulators/tpg26x/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedTpg26x + +__all__ = ['SimulatedTpg26x'] diff --git a/lewis_emulators/tpg26x/device.py b/lewis_emulators/tpg26x/device.py new file mode 100644 index 00000000..7e33d743 --- /dev/null +++ b/lewis_emulators/tpg26x/device.py @@ -0,0 +1,81 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedTpg26x(StateMachineDevice): + """ + Simulated TPG26x + """ + + def _initialize_data(self): + + """ + + Sets the initial state of the device + + """ + + self._pressure1 = 2.0 + self._pressure2 = 3.0 + + def _get_state_handlers(self): + + """ + + Returns: states and their names + + """ + return { + DefaultState.NAME: DefaultState(), + } + + def _get_initial_state(self): + """ + + Returns: the name of the initial state + + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + + """ + + Returns: the state transitions + + """ + + return OrderedDict([ + ]) + + @property + def pressure1(self): + """ + Returns: the first pressure + """ + return self._pressure1 + + @pressure1.setter + def pressure1(self, pressure): + """ + Set the pressure for pressure 1 + :param pressure: the pressure value to set the first pressure to + """ + self._pressure1 = pressure + + @property + def pressure2(self): + """ + Returns: the second pressure + """ + return self._pressure2 + + @pressure2.setter + def pressure2(self, pressure): + """ + Set the pressure for pressure 2 + :param pressure: the pressure value to set the second pressure to + """ + self._pressure2 = pressure diff --git a/lewis_emulators/tpg26x/interfaces/__init__.py b/lewis_emulators/tpg26x/interfaces/__init__.py new file mode 100644 index 00000000..e0d4fb8c --- /dev/null +++ b/lewis_emulators/tpg26x/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Tpg26xStreamInterface + +__all__ = ['Tpg26xStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py new file mode 100644 index 00000000..770acf38 --- /dev/null +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -0,0 +1,55 @@ +from lewis.adapters.stream import StreamAdapter +from lewis_emulators.utils.command_builder import CmdBuilder + + +class Tpg26xStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + _last_command = None + ACK = chr(6) + + commands = { + CmdBuilder("get_pressure").escape("PRX").build(), + CmdBuilder("handle_enquiry").enq().build() + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def handle_enquiry(self): + """ + Handle an enquiry using the last command sent. + :return: + """ + + if self._last_command == "PRX": + return self.get_pressure() + else: + print "Last command was unknown: " + repr(self._last_command) + + def get_pressure(self): + """ + Get the current pressure of the TPG26x + + Returns: a string with pressure and error codes + """ + if self._last_command is None: + self._last_command = "PRX" + return self.ACK + else: + self._last_command = None + return "0,{0},0,{1}".format(self._device.pressure1, self._device.pressure2) + diff --git a/lewis_emulators/tpg26x/states.py b/lewis_emulators/tpg26x/states.py new file mode 100644 index 00000000..d9e8716b --- /dev/null +++ b/lewis_emulators/tpg26x/states.py @@ -0,0 +1,12 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + + """ + NAME = 'Default' + + + diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index f851476e..740ec042 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -108,7 +108,21 @@ def stx(self): def etx(self): """ - Add the ETX character (0xe) to the string + Add the ETX character (0x3) to the string :return: builder """ return self.add_ascii_character(3) + + def enq(self): + """ + Add the ENQ character (0x5) to the string + :return: builder + """ + return self.add_ascii_character(5) + + def ack(self): + """ + Add the ACK character (0x6) to the string + :return: builder + """ + return self.add_ascii_character(6) From 6ca1f372cd413a93ebce21fcfbf1c85e109e2f92 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Thu, 1 Jun 2017 17:21:47 +0100 Subject: [PATCH 0191/1466] Add ability to read units to TPG26x emulator --- lewis_emulators/tpg26x/device.py | 17 ++++++++++++++ .../tpg26x/interfaces/stream_interface.py | 22 ++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/tpg26x/device.py b/lewis_emulators/tpg26x/device.py index 7e33d743..f5591b2f 100644 --- a/lewis_emulators/tpg26x/device.py +++ b/lewis_emulators/tpg26x/device.py @@ -19,6 +19,7 @@ def _initialize_data(self): self._pressure1 = 2.0 self._pressure2 = 3.0 + self._units = 1 def _get_state_handlers(self): @@ -79,3 +80,19 @@ def pressure2(self, pressure): :param pressure: the pressure value to set the second pressure to """ self._pressure2 = pressure + + @property + def units(self): + """ + Returns: the units of the TPG26x + """ + return self._units + + @units.setter + def units(self, units): + """ + Set the units for the TPG26x + :param units: the units to set the device to as a string + """ + self._units = units + diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py index 770acf38..a2114983 100644 --- a/lewis_emulators/tpg26x/interfaces/stream_interface.py +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -12,6 +12,7 @@ class Tpg26xStreamInterface(StreamAdapter): commands = { CmdBuilder("get_pressure").escape("PRX").build(), + CmdBuilder("get_units").escape("UNI").build(), CmdBuilder("handle_enquiry").enq().build() } @@ -37,6 +38,8 @@ def handle_enquiry(self): if self._last_command == "PRX": return self.get_pressure() + elif self._last_command == "UNI": + return self.get_units() else: print "Last command was unknown: " + repr(self._last_command) @@ -49,7 +52,20 @@ def get_pressure(self): if self._last_command is None: self._last_command = "PRX" return self.ACK - else: - self._last_command = None - return "0,{0},0,{1}".format(self._device.pressure1, self._device.pressure2) + + self._last_command = None + return "0,{0},0,{1}".format(self._device.pressure1, self._device.pressure2) + + def get_units(self): + """ + Get the current units of the TPG26x + + Returns: a string representing the units + """ + if self._last_command is None: + self._last_command = "UNI" + return self.ACK + + self._last_command = None + return self._device.units From 62fdbf299f137c511bd4d18f656e3d9caa0bf152 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Fri, 2 Jun 2017 09:15:52 +0100 Subject: [PATCH 0192/1466] Add method for setting device units --- .../tpg26x/interfaces/stream_interface.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py index a2114983..91b38666 100644 --- a/lewis_emulators/tpg26x/interfaces/stream_interface.py +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -13,6 +13,7 @@ class Tpg26xStreamInterface(StreamAdapter): commands = { CmdBuilder("get_pressure").escape("PRX").build(), CmdBuilder("get_units").escape("UNI").build(), + CmdBuilder("set_units").escape("UNI").arg("{0|1|2}").build(), CmdBuilder("handle_enquiry").enq().build() } @@ -69,3 +70,14 @@ def get_units(self): self._last_command = None return self._device.units + def set_units(self, units): + """ + Set the units of the TPG26x + :param: the unit flag to change the units too + """ + if self._last_command is None: + self._last_command = "UNI" + return self.ACK + + self._device.units = units + From 03d6101efb469cf57eddf15e694529ede7d533b0 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Fri, 2 Jun 2017 09:24:14 +0100 Subject: [PATCH 0193/1466] Refactor device emulator code --- .../tpg26x/interfaces/stream_interface.py | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py index 91b38666..9adb4d49 100644 --- a/lewis_emulators/tpg26x/interfaces/stream_interface.py +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -11,8 +11,8 @@ class Tpg26xStreamInterface(StreamAdapter): ACK = chr(6) commands = { - CmdBuilder("get_pressure").escape("PRX").build(), - CmdBuilder("get_units").escape("UNI").build(), + CmdBuilder("acknowledge_pressure").escape("PRX").build(), + CmdBuilder("acknowledge_units").escape("UNI").build(), CmdBuilder("set_units").escape("UNI").arg("{0|1|2}").build(), CmdBuilder("handle_enquiry").enq().build() } @@ -31,6 +31,22 @@ def handle_error(self, request, error): """ print "An error occurred at request " + repr(request) + ": " + repr(error) + def acknowledge_pressure(self): + """ + Acknowledge that the request for current pressure was received + :return: ASCII acknowledgement character (0x6) + """ + self._last_command = "PRX" + return self.ACK + + def acknowledge_units(self): + """ + Acknowledge that the request for current units was received + :return: ASCII acknowledgement character (0x6) + """ + self._last_command = "UNI" + return self.ACK + def handle_enquiry(self): """ Handle an enquiry using the last command sent. @@ -50,11 +66,6 @@ def get_pressure(self): Returns: a string with pressure and error codes """ - if self._last_command is None: - self._last_command = "PRX" - return self.ACK - - self._last_command = None return "0,{0},0,{1}".format(self._device.pressure1, self._device.pressure2) def get_units(self): @@ -63,11 +74,6 @@ def get_units(self): Returns: a string representing the units """ - if self._last_command is None: - self._last_command = "UNI" - return self.ACK - - self._last_command = None return self._device.units def set_units(self, units): @@ -80,4 +86,5 @@ def set_units(self, units): return self.ACK self._device.units = units + self._last_command = None From f7949a40ab85c5ea02b58f4fc204ca3d4054af81 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Fri, 2 Jun 2017 12:59:30 +0100 Subject: [PATCH 0194/1466] Add error handling to the device from both pressures --- lewis_emulators/tpg26x/device.py | 34 ++++++++++++++++++- .../tpg26x/interfaces/stream_interface.py | 3 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/tpg26x/device.py b/lewis_emulators/tpg26x/device.py index f5591b2f..d6d6cd0b 100644 --- a/lewis_emulators/tpg26x/device.py +++ b/lewis_emulators/tpg26x/device.py @@ -19,7 +19,9 @@ def _initialize_data(self): self._pressure1 = 2.0 self._pressure2 = 3.0 - self._units = 1 + self._error1 = 0 + self._error2 = 0 + self._units = 0 def _get_state_handlers(self): @@ -96,3 +98,33 @@ def units(self, units): """ self._units = units + @property + def error1(self): + """ + Returns: the error state for pressure 1 + """ + return self._error1 + + @error1.setter + def error1(self, state): + """ + Set the error state for pressure 1 + :param state: the error code state for pressure 1 + """ + self._error1 = state + + @property + def error2(self): + """ + Returns: the error state for pressure 2 + """ + return self._error2 + + @error2.setter + def error2(self, state): + """ + Set the error state for pressure 2 + :param state: the error code state for pressure 2 + """ + self._error2 = state + diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py index 9adb4d49..c46eafc6 100644 --- a/lewis_emulators/tpg26x/interfaces/stream_interface.py +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -66,7 +66,8 @@ def get_pressure(self): Returns: a string with pressure and error codes """ - return "0,{0},0,{1}".format(self._device.pressure1, self._device.pressure2) + return "{0},{1},{2},{3}".format(self._device.error1, self._device.pressure1, + self._device.error2, self._device.pressure2) def get_units(self): """ From 287c03595b2b62457bfef8f99788f9902f292d5b Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 2 Jun 2017 14:25:00 +0100 Subject: [PATCH 0195/1466] Add current value set/read --- .../instron_stress_rig/interfaces/stream_interface.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 4caba231..d1a7a750 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -23,6 +23,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_chan_waveform_type", "^C2,([1-3]),([0-5])$"), Cmd("get_ramp_amplitude_setpoint", "^Q4,([1-3])$"), Cmd("set_ramp_amplitude_setpoint", "^C4,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_single_point_feedback_data", "^Q134,([1-3]),([0-9]+)$"), } in_terminator = "\r\n" @@ -87,3 +88,9 @@ def get_ramp_amplitude_setpoint(self, channel): def set_ramp_amplitude_setpoint(self, channel, value): self._device.set_ramp_amplitude_setpoint(int(channel), float(value)) + def get_single_point_feedback_data(self, channel, type): + # Emulator/IOC only currently supports getting current value (type 0). + # Actual rig accepts values 0-12 + assert int(type) == 0 + return float(self._device.get_ramp_amplitude_setpoint(int(channel))) + From 71aa65f37a78e0ab1fed9cc1045ee8355f9f4652 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 2 Jun 2017 16:06:13 +0100 Subject: [PATCH 0196/1466] Add channel scale to emulator --- lewis_emulators/instron_stress_rig/device.py | 4 ++++ .../instron_stress_rig/interfaces/stream_interface.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index cd5a8636..d60d34e0 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -114,9 +114,13 @@ def set_ramp_amplitude_setpoint(self, channel, value): def get_ramp_amplitude_setpoint(self, channel): return self._channels[channel].ramp_amplitude_setpoint + def get_chan_scale(self, channel): + return self._channels[channel].chan_scale + class Channel(object): def __init__(self): self.waveform_type = 0 self.step_time = 0 self.ramp_amplitude_setpoint = 0 + self.chan_scale = 10 diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index d1a7a750..e0c63e9c 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -24,6 +24,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_ramp_amplitude_setpoint", "^Q4,([1-3])$"), Cmd("set_ramp_amplitude_setpoint", "^C4,([1-3]),([0-9]*.[0-9]*)$"), Cmd("get_single_point_feedback_data", "^Q134,([1-3]),([0-9]+)$"), + Cmd("get_chan_scale", "^Q308,([1-3])$"), } in_terminator = "\r\n" @@ -94,3 +95,6 @@ def get_single_point_feedback_data(self, channel, type): assert int(type) == 0 return float(self._device.get_ramp_amplitude_setpoint(int(channel))) + def get_chan_scale(self, channel): + return self._device.get_chan_scale(int(channel)) + From 8bce17097a24373b6bb080fa76b73d90d4d1c075 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 2 Jun 2017 17:09:33 +0100 Subject: [PATCH 0197/1466] Make channels public --- lewis_emulators/instron_stress_rig/device.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index d60d34e0..1823bf70 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -28,7 +28,7 @@ def _initialize_data(self): # Maps a channel number to a channel object # Usually 1=position, 2=stress, 3=strain but in the # context of the emulator it doesn't matter as all channels are treated equally. - self._channels = {1 : Channel(), 2 : Channel(), 3 : Channel()} + self.channels = {1 : Channel(), 2 : Channel(), 3 : Channel()} def raise_exception_if_cannot_write(self): if self._control_mode != 1: @@ -97,25 +97,25 @@ def set_current_time(self): self.current_time = time.time() def set_step_time(self, channel, value): - self._channels[channel].step_time = value + self.channels[channel].step_time = value def get_step_time(self, channel): - return self._channels[channel].step_time + return self.channels[channel].step_time def set_chan_waveform_type(self, channel, value): - self._channels[channel].waveform_type = value + self.channels[channel].waveform_type = value def get_chan_waveform_type(self, channel): - return self._channels[channel].waveform_type + return self.channels[channel].waveform_type def set_ramp_amplitude_setpoint(self, channel, value): - self._channels[channel].ramp_amplitude_setpoint = value + self.channels[channel].ramp_amplitude_setpoint = value def get_ramp_amplitude_setpoint(self, channel): - return self._channels[channel].ramp_amplitude_setpoint + return self.channels[channel].ramp_amplitude_setpoint def get_chan_scale(self, channel): - return self._channels[channel].chan_scale + return self.channels[channel].chan_scale class Channel(object): def __init__(self): From d10c6538cd198fdd43fdc6ab9651adcf6648ac41 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 5 Jun 2017 15:57:45 +0100 Subject: [PATCH 0198/1466] Add function to emulator to enable setting values within the Channel() objects --- lewis_emulators/instron_stress_rig/device.py | 14 ++++++++++++++ .../interfaces/stream_interface.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 1823bf70..9131a1eb 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -39,6 +39,15 @@ def _get_state_handlers(self): 'default': DefaultState(), } + # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 + def set_channel_param(self, index, param, value): + print "Backdoor set: {index} {param} {value}".format(index=index, param=param, value=value) + setattr(self.channels[int(index)], str(param), value) + + # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 + def get_channel_param(self, index, param): + return getattr(self.channels[int(index)], str(param)) + def _get_initial_state(self): return 'default' @@ -117,10 +126,15 @@ def get_ramp_amplitude_setpoint(self, channel): def get_chan_scale(self, channel): return self.channels[channel].chan_scale + def get_chan_value(self, channel): + return self.channels[channel].value + + class Channel(object): def __init__(self): self.waveform_type = 0 self.step_time = 0 self.ramp_amplitude_setpoint = 0 self.chan_scale = 10 + self.value = 0 diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index e0c63e9c..59f7b956 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -93,7 +93,7 @@ def get_single_point_feedback_data(self, channel, type): # Emulator/IOC only currently supports getting current value (type 0). # Actual rig accepts values 0-12 assert int(type) == 0 - return float(self._device.get_ramp_amplitude_setpoint(int(channel))) + return float(self._device.get_chan_value(int(channel))) def get_chan_scale(self, channel): return self._device.get_chan_scale(int(channel)) From c2cb5c4078f4e4953821cbc192fb2779bbb0ada9 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 5 Jun 2017 16:23:11 +0100 Subject: [PATCH 0199/1466] Rename variable to work with tests --- lewis_emulators/instron_stress_rig/device.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 9131a1eb..b96a9a2f 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -41,7 +41,6 @@ def _get_state_handlers(self): # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 def set_channel_param(self, index, param, value): - print "Backdoor set: {index} {param} {value}".format(index=index, param=param, value=value) setattr(self.channels[int(index)], str(param), value) # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 @@ -124,7 +123,7 @@ def get_ramp_amplitude_setpoint(self, channel): return self.channels[channel].ramp_amplitude_setpoint def get_chan_scale(self, channel): - return self.channels[channel].chan_scale + return self.channels[channel].scale def get_chan_value(self, channel): return self.channels[channel].value @@ -135,6 +134,6 @@ def __init__(self): self.waveform_type = 0 self.step_time = 0 self.ramp_amplitude_setpoint = 0 - self.chan_scale = 10 + self.scale = 10 self.value = 0 From 38dbadc9c9871bf73c9218c45b3395bac1bf94da Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 5 Jun 2017 17:34:36 +0100 Subject: [PATCH 0200/1466] Refactor device to accomodate the strain channel having a length field --- lewis_emulators/instron_stress_rig/device.py | 24 ++++++++++++++++++- .../interfaces/stream_interface.py | 4 ++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index b96a9a2f..4ff23b39 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -28,7 +28,7 @@ def _initialize_data(self): # Maps a channel number to a channel object # Usually 1=position, 2=stress, 3=strain but in the # context of the emulator it doesn't matter as all channels are treated equally. - self.channels = {1 : Channel(), 2 : Channel(), 3 : Channel()} + self.channels = {1: PositionChannel(), 2: StressChannel(), 3: StrainChannel()} def raise_exception_if_cannot_write(self): if self._control_mode != 1: @@ -128,6 +128,13 @@ def get_chan_scale(self, channel): def get_chan_value(self, channel): return self.channels[channel].value + def get_strain_channel_length(self, channel): + # Getting the length is only supported for channel 3 (strain). + assert channel == 3, "Channel was not 3" + # This number gets divided by in the IOC - if it's zero things will break. + assert self.channels[channel].length != 0, "Strain channel length was zero" + return self.channels[channel].length + class Channel(object): def __init__(self): @@ -137,3 +144,18 @@ def __init__(self): self.scale = 10 self.value = 0 + +class PositionChannel(Channel): + def __init__(self): + super(PositionChannel, self).__init__() + + +class StressChannel(Channel): + def __init__(self): + super(StressChannel, self).__init__() + + +class StrainChannel(Channel): + def __init__(self): + super(StrainChannel, self).__init__() + self.length = 1 diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 59f7b956..3ec2a123 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -25,6 +25,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_ramp_amplitude_setpoint", "^C4,([1-3]),([0-9]*.[0-9]*)$"), Cmd("get_single_point_feedback_data", "^Q134,([1-3]),([0-9]+)$"), Cmd("get_chan_scale", "^Q308,([1-3])$"), + Cmd("get_strain_channel_length", "^Q340,([1-3])$"), } in_terminator = "\r\n" @@ -98,3 +99,6 @@ def get_single_point_feedback_data(self, channel, type): def get_chan_scale(self, channel): return self._device.get_chan_scale(int(channel)) + def get_strain_channel_length(self, channel): + return self._device.get_strain_channel_length(int(channel)) + From 33ee44d41ede8e97445c9c1f66c9715c9bb0a510 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 6 Jun 2017 09:50:26 +0100 Subject: [PATCH 0201/1466] Add support for stress:area values --- lewis_emulators/instron_stress_rig/device.py | 18 ++++++++++++++++-- .../interfaces/stream_interface.py | 13 +++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 4ff23b39..c43f6438 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -125,16 +125,29 @@ def get_ramp_amplitude_setpoint(self, channel): def get_chan_scale(self, channel): return self.channels[channel].scale - def get_chan_value(self, channel): + def get_chan_value(self, channel, type): + # Emulator/IOC only currently supports getting current value (type 0). + # Actual rig accepts values 0-12 + assert int(type) == 0, "Emulator only supports getting current value" return self.channels[channel].value def get_strain_channel_length(self, channel): # Getting the length is only supported for channel 3 (strain). - assert channel == 3, "Channel was not 3" + assert isinstance(self.channels[channel], StrainChannel), "Length only applies to strain channel" # This number gets divided by in the IOC - if it's zero things will break. assert self.channels[channel].length != 0, "Strain channel length was zero" return self.channels[channel].length + def get_chan_area(self, channel): + # Area is only applicable to stress channel + assert isinstance(self.channels[channel], StressChannel), "Area only applies to stress channel" + return self.channels[channel] + + def set_chan_area(self, channel, value): + # Area is only applicable to stress channel + assert isinstance(self.channels[channel], StressChannel), "Area only applies to stress channel" + self.channels[channel].area = value + class Channel(object): def __init__(self): @@ -153,6 +166,7 @@ def __init__(self): class StressChannel(Channel): def __init__(self): super(StressChannel, self).__init__() + self.area = 1 class StrainChannel(Channel): diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 3ec2a123..81688cf8 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -26,6 +26,8 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_single_point_feedback_data", "^Q134,([1-3]),([0-9]+)$"), Cmd("get_chan_scale", "^Q308,([1-3])$"), Cmd("get_strain_channel_length", "^Q340,([1-3])$"), + Cmd("get_chan_area", "^Q341,([1-3])$"), + Cmd("set_chan_area", "^C341,([1-3]),([0-9]*.[0-9]*)$"), } in_terminator = "\r\n" @@ -91,10 +93,7 @@ def set_ramp_amplitude_setpoint(self, channel, value): self._device.set_ramp_amplitude_setpoint(int(channel), float(value)) def get_single_point_feedback_data(self, channel, type): - # Emulator/IOC only currently supports getting current value (type 0). - # Actual rig accepts values 0-12 - assert int(type) == 0 - return float(self._device.get_chan_value(int(channel))) + return float(self._device.get_chan_value(int(channel), int(type))) def get_chan_scale(self, channel): return self._device.get_chan_scale(int(channel)) @@ -102,3 +101,9 @@ def get_chan_scale(self, channel): def get_strain_channel_length(self, channel): return self._device.get_strain_channel_length(int(channel)) + def get_chan_area(self, channel): + return self._device.get_chan_area(int(channel)) + + def set_chan_area(self, channel, value): + self._device.set_chan_area(int(channel), float(value)) + From ba3e68f59ab44a02bc6618dcb91fa7e4a64f9026 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 6 Jun 2017 09:57:26 +0100 Subject: [PATCH 0202/1466] Fix key error --- lewis_emulators/instron_stress_rig/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index c43f6438..a277ab3b 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -141,7 +141,7 @@ def get_strain_channel_length(self, channel): def get_chan_area(self, channel): # Area is only applicable to stress channel assert isinstance(self.channels[channel], StressChannel), "Area only applies to stress channel" - return self.channels[channel] + return self.channels[channel].area def set_chan_area(self, channel, value): # Area is only applicable to stress channel From 81b69d6e808fb9ceb0b138be1179cb4865e2e6e9 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Tue, 6 Jun 2017 13:56:27 +0100 Subject: [PATCH 0203/1466] Add emulator for superlogics --- lewis_emulators/superlogics/__init__.py | 3 + lewis_emulators/superlogics/device.py | 69 +++++++++++++++++++ .../superlogics/interfaces/__init__.py | 3 + .../interfaces/stream_interface.py | 35 ++++++++++ lewis_emulators/superlogics/states.py | 12 ++++ 5 files changed, 122 insertions(+) create mode 100644 lewis_emulators/superlogics/__init__.py create mode 100644 lewis_emulators/superlogics/device.py create mode 100644 lewis_emulators/superlogics/interfaces/__init__.py create mode 100644 lewis_emulators/superlogics/interfaces/stream_interface.py create mode 100644 lewis_emulators/superlogics/states.py diff --git a/lewis_emulators/superlogics/__init__.py b/lewis_emulators/superlogics/__init__.py new file mode 100644 index 00000000..d5942cbe --- /dev/null +++ b/lewis_emulators/superlogics/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedSuperlogics + +__all__ = ['SimulatedSuperlogics'] diff --git a/lewis_emulators/superlogics/device.py b/lewis_emulators/superlogics/device.py new file mode 100644 index 00000000..ce9cd804 --- /dev/null +++ b/lewis_emulators/superlogics/device.py @@ -0,0 +1,69 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedSuperlogics(StateMachineDevice): + """ + Simulated Superlogics 8019R + """ + + def _initialize_data(self): + + """ + + Sets the initial state of the device + + """ + + self._values = [0.]*8 + self.address = "AB" + + def _get_state_handlers(self): + + """ + + Returns: states and their names + + """ + return { + DefaultState.NAME: DefaultState(), + } + + def _get_initial_state(self): + """ + + Returns: the name of the initial state + + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + + """ + + Returns: the state transitions + + """ + + return OrderedDict([ + ]) + + def get_values(self): + return self._values + + @property + def value1(self): + """ + Returns: the value of PV Value1 + """ + return self._values[0] + + @value1.setter + def value1(self, value): + """ + + :param values: set the value for a the Value1 PV + """ + self._values[0] = value diff --git a/lewis_emulators/superlogics/interfaces/__init__.py b/lewis_emulators/superlogics/interfaces/__init__.py new file mode 100644 index 00000000..30272cc8 --- /dev/null +++ b/lewis_emulators/superlogics/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import SuperlogicsStreamInterface + +__all__ = ['SuperlogicsStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/superlogics/interfaces/stream_interface.py b/lewis_emulators/superlogics/interfaces/stream_interface.py new file mode 100644 index 00000000..9f433047 --- /dev/null +++ b/lewis_emulators/superlogics/interfaces/stream_interface.py @@ -0,0 +1,35 @@ +from lewis.adapters.stream import StreamAdapter +from lewis_emulators.utils.command_builder import CmdBuilder + + +class SuperlogicsStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + CmdBuilder("get_values").escape("#").arg("[0-9]+").build() + } + + in_terminator = "\r" + out_terminator = "\r" + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def get_values(self, address): + """ + Gets the values from the device + + Returns: List of values, one for each connected channel + """ + formatted_values = map(lambda s: "+{0:.2f}".format(s), self._device.get_values()) + return ",".join(formatted_values) diff --git a/lewis_emulators/superlogics/states.py b/lewis_emulators/superlogics/states.py new file mode 100644 index 00000000..d9e8716b --- /dev/null +++ b/lewis_emulators/superlogics/states.py @@ -0,0 +1,12 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + + """ + NAME = 'Default' + + + From fe29536d8db498500a0c5ecfc7881d8b151beb81 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Tue, 6 Jun 2017 16:02:26 +0100 Subject: [PATCH 0204/1466] Add support for multiple values to superlogics emulator --- lewis_emulators/superlogics/device.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/superlogics/device.py b/lewis_emulators/superlogics/device.py index ce9cd804..19dc1149 100644 --- a/lewis_emulators/superlogics/device.py +++ b/lewis_emulators/superlogics/device.py @@ -60,10 +60,17 @@ def value1(self): """ return self._values[0] - @value1.setter - def value1(self, value): + @property + def values(self): + """ + :return: All values on this channel """ + return self._values - :param values: set the value for a the Value1 PV + @values.setter + def values(self, values): """ - self._values[0] = value + :param values: set all the values on this channel + """ + self._values = values + From 6fec4c37a926e82dafe93b6e7a74807c089b0d32 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 7 Jun 2017 15:35:55 +0100 Subject: [PATCH 0205/1466] Add support for channel type --- lewis_emulators/instron_stress_rig/device.py | 8 ++++++++ .../instron_stress_rig/interfaces/stream_interface.py | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index a277ab3b..f93207c0 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -148,6 +148,12 @@ def set_chan_area(self, channel, value): assert isinstance(self.channels[channel], StressChannel), "Area only applies to stress channel" self.channels[channel].area = value + def get_chan_type_1(self, channel): + return self.channels[channel].type_1 + + def get_chan_type_2(self, channel): + return self.channels[channel].type_2 + class Channel(object): def __init__(self): @@ -156,6 +162,8 @@ def __init__(self): self.ramp_amplitude_setpoint = 0 self.scale = 10 self.value = 0 + self.type_1 = 0 + self.type_2 = 0 class PositionChannel(Channel): diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 81688cf8..0e5c59c0 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -28,6 +28,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_strain_channel_length", "^Q340,([1-3])$"), Cmd("get_chan_area", "^Q341,([1-3])$"), Cmd("set_chan_area", "^C341,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_chan_type", "^Q307,([1-3])$"), } in_terminator = "\r\n" @@ -107,3 +108,7 @@ def get_chan_area(self, channel): def set_chan_area(self, channel, value): self._device.set_chan_area(int(channel), float(value)) + def get_chan_type(self, channel): + type_1 = self._device.get_chan_type_1(int(channel)) + type_2 = self._device.get_chan_type_2(int(channel)) + return "{a},{b}".format(a=type_1, b=type_2) From 3ea176541c1a3e6600e9dd32e6151f783b2b6719 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 8 Jun 2017 11:12:49 +0100 Subject: [PATCH 0206/1466] Missing EOL --- lewis_emulators/rotating_sample_changer/states.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/rotating_sample_changer/states.py b/lewis_emulators/rotating_sample_changer/states.py index e0af1f0e..7e8becd2 100644 --- a/lewis_emulators/rotating_sample_changer/states.py +++ b/lewis_emulators/rotating_sample_changer/states.py @@ -21,4 +21,5 @@ def in_state(self, dt): self._context.CAR_SPEED, dt) def on_exit(self, dt): - self._context.arm_lowered = True \ No newline at end of file + self._context.arm_lowered = True + From 9d5a6dd687f5b662fe18b1da91dc7380466fce99 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 8 Jun 2017 11:20:05 +0100 Subject: [PATCH 0207/1466] Move towards setpoint when going --- lewis_emulators/instron_stress_rig/device.py | 14 +++++++++----- lewis_emulators/instron_stress_rig/states.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index f93207c0..794fdef4 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultState +from states import DefaultState, GoingToSetpointState from lewis.devices import StateMachineDevice import time @@ -12,7 +12,7 @@ def _initialize_data(self): # When initialisation is complete, this is set to true and the device will enter a running state self.ready = True - self._control_channel = 1 + self.control_channel = 1 self._watchdog_status = (0, 0) self._control_mode = 0 self._actuator_status = 0 @@ -37,6 +37,7 @@ def raise_exception_if_cannot_write(self): def _get_state_handlers(self): return { 'default': DefaultState(), + 'going': GoingToSetpointState(), } # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 @@ -51,13 +52,16 @@ def _get_initial_state(self): return 'default' def _get_transition_handlers(self): - return OrderedDict([]) + return OrderedDict([ + (('default', 'going'), lambda: self._movement_type != 0 and self.channels[self.control_channel].value != self.channels[self.control_channel].ramp_amplitude_setpoint), + (('going', 'default'), lambda: self._movement_type == 0 or self.channels[self.control_channel].value == self.channels[self.control_channel].ramp_amplitude_setpoint), + ]) def get_control_channel(self): - return self._control_channel + return self.control_channel def set_control_channel(self, channel): - self._control_channel = channel + self.control_channel = channel def get_watchdog_status(self): return self._watchdog_status diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index 387fb406..d2c58f5e 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -1,5 +1,6 @@ from lewis.core.statemachine import State import time +from lewis.core import approaches class DefaultState(State): @@ -12,3 +13,12 @@ def in_state(self, dt): print "Watchdog time expired, going back to front panel control mode" device.set_control_mode(0) + +class GoingToSetpointState(DefaultState): + def in_state(self, dt): + super(GoingToSetpointState, self).in_state(dt) + device = self._context + device.channels[device.control_channel].value = approaches.linear(device.channels[device.control_channel].value, + device.channels[device.control_channel].ramp_amplitude_setpoint, + 0.001, dt) + From acb3a60735d35782efefea26276db9222a404b01 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 8 Jun 2017 11:38:31 +0100 Subject: [PATCH 0208/1466] Remove misleading comment --- lewis_emulators/instron_stress_rig/device.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 794fdef4..6b4cd5db 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -26,8 +26,6 @@ def _initialize_data(self): self._waveform_mode = 0 # Maps a channel number to a channel object - # Usually 1=position, 2=stress, 3=strain but in the - # context of the emulator it doesn't matter as all channels are treated equally. self.channels = {1: PositionChannel(), 2: StressChannel(), 3: StrainChannel()} def raise_exception_if_cannot_write(self): From 605295beeda47b7447abb5371edc867145594bbd Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 8 Jun 2017 12:28:08 +0100 Subject: [PATCH 0209/1466] Tidied up the formatting for a lot of files --- lewis_emulators/CCD100/__init__.py | 5 - lewis_emulators/CCD100/device.py | 2 - lewis_emulators/__init__.py | 2 +- lewis_emulators/amint2l/device.py | 27 +-- .../amint2l/interfaces/__init__.py | 2 +- lewis_emulators/amint2l/states.py | 4 - lewis_emulators/instron_stress_rig/device.py | 5 +- lewis_emulators/instron_stress_rig/states.py | 2 - lewis_emulators/iris_cryo_valve/__init__.py | 5 - lewis_emulators/iris_cryo_valve/device.py | 3 - .../iris_cryo_valve/interfaces/__init__.py | 2 +- .../interfaces/stream_interface.py | 1 - lewis_emulators/keithley_2400/__init__.py | 5 - lewis_emulators/keithley_2400/device.py | 16 +- .../interfaces/stream_interface.py | 21 ++- lewis_emulators/keithley_2400/utilities.py | 4 +- lewis_emulators/mk2_chopper/chopper_type.py | 1 - lewis_emulators/mk2_chopper/device.py | 4 +- .../interfaces/stream_interface.py | 4 +- lewis_emulators/mk2_chopper/states.py | 2 + lewis_emulators/neocera_ltc21/constants.py | 2 +- lewis_emulators/neocera_ltc21/device.py | 34 +--- .../neocera_ltc21/device_errors.py | 4 +- .../neocera_ltc21/interfaces/__init__.py | 2 +- .../interfaces/stream_interface.py | 156 +++++++----------- lewis_emulators/neocera_ltc21/states.py | 4 +- lewis_emulators/tpg26x/device.py | 42 ++--- lewis_emulators/tpg26x/interfaces/__init__.py | 2 +- .../tpg26x/interfaces/stream_interface.py | 27 +-- lewis_emulators/tpg26x/states.py | 4 - lewis_emulators/utils/command_builder.py | 68 ++++---- lewis_emulators/volumetric_rig/__init__.py | 5 - .../volumetric_rig/error_states.py | 2 +- .../volumetric_rig/ethernet_device.py | 2 +- lewis_emulators/volumetric_rig/gas.py | 2 +- .../volumetric_rig/interfaces/__init__.py | 2 +- .../interfaces/stream_interface.py | 59 ++++--- .../volumetric_rig/pressure_sensor.py | 2 +- .../volumetric_rig/seed_gas_data.py | 2 +- lewis_emulators/volumetric_rig/sensor.py | 2 +- .../volumetric_rig/sensor_status.py | 2 +- .../volumetric_rig/system_gases.py | 2 +- .../volumetric_rig/two_gas_mixer.py | 2 +- .../volumetric_rig/valve_status.py | 2 +- 44 files changed, 221 insertions(+), 327 deletions(-) diff --git a/lewis_emulators/CCD100/__init__.py b/lewis_emulators/CCD100/__init__.py index f3f88c34..b50a6e12 100644 --- a/lewis_emulators/CCD100/__init__.py +++ b/lewis_emulators/CCD100/__init__.py @@ -1,8 +1,3 @@ from .device import SimulatedCCD100 __all__ = ['SimulatedCCD100'] - - - - - diff --git a/lewis_emulators/CCD100/device.py b/lewis_emulators/CCD100/device.py index 6c41a2a2..61462397 100644 --- a/lewis_emulators/CCD100/device.py +++ b/lewis_emulators/CCD100/device.py @@ -6,5 +6,3 @@ class SimulatedCCD100(Device): setpoint = 0.00 units = "" setpoint_mode = 1 - - diff --git a/lewis_emulators/__init__.py b/lewis_emulators/__init__.py index 3be6bd29..c3961685 100644 --- a/lewis_emulators/__init__.py +++ b/lewis_emulators/__init__.py @@ -1 +1 @@ -from __future__ import absolute_import \ No newline at end of file +from __future__ import absolute_import diff --git a/lewis_emulators/amint2l/device.py b/lewis_emulators/amint2l/device.py index b4d5dc83..b4dc1a21 100644 --- a/lewis_emulators/amint2l/device.py +++ b/lewis_emulators/amint2l/device.py @@ -6,63 +6,44 @@ class SimulatedAmint2l(StateMachineDevice): """ - Simulated AM Int2-L pressure transducer + Simulated AM Int2-L pressure transducer. """ def _initialize_data(self): - """ - - Sets the initial state of the device - + Sets the initial state of the device. """ - self._pressure = 2.0 self.address = "AB" def _get_state_handlers(self): - """ - Returns: states and their names - """ - return { - DefaultState.NAME: DefaultState(), - } + return {DefaultState.NAME: DefaultState()} def _get_initial_state(self): """ - Returns: the name of the initial state - """ return DefaultState.NAME def _get_transition_handlers(self): - """ - Returns: the state transitions - """ - - return OrderedDict([ - ]) + return OrderedDict() @property def pressure(self): """ - Returns: the pressure - """ return self._pressure @pressure.setter def pressure(self, pressure): """ - :param pressure: set the pressure :return: """ diff --git a/lewis_emulators/amint2l/interfaces/__init__.py b/lewis_emulators/amint2l/interfaces/__init__.py index 3108e653..1e1decfb 100644 --- a/lewis_emulators/amint2l/interfaces/__init__.py +++ b/lewis_emulators/amint2l/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import Amint2lStreamInterface -__all__ = ['Amint2lStreamInterface'] \ No newline at end of file +__all__ = ['Amint2lStreamInterface'] diff --git a/lewis_emulators/amint2l/states.py b/lewis_emulators/amint2l/states.py index d9e8716b..c2beedfb 100644 --- a/lewis_emulators/amint2l/states.py +++ b/lewis_emulators/amint2l/states.py @@ -4,9 +4,5 @@ class DefaultState(State): """ Device is in default state. - """ NAME = 'Default' - - - diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index e8ffe4d0..81f8c353 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -8,7 +8,9 @@ class SimulatedInstron(StateMachineDevice): def _initialize_data(self): - """ Initialize all of the device's attributes """ + """ + Initialize all of the device's attributes. + """ # When initialisation is complete, this is set to true and the device will enter a running state self.ready = True @@ -87,4 +89,3 @@ def set_movement_type(self, mov_type): def set_current_time(self): self.current_time = time.time() - diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index 387fb406..300c9121 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -5,10 +5,8 @@ class DefaultState(State): def in_state(self, dt): device = self._context - device.set_current_time() if device.watchdog_refresh_time + 3 < time.time() and device.get_control_mode() != 0: print "Watchdog time expired, going back to front panel control mode" device.set_control_mode(0) - diff --git a/lewis_emulators/iris_cryo_valve/__init__.py b/lewis_emulators/iris_cryo_valve/__init__.py index 6a83a462..79e07f99 100644 --- a/lewis_emulators/iris_cryo_valve/__init__.py +++ b/lewis_emulators/iris_cryo_valve/__init__.py @@ -1,8 +1,3 @@ from .device import SimulatedIrisCryoValve __all__ = ['SimulatedIrisCryoValve'] - - - - - diff --git a/lewis_emulators/iris_cryo_valve/device.py b/lewis_emulators/iris_cryo_valve/device.py index d37bd213..8562e617 100644 --- a/lewis_emulators/iris_cryo_valve/device.py +++ b/lewis_emulators/iris_cryo_valve/device.py @@ -3,6 +3,3 @@ class SimulatedIrisCryoValve(Device): is_open = False - - - diff --git a/lewis_emulators/iris_cryo_valve/interfaces/__init__.py b/lewis_emulators/iris_cryo_valve/interfaces/__init__.py index 71f9193c..6df99c98 100644 --- a/lewis_emulators/iris_cryo_valve/interfaces/__init__.py +++ b/lewis_emulators/iris_cryo_valve/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import IrisCryoValveStreamInterface -__all__ = ['IrisCryoValveStreamInterface'] \ No newline at end of file +__all__ = ['IrisCryoValveStreamInterface'] diff --git a/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py b/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py index 02137798..9ef94fcf 100644 --- a/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py +++ b/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py @@ -26,4 +26,3 @@ def set_closed(self): def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) - diff --git a/lewis_emulators/keithley_2400/__init__.py b/lewis_emulators/keithley_2400/__init__.py index 684ff8c2..ea6e9ec3 100644 --- a/lewis_emulators/keithley_2400/__init__.py +++ b/lewis_emulators/keithley_2400/__init__.py @@ -1,8 +1,3 @@ from .device import SimulatedKeithley2400 __all__ = ['SimulatedKeithley2400'] - - - - - diff --git a/lewis_emulators/keithley_2400/device.py b/lewis_emulators/keithley_2400/device.py index fcd94ddb..fe2525b2 100644 --- a/lewis_emulators/keithley_2400/device.py +++ b/lewis_emulators/keithley_2400/device.py @@ -16,7 +16,9 @@ class SimulatedKeithley2400(StateMachineDevice): RESISTANCE_RANGE_MULTIPLIER = 2.1 def _initialize_data(self): - """ Initialize all of the device's attributes """ + """ + Initialize all of the device's attributes. + """ self.serial_command_mode = True # Power properties @@ -81,7 +83,9 @@ def get_resistance(self, as_string=False): return self._format_power_output(self._resistance(), as_string) def update(self, dt): - """ Update the current and voltage values based on the current mode and time elapsed """ + """ + Update the current and voltage values based on the current mode and time elapsed. + """ def update_value(value): return abs(value + uniform(-1,1)*dt) new_current = max(update_value(self._current), SimulatedKeithley2400.MINIMUM_CURRENT) @@ -98,12 +102,16 @@ def update_value(value): self._voltage = new_voltage def reset(self): - """ Set all the attributes back to their initial values """ + """ + Set all the attributes back to their initial values. + """ self._initialize_data() @staticmethod def _check_mode(mode, mode_class): - """ Make sure the mode requested exists in the related class """ + """ + Make sure the mode requested exists in the related class. + """ if mode in mode_class.MODES: return True else: diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index 1eee7fd8..d88c7b10 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -41,10 +41,10 @@ class Keithley2400StreamInterface(StreamAdapter): out_terminator = "\r\n" def get_values(self): - """ Get the current, voltage and resistance readings + """ + Get the current, voltage and resistance readings - Returns: - string : A string of 3 doubles: voltage, current, resistance. In that order + :return: A string of 3 doubles: voltage, current, resistance. In that order """ return ", ".join([ self._device.get_voltage(as_string=True), @@ -53,12 +53,16 @@ def get_values(self): ]) if self._device.get_output_mode() == OutputMode.ON else None def reset(self): - """ Resets the device """ + """ + Resets the device. + """ self._device.reset() return "*RST" def identify(self): - """ Replies with the device's identity """ + """ + Replies with the device's identity. + """ return "Keithley 2400 Source Meter emulator" def set_current(self, value): @@ -70,7 +74,9 @@ def set_voltage(self, value): return "Voltage set to: " + str(value) def _set_mode(self, set_method, mode, command): - """ The generic form of how mode sets are executed and responded to """ + """ + The generic form of how mode sets are executed and responded to. + """ set_method(mode) return command + " " + mode @@ -129,6 +135,5 @@ def get_voltage_compliance(self): return self._device.get_voltage_compliance() def handle_error(self, request, error): - print "An error occurred at request " + repr(request) + ": " + repr(error) + print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) - diff --git a/lewis_emulators/keithley_2400/utilities.py b/lewis_emulators/keithley_2400/utilities.py index 75bfb564..9585e0f8 100644 --- a/lewis_emulators/keithley_2400/utilities.py +++ b/lewis_emulators/keithley_2400/utilities.py @@ -1,3 +1,5 @@ def format_value(f, as_string): - """ Format a floating point value into either a string or return it as is """ + """ + Format a floating point value into either a string or return it as is. + """ return "{0:.3f}".format(f) if as_string else f diff --git a/lewis_emulators/mk2_chopper/chopper_type.py b/lewis_emulators/mk2_chopper/chopper_type.py index f124b687..da99cdbb 100644 --- a/lewis_emulators/mk2_chopper/chopper_type.py +++ b/lewis_emulators/mk2_chopper/chopper_type.py @@ -25,7 +25,6 @@ def __init__(self, max_frequency, manufacturer): manufacturer_low = manufacturer.lower() self._manufacturer = manufacturer_low if manufacturer_low in ChopperType.MANUFACTURERS else ChopperType.INDRAMAT - def get_closest_valid_frequency(self, frequency): return self._get_frequency_and_phase_closest_to_frequency(frequency)[0] diff --git a/lewis_emulators/mk2_chopper/device.py b/lewis_emulators/mk2_chopper/device.py index 915ebe77..ab66cf5b 100644 --- a/lewis_emulators/mk2_chopper/device.py +++ b/lewis_emulators/mk2_chopper/device.py @@ -6,7 +6,9 @@ class SimulatedMk2Chopper(StateMachineDevice): def _initialize_data(self): - """ Initialize all of the device's attributes """ + """ + Initialize all of the device's attributes. + """ self._type = ChopperType(50, ChopperType.INDRAMAT) self._demanded_frequency = self._type.get_frequency() diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index 635561c4..060690ef 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -1,9 +1,11 @@ from lewis.adapters.stream import StreamAdapter, Cmd from ..chopper_type import ChopperType + def filled_int(val, length): """ - Takes a value and returns a zero padded representation of the integer component + Takes a value and returns a zero padded representation of the integer component. + :param val: The original value. :param length: Minimum length of the returned string :return: Zero-padded integer representation (if possible) of string. Original string used if integer conversion diff --git a/lewis_emulators/mk2_chopper/states.py b/lewis_emulators/mk2_chopper/states.py index 056db4b6..798e87f9 100644 --- a/lewis_emulators/mk2_chopper/states.py +++ b/lewis_emulators/mk2_chopper/states.py @@ -4,6 +4,7 @@ # Would rather this were in device but causes Lewis to fail MAX_TEMPERATURE = 1 + def output_current_state(device, state_name): print "{0}: Freq {1:.2f}, Phase {2:.2f}, Error {3:.2f}, Temperature {4:.2f}".format( state_name.upper(), @@ -13,6 +14,7 @@ def output_current_state(device, state_name): device.get_temperature(), ) + class DefaultInitState(State): pass diff --git a/lewis_emulators/neocera_ltc21/constants.py b/lewis_emulators/neocera_ltc21/constants.py index 8d07a3a8..45d3e0f1 100644 --- a/lewis_emulators/neocera_ltc21/constants.py +++ b/lewis_emulators/neocera_ltc21/constants.py @@ -1,5 +1,5 @@ """ -Constants associated with the NEOCERA +Constants associated with the NEOCERA. """ # Index in the arrays of the heater output diff --git a/lewis_emulators/neocera_ltc21/device.py b/lewis_emulators/neocera_ltc21/device.py index 4dff01ee..3a4518c3 100644 --- a/lewis_emulators/neocera_ltc21/device.py +++ b/lewis_emulators/neocera_ltc21/device.py @@ -9,15 +9,12 @@ class SimulatedNeocera(StateMachineDevice): """ - Simulated Neocera LTG21 temperature controller + Simulated Neocera LTG21 temperature controller. """ def _initialize_data(self): - """ - - Sets the initial state of the device - + Sets the initial state of the device. """ # desired current state of the system @@ -56,11 +53,8 @@ def _initialize_data(self): self._error = NeoceraDeviceErrors() def _get_state_handlers(self): - """ - - Returns: states and their names - + :return: states and their names """ return { MonitorState.NAME: MonitorState(), @@ -69,20 +63,14 @@ def _get_state_handlers(self): def _get_initial_state(self): """ - - Returns: the name of the initial state - + :return: the name of the initial state """ return ControlState.NAME def _get_transition_handlers(self): - """ - - Returns: the state transitions - + :return: the state transitions """ - return OrderedDict([ ((MonitorState.NAME, ControlState.NAME), lambda: self.current_state == ControlState.NAME), ((ControlState.NAME, MonitorState.NAME), lambda: self.current_state == MonitorState.NAME), @@ -90,25 +78,19 @@ def _get_transition_handlers(self): def set_state_monitor(self): """ - - Sets the current state to MONITOR - + Sets the current state to MONITOR. """ self.current_state = MonitorState.NAME def set_state_control(self): """ - - Sets the current state to CONTROL - + Sets the current state to CONTROL. """ self.current_state = ControlState.NAME @property def state(self): """ - - Returns: the state - + :return: the state """ return self._csm.state diff --git a/lewis_emulators/neocera_ltc21/device_errors.py b/lewis_emulators/neocera_ltc21/device_errors.py index b09d17ab..83e467f4 100644 --- a/lewis_emulators/neocera_ltc21/device_errors.py +++ b/lewis_emulators/neocera_ltc21/device_errors.py @@ -1,8 +1,6 @@ - - class NeoceraDeviceErrors(object): """ - Class to represent errors + Class to represent errors. """ # bad parameter has been encountered diff --git a/lewis_emulators/neocera_ltc21/interfaces/__init__.py b/lewis_emulators/neocera_ltc21/interfaces/__init__.py index 693c44e4..105d559e 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/__init__.py +++ b/lewis_emulators/neocera_ltc21/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import NeoceraStreamInterface -__all__ = ['NeoceraStreamInterface'] \ No newline at end of file +__all__ = ['NeoceraStreamInterface'] diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index 0517b517..b13f0130 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -10,7 +10,7 @@ class NeoceraStreamInterface(StreamAdapter): """ - Stream interface for the serial port + Stream interface for the serial port. """ commands = { @@ -33,12 +33,10 @@ class NeoceraStreamInterface(StreamAdapter): out_terminator = ";\n" def get_state(self): - """ - Gets the current state of the device - - Returns: a single character string containing a number which represents the state of the device + Gets the current state of the device. + :return: a single character string containing a number which represents the state of the device """ if self._device.state == MonitorState.NAME: @@ -49,23 +47,19 @@ def get_state(self): def get_temperature_and_unit(self, sensor_number): """ Return the temperature and unit for the sensor number given. - Args: - sensor_number: sensor number - - Returns: formatted temperature and unit for the device + :param sensor_number: sensor number + :return: formatted temperature and unit for the device """ return self._get_indexed_value_with_unit(self._device.temperatures, sensor_number) def _get_indexed_value_with_unit(self, device_values, item_number): """ - Get a temperature like value back from device temperatures in the format produced by the device - Args: - device_values: device value, e.g. temperatures list - item_number: item to return - - Returns: temp and units; e.g. setpoint 1.2K + Get a temperature like value back from device temperatures in the format produced by the device. + :param device_values: device value, e.g. temperatures list + :param item_number: item to return + :return: temp and units; e.g. setpoint 1.2K """ try: sensor_index = int(item_number) - 1 @@ -86,24 +80,18 @@ def _get_indexed_value_with_unit(self, device_values, item_number): def get_setpoint_and_unit(self, output_number): """ - - Args: - output_number: the number of set point top return; 1=HEATER, 2=ANALOG - - Returns: setpoint with unit - + :param output_number: the number of set point top return; 1=HEATER, 2=ANALOG + :return: setpoint with unit """ return self._get_indexed_value_with_unit(self._device.setpoints, output_number) def set_setpoint(self, output_number, value): """ Set the setpoint. - Args: - output_number: output number; 1=HEATER, 2=ANALOG - value: value to set it to - - Returns: blank + :param output_number: output number; 1=HEATER, 2=ANALOG + :param value: value to set it to + :return: """ try: output_index = int(output_number) - 1 @@ -118,14 +106,12 @@ def set_setpoint(self, output_number, value): def get_output_config(self, output_number): """ Reply to output configuration query. - # Example QOUT?1; produces -> 2;4;3; - # Example QOUT?2; produces -> 3;5; - - Args: - output_number: The output number being queries; 1 HEATER, 2 Analogue - Returns: configuration as a string; sensor source;control;heater_range + Example QOUT?1; produces -> 2;4;3; + Example QOUT?2; produces -> 3;5; + :param output_number: The output number being queries; 1 HEATER, 2 Analogue + :return: configuration as a string; sensor source;control;heater_range """ device = self._device @@ -147,37 +133,29 @@ def get_output_config(self, output_number): def set_heater_control(self, control_type_number): """ - Set the heater output control - Args: - control_type_number: control type to set the heater to - - Returns: None + Set the heater output control. + :param control_type_number: control type to set the heater to + :return: None """ - self._set_output_control(HEATER_INDEX, control_type_number) def set_analog_control(self, control_type_number): """ - Set the analog output control - Args: - control_type_number: control type to set the heater to - - Returns: None + Set the analog output control. + :param control_type_number: control type to set the heater to + :return: None """ - self._set_output_control(ANALOG_INDEX, control_type_number) def _set_output_control(self, output_index, control_type_number): """ - Set the output control for either the heater or the analog output - Args: - output_index: output index - control_type_number: control type to set - - Returns: None + Set the output control for either the heater or the analog output. + :param output_index: output index + :param control_type_number: control type to set + :return: None """ device = self._device try: @@ -196,28 +174,25 @@ def _set_output_control(self, output_index, control_type_number): def get_heater(self): """ - - Returns: Heater output - + :return: Heater output """ return "{0:5.1f}".format(self._device.heater) def get_pid(self, output_number): """ - Get the PID and other info of the output. Information is + Get the PID and other info of the output. + + Information is: P, I, D, fixed power settting, for heater: power limit for analog: gain and offset - Exmaples: + Examples: QPID?1; -> 24.999;32.;8.;0.0;100.; QPID?2; -> 99.999;10.;0.0;0.0;1.;0.0; - Args: - output_number: output number; - - Returns: various info as a string - + :param output_number: output number; + :return: various info as a string """ device = self._device try: @@ -236,16 +211,14 @@ def get_pid(self, output_number): def set_pid_heater(self, p, i, d, fixed_power, limit): """ - Set the pid settings for the heater - Args: - p: p - i: i - d: d - fixed_power: fixed power - limit: limit of the heater - - Returns: None - + Set the pid settings for the heater. + + :param p: p + :param i: i + :param d: d + :param fixed_power: fixed power + :param limit: limit of the heater + :returns: None """ pid_settings = self._device.pid[HEATER_INDEX] try: @@ -261,17 +234,15 @@ def set_pid_heater(self, p, i, d, fixed_power, limit): def set_pid_analog(self, p, i, d, fixed_power, gain, offset): """ - Set the pid settings for the analog output - Args: - p: p - i: i - d: d - fixed_power: fixed power - gain: gain of the output - offset: offset for the output - - Returns: None - + Set the pid settings for the analog output. + + :param p: p + :param i: i + :param d: d + :param fixed_power: fixed power + :param gain: gain of the output + :param offset: offset for the output + :return: None """ pid_settings = self._device.pid[ANALOG_INDEX] try: @@ -285,17 +256,15 @@ def set_pid_analog(self, p, i, d, fixed_power, gain, offset): def _set_pid(self, p, i, d, fixed_power, pid_settings): """ - Common function to set p,i,d and power - Args: - Args: - p: p - i: i - d: d - fixed_power: fixed power - pid_settings: in which to set them + Common function to set p,i,d and power. - Returns: None + :param p: p + :param i: i + :param d: d + :param fixed_power: fixed power + :param pid_settings: in which to set them + :return: None """ pid_settings["P"] = float(p) pid_settings["I"] = float(i) @@ -304,12 +273,9 @@ def _set_pid(self, p, i, d, fixed_power, pid_settings): def handle_error(self, request, error): """ - Handles errors. - Args: - request: - error: - + :param request: + :param error: """ print "An error occurred at request " + repr(request) + ": " + repr(error) diff --git a/lewis_emulators/neocera_ltc21/states.py b/lewis_emulators/neocera_ltc21/states.py index 917bc1f9..b87b5e8d 100644 --- a/lewis_emulators/neocera_ltc21/states.py +++ b/lewis_emulators/neocera_ltc21/states.py @@ -7,15 +7,15 @@ class OffState(State): """ Device is in off state. - It does not display the temperature on the front it is not monitoring or controlling it. + It does not display the temperature on the front it is not monitoring or controlling it. """ NAME = 'off' class MonitorState(State): """ - Temperature is being monitored but heater is switched off + Temperature is being monitored but heater is switched off. """ NAME = 'monitor' diff --git a/lewis_emulators/tpg26x/device.py b/lewis_emulators/tpg26x/device.py index d6d6cd0b..9ae743e7 100644 --- a/lewis_emulators/tpg26x/device.py +++ b/lewis_emulators/tpg26x/device.py @@ -6,17 +6,13 @@ class SimulatedTpg26x(StateMachineDevice): """ - Simulated TPG26x + Simulated TPG26x. """ def _initialize_data(self): - """ - - Sets the initial state of the device - + Sets the initial state of the device. """ - self._pressure1 = 2.0 self._pressure2 = 3.0 self._error1 = 0 @@ -24,34 +20,22 @@ def _initialize_data(self): self._units = 0 def _get_state_handlers(self): - """ - Returns: states and their names - """ - return { - DefaultState.NAME: DefaultState(), - } + return {DefaultState.NAME: DefaultState()} def _get_initial_state(self): """ - Returns: the name of the initial state - """ return DefaultState.NAME def _get_transition_handlers(self): - """ - Returns: the state transitions - """ - - return OrderedDict([ - ]) + return OrderedDict() @property def pressure1(self): @@ -63,7 +47,8 @@ def pressure1(self): @pressure1.setter def pressure1(self, pressure): """ - Set the pressure for pressure 1 + Set the pressure for pressure 1. + :param pressure: the pressure value to set the first pressure to """ self._pressure1 = pressure @@ -71,14 +56,15 @@ def pressure1(self, pressure): @property def pressure2(self): """ - Returns: the second pressure + Returns: the second pressure. """ return self._pressure2 @pressure2.setter def pressure2(self, pressure): """ - Set the pressure for pressure 2 + Set the pressure for pressure 2. + :param pressure: the pressure value to set the second pressure to """ self._pressure2 = pressure @@ -93,7 +79,8 @@ def units(self): @units.setter def units(self, units): """ - Set the units for the TPG26x + Set the units for the TPG26x. + :param units: the units to set the device to as a string """ self._units = units @@ -108,7 +95,8 @@ def error1(self): @error1.setter def error1(self, state): """ - Set the error state for pressure 1 + Set the error state for pressure 1. + :param state: the error code state for pressure 1 """ self._error1 = state @@ -123,8 +111,8 @@ def error2(self): @error2.setter def error2(self, state): """ - Set the error state for pressure 2 + Set the error state for pressure 2. + :param state: the error code state for pressure 2 """ self._error2 = state - diff --git a/lewis_emulators/tpg26x/interfaces/__init__.py b/lewis_emulators/tpg26x/interfaces/__init__.py index e0d4fb8c..6aa931d9 100644 --- a/lewis_emulators/tpg26x/interfaces/__init__.py +++ b/lewis_emulators/tpg26x/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import Tpg26xStreamInterface -__all__ = ['Tpg26xStreamInterface'] \ No newline at end of file +__all__ = ['Tpg26xStreamInterface'] diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py index c46eafc6..07970b82 100644 --- a/lewis_emulators/tpg26x/interfaces/stream_interface.py +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -6,7 +6,6 @@ class Tpg26xStreamInterface(StreamAdapter): """ Stream interface for the serial port """ - _last_command = None ACK = chr(6) @@ -22,18 +21,18 @@ class Tpg26xStreamInterface(StreamAdapter): def handle_error(self, request, error): """ - If command is not recognised print and error - - Args: - request: requested string - error: problem + If command is not recognised print and error. + :param request: requested string + :param error: problem + :return: """ print "An error occurred at request " + repr(request) + ": " + repr(error) def acknowledge_pressure(self): """ - Acknowledge that the request for current pressure was received + Acknowledge that the request for current pressure was received. + :return: ASCII acknowledgement character (0x6) """ self._last_command = "PRX" @@ -41,7 +40,8 @@ def acknowledge_pressure(self): def acknowledge_units(self): """ - Acknowledge that the request for current units was received + Acknowledge that the request for current units was received. + :return: ASCII acknowledgement character (0x6) """ self._last_command = "UNI" @@ -50,6 +50,7 @@ def acknowledge_units(self): def handle_enquiry(self): """ Handle an enquiry using the last command sent. + :return: """ @@ -62,7 +63,7 @@ def handle_enquiry(self): def get_pressure(self): """ - Get the current pressure of the TPG26x + Get the current pressure of the TPG26x. Returns: a string with pressure and error codes """ @@ -71,7 +72,7 @@ def get_pressure(self): def get_units(self): """ - Get the current units of the TPG26x + Get the current units of the TPG26x. Returns: a string representing the units """ @@ -79,8 +80,9 @@ def get_units(self): def set_units(self, units): """ - Set the units of the TPG26x - :param: the unit flag to change the units too + Set the units of the TPG26x. + + :param units: the unit flag to change the units to """ if self._last_command is None: self._last_command = "UNI" @@ -88,4 +90,3 @@ def set_units(self, units): self._device.units = units self._last_command = None - diff --git a/lewis_emulators/tpg26x/states.py b/lewis_emulators/tpg26x/states.py index d9e8716b..c2beedfb 100644 --- a/lewis_emulators/tpg26x/states.py +++ b/lewis_emulators/tpg26x/states.py @@ -4,9 +4,5 @@ class DefaultState(State): """ Device is in default state. - """ NAME = 'Default' - - - diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 740ec042..0fa4468b 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -5,19 +5,16 @@ class CmdBuilder(object): """ - Build a command for the stream adapter + Build a command for the stream adapter. """ def __init__(self, target_method, arg_sep=",", ignore=""): """ - Create a builder. Use build to create the final objecy - Args: - target_method: name of the method target to call when the reg ex matches - arg_sep: seperators between the arguments - ignore: set of characters to ignore between text and arguments - - Returns: + Create a builder. Use build to create the final object + :param target_method: name of the method target to call when the reg ex matches + :param arg_sep: separators between the arguments + :param ignore: set of characters to ignore between text and arguments """ self._target_method = target_method self._arg_sep = arg_sep @@ -30,24 +27,20 @@ def __init__(self, target_method, arg_sep=",", ignore=""): def escape(self, text): """ - Add some text to the regex which is esacped - Args: - text: text to add - - Returns: builder - + Add some text to the regex which is escaped. + + :param text: text to add + :return: builder """ self._reg_ex += re.escape(text) + self._ignore return self def arg(self, arg_regex): """ - Add an argument to the command - Args: - arg_regex: regex for the argument (capture group will be added) - - Returns: builder + Add an argument to the command. + :param arg_regex: regex for the argument (capture group will be added) + :return: builder """ self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore self._current_sep = self._arg_sep @@ -55,43 +48,42 @@ def arg(self, arg_regex): def float(self): """ - Add a float argument - Returns: builder + Add a float argument. + :return: builder """ return self.arg(r"[+-]?\d+\.?\d*") def digit(self): """ - Add a single digit argument - Returns: builder + Add a single digit argument. + :return: builder """ return self.arg(r"\d") def int(self): """ - Add an integer argument - Returns: builder + Add an integer argument. + :return: builder """ return self.arg(r"\d+") def build(self, *args, **kwargs): """ - Builds the CMd object based on the target and regular expression - Args: - *args: arguments to pass to Cmd constructor - **kwargs: key word arguments to pass to Cmd constructor - - Returns: Cmd object + Builds the CMd object based on the target and regular expression. + :param *args: arguments to pass to Cmd constructor + :param **kwargs: key word arguments to pass to Cmd constructor + :return: Cmd object """ return Cmd(self._target_method, self._reg_ex, *args, **kwargs) def add_ascii_character(self, char_number): """ - Add a single character based on its integer value, e.g. 49 is a + Add a single character based on its integer value, e.g. 49 is 'a'. + :param char_number: character number :return: self """ @@ -101,28 +93,32 @@ def add_ascii_character(self, char_number): def stx(self): """ - Add the STX character (0x2) to the string + Add the STX character (0x2) to the string. + :return: builder """ return self.add_ascii_character(2) def etx(self): """ - Add the ETX character (0x3) to the string + Add the ETX character (0x3) to the string. + :return: builder """ return self.add_ascii_character(3) def enq(self): """ - Add the ENQ character (0x5) to the string + Add the ENQ character (0x5) to the string. + :return: builder """ return self.add_ascii_character(5) def ack(self): """ - Add the ACK character (0x6) to the string + Add the ACK character (0x6) to the string. + :return: builder """ return self.add_ascii_character(6) diff --git a/lewis_emulators/volumetric_rig/__init__.py b/lewis_emulators/volumetric_rig/__init__.py index 992e8767..c45f2475 100644 --- a/lewis_emulators/volumetric_rig/__init__.py +++ b/lewis_emulators/volumetric_rig/__init__.py @@ -1,8 +1,3 @@ from .device import SimulatedVolumetricRig __all__ = ['SimulatedVolumetricRig'] - - - - - diff --git a/lewis_emulators/volumetric_rig/error_states.py b/lewis_emulators/volumetric_rig/error_states.py index 622f0350..2527cc68 100644 --- a/lewis_emulators/volumetric_rig/error_states.py +++ b/lewis_emulators/volumetric_rig/error_states.py @@ -1,6 +1,6 @@ class ErrorStates(object): """ - The possible error states the device can be in + The possible error states the device can be in. """ def __init__(self): self.run = False diff --git a/lewis_emulators/volumetric_rig/ethernet_device.py b/lewis_emulators/volumetric_rig/ethernet_device.py index 07ddb361..74ad912f 100644 --- a/lewis_emulators/volumetric_rig/ethernet_device.py +++ b/lewis_emulators/volumetric_rig/ethernet_device.py @@ -3,7 +3,7 @@ class EthernetDevice(object): """ - An ethernet device that the rig communicates with + An ethernet device that the rig communicates with. """ def __init__(self, ip): assert type(ip) is StringType diff --git a/lewis_emulators/volumetric_rig/gas.py b/lewis_emulators/volumetric_rig/gas.py index d83bec8b..90b49a52 100644 --- a/lewis_emulators/volumetric_rig/gas.py +++ b/lewis_emulators/volumetric_rig/gas.py @@ -4,7 +4,7 @@ class Gas(object): """ - A gas within the system, identified by either its name or an integer index + A gas within the system, identified by either its name or an integer index. """ def __init__(self, index, name): assert type(index) is IntType and type(name) is StringType diff --git a/lewis_emulators/volumetric_rig/interfaces/__init__.py b/lewis_emulators/volumetric_rig/interfaces/__init__.py index db4c0f08..c0003c66 100644 --- a/lewis_emulators/volumetric_rig/interfaces/__init__.py +++ b/lewis_emulators/volumetric_rig/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import VolumetricRigStreamInterface -__all__ = ['VolumetricRigStreamInterface'] \ No newline at end of file +__all__ = ['VolumetricRigStreamInterface'] diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 99ed4675..08d3b216 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -60,7 +60,7 @@ class VolumetricRigStreamInterface(StreamAdapter): def purge(self, chars): """ - Responds any current input to the screen without executing it + Responds any current input to the screen without executing it. :param chars: Whatever characters are left over in the buffer :return: Purge message including ignored input @@ -73,7 +73,8 @@ def purge(self, chars): ]) def get_identity(self): - """ Responds with the devices identity + """ + Responds with the devices identity. :return: Device identity """ @@ -104,7 +105,7 @@ def get_buffer_control_and_status(self, buffer_number_raw): Get information about a specific buffer, its valve state and the gases connected to it. :param buffer_number_raw : The buffer "number" entered by a user. Although a number is expected, the command - will accept other types of input + will accept other types of input :return: Information about the requested buffer """ buffer_number = convert_raw_to_int(buffer_number_raw) @@ -123,7 +124,7 @@ def get_buffer_control_and_status(self, buffer_number_raw): def get_ethernet_and_hmi_status(self): """ - Get information about the rig's hmi and plc ethernet devices + Get information about the rig's hmi and plc ethernet devices. :return: Information about the ethernet devices status. The syntax of the return string is odd: the separators are not consistent @@ -137,7 +138,7 @@ def get_ethernet_and_hmi_status(self): def get_gas_control_and_status(self): """ - Get a list of information about all the buffers, their associated gases and valve statuses + Get a list of information about all the buffers, their associated gases and valve statuses. :return: Buffer information. One line per buffer with a header """ @@ -149,7 +150,7 @@ def get_gas_control_and_status(self): def get_gas_mix_matrix(self): """ - Get information about which gases can be mixed together + Get information about which gases can be mixed together. :return: A 2D matrix representation of the ability to mix different gases with column and row titles """ @@ -185,13 +186,11 @@ def get_gas_mix_matrix(self): def gas_mix_check(self, gas1_index_raw, gas2_index_raw): """ - Query whether two gases can be mixed + Query whether two gases can be mixed. - Args: - gas1_index_raw : The index of the first gas. Although a number is expected, the command will + :param gas1_index_raw : The index of the first gas. Although a number is expected, the command will accept other types of input - gas2_index_raw : As above for the 2nd gas - + :param gas2_index_raw : As above for the 2nd gas :return: An echo of the name and index of the requested gases as well as an ok/NO indicating whether the gases can be mixed @@ -210,8 +209,7 @@ def gas_mix_check(self, gas1_index_raw, gas2_index_raw): def get_gas_number_available(self): """ - Get the number of available gases - + Get the number of available gases. :return: The number of available gases """ @@ -219,7 +217,7 @@ def get_gas_number_available(self): def get_hmi_status(self): """ - Get the current status of the HMI + Get the current status of the HMI. :return: Information about the HMI """ @@ -234,7 +232,7 @@ def get_hmi_status(self): def get_hmi_count_cycles(self): """ - Get information about how frequently the HMI is disconnected + Get information about how frequently the HMI is disconnected. :return: A list of integers indicating the number of occurrences of a disconnected count cycle of a specific length @@ -243,10 +241,9 @@ def get_hmi_count_cycles(self): def get_memory_location(self, location_raw): """ - Get the value stored in a particular location in memory + Get the value stored in a particular location in memory. - Args: - location_raw : The memory location to read. Although a number is expected, the command will accept other + :param location_raw : The memory location to read. Although a number is expected, the command will accept other types of input :return: The memory location and the value stored there @@ -257,7 +254,7 @@ def get_memory_location(self, location_raw): def get_pressure_and_temperature_status(self): """ - Get the status of the temperature and pressure sensors + Get the status of the temperature and pressure sensors. :return: A letter for each sensor indicating its status. Refer to the spec for the meaning and sensor order """ @@ -277,7 +274,7 @@ def get_pressure_and_temperature_status(self): def get_pressures(self): """ - Get the current pressure sensor readings, and target pressure + Get the current pressure sensor readings, and target pressure. :return: The pressure readings from each of the pressure sensors and the target pressure which, if exceeded, will cause all buffer valves to close and disable @@ -288,7 +285,7 @@ def get_pressures(self): def get_temperatures(self): """ - Get the current temperature reading + Get the current temperature reading. :return: The current temperature for each of the temperature sensors """ @@ -297,7 +294,7 @@ def get_temperatures(self): def get_valve_status(self): """ - Get the status of the buffer and system valves + Get the status of the buffer and system valves. :return: The status of each of the system valves represented by a letter. Refer to the specification for the exact meaning and order @@ -313,7 +310,7 @@ def get_valve_status(self): @staticmethod def _convert_raw_valve_to_int(raw): """ - Get the valve number from its identifier + Get the valve number from its identifier. :param raw: The raw valve identifier :return: An integer indicating the valve number @@ -328,7 +325,7 @@ def _convert_raw_valve_to_int(raw): def _set_valve_status(self, valve_identifier_raw, set_to_open=None, set_to_enabled=None): """ - Change the valve status + Change the valve status. :param valve_identifier_raw: A raw value that identifies the valve :param set_to_open: Whether to set the valve to open(True)/closed(False)/do noting(None) @@ -409,7 +406,7 @@ def _set_valve_status(self, valve_identifier_raw, set_to_open=None, set_to_enabl def close_valve(self, valve_number_raw): """ - Close a valve + Close a valve. :param valve_number_raw: The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply @@ -421,7 +418,7 @@ def close_valve(self, valve_number_raw): def open_valve(self, valve_number_raw): """ - Open a valve + Open a valve. :param valve_number_raw : The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply @@ -433,7 +430,7 @@ def open_valve(self, valve_number_raw): def enable_valve(self, valve_number_raw): """ - Enable a valve + Enable a valve. :param valve_number_raw: The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply @@ -445,7 +442,7 @@ def enable_valve(self, valve_number_raw): def disable_valve(self, valve_number_raw): """ - Disable a valve + Disable a valve. :param valve_number_raw: The number of the valve to close. The first n valves correspond to the buffers where n is the number of buffers. The n+1th valve is the cell valve, the n+2nd valve is for the vacuum. The supply @@ -520,7 +517,7 @@ def get_com_activity(self): def set_buffer_system_gas(self, buffer_index_raw, gas_index_raw): """ - Changes the system gas associated with a particular buffer + Changes the system gas associated with a particular buffer. :param buffer_index_raw: The index of the buffer to update :param gas_index_raw: The index of the gas to update @@ -559,7 +556,7 @@ def set_pressure_cycling(self, on_int_raw): def set_pressures(self, value_raw): """ - Set the reading for all pressure sensors to a fixed value + Set the reading for all pressure sensors to a fixed value. :param value_raw: The value to apply to the pressure sensors :return: Echo the new pressure @@ -570,7 +567,7 @@ def set_pressures(self, value_raw): def set_pressure_target(self, value_raw): """ - Set the target (limit) pressure for the system + Set the target (limit) pressure for the system. :param value_raw: The new pressure target :return: Echo the new target diff --git a/lewis_emulators/volumetric_rig/pressure_sensor.py b/lewis_emulators/volumetric_rig/pressure_sensor.py index 0981a9c2..f6dcca0d 100644 --- a/lewis_emulators/volumetric_rig/pressure_sensor.py +++ b/lewis_emulators/volumetric_rig/pressure_sensor.py @@ -12,7 +12,7 @@ def __init__(self): def set_value(self, v, target): """ - Updates the pressure reading with a new value along with the sensor's status + Updates the pressure reading with a new value along with the sensor's status. :param v: The new value :param target: The target/limit value diff --git a/lewis_emulators/volumetric_rig/seed_gas_data.py b/lewis_emulators/volumetric_rig/seed_gas_data.py index 4b61f6ec..e377f1d6 100644 --- a/lewis_emulators/volumetric_rig/seed_gas_data.py +++ b/lewis_emulators/volumetric_rig/seed_gas_data.py @@ -1,6 +1,6 @@ class SeedGasData(object): """ - Contains information about gases and their mixing properties used to set up the initial device state + Contains information about gases and their mixing properties used to set up the initial device state. """ # Gas names diff --git a/lewis_emulators/volumetric_rig/sensor.py b/lewis_emulators/volumetric_rig/sensor.py index 23882990..e4b65dad 100644 --- a/lewis_emulators/volumetric_rig/sensor.py +++ b/lewis_emulators/volumetric_rig/sensor.py @@ -4,7 +4,7 @@ class Sensor(object): """ - A basic sensor which monitors a value and keeps track of its own status + A basic sensor which monitors a value and keeps track of its own status. """ def __init__(self): self._status = SensorStatus.NO_REPLY diff --git a/lewis_emulators/volumetric_rig/sensor_status.py b/lewis_emulators/volumetric_rig/sensor_status.py index 2035ebcb..974b06ca 100644 --- a/lewis_emulators/volumetric_rig/sensor_status.py +++ b/lewis_emulators/volumetric_rig/sensor_status.py @@ -1,5 +1,5 @@ class SensorStatus(object): """ - An enumeration of possible sensor states + An enumeration of possible sensor states. """ UNKNOWN, DISABLED, NO_REPLY, VALUE_IN_RANGE, VALUE_TOO_LOW, VALUE_TOO_HIGH = (i for i in range(6)) diff --git a/lewis_emulators/volumetric_rig/system_gases.py b/lewis_emulators/volumetric_rig/system_gases.py index a43c8215..1fea9a35 100644 --- a/lewis_emulators/volumetric_rig/system_gases.py +++ b/lewis_emulators/volumetric_rig/system_gases.py @@ -3,7 +3,7 @@ class SystemGases(object): """ - The collection of gases that are available within the system + The collection of gases that are available within the system. """ def __init__(self, gases=list()): self._gases = set() diff --git a/lewis_emulators/volumetric_rig/two_gas_mixer.py b/lewis_emulators/volumetric_rig/two_gas_mixer.py index d2b24408..862b46f7 100644 --- a/lewis_emulators/volumetric_rig/two_gas_mixer.py +++ b/lewis_emulators/volumetric_rig/two_gas_mixer.py @@ -1,6 +1,6 @@ class TwoGasMixer(object): """ - Keeps a record of whether pairs of gases can be mixed + Keeps a record of whether pairs of gases can be mixed. """ def __init__(self): self.mixable = set() diff --git a/lewis_emulators/volumetric_rig/valve_status.py b/lewis_emulators/volumetric_rig/valve_status.py index 1decefce..18db2292 100644 --- a/lewis_emulators/volumetric_rig/valve_status.py +++ b/lewis_emulators/volumetric_rig/valve_status.py @@ -1,5 +1,5 @@ class ValveStatus(object): """ - An enumeration of possible valve states. OPEN_AND_DISABLED should never happen + An enumeration of possible valve states. OPEN_AND_DISABLED should never happen. """ OPEN_AND_ENABLED, CLOSED_AND_ENABLED, OPEN_AND_DISABLED, CLOSED_AND_DISABLED = (i for i in range(4)) \ No newline at end of file From f64025a6b1dadd7caa0640f16c797ed2503114fe Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Tue, 13 Jun 2017 09:39:32 +0100 Subject: [PATCH 0210/1466] Add support for firmware version --- lewis_emulators/superlogics/device.py | 26 +++++++++++-------- .../interfaces/stream_interface.py | 13 ++++++++-- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/lewis_emulators/superlogics/device.py b/lewis_emulators/superlogics/device.py index 19dc1149..89480154 100644 --- a/lewis_emulators/superlogics/device.py +++ b/lewis_emulators/superlogics/device.py @@ -18,7 +18,7 @@ def _initialize_data(self): """ self._values = [0.]*8 - self.address = "AB" + self._version = "A1.0" def _get_state_handlers(self): @@ -50,16 +50,6 @@ def _get_transition_handlers(self): return OrderedDict([ ]) - def get_values(self): - return self._values - - @property - def value1(self): - """ - Returns: the value of PV Value1 - """ - return self._values[0] - @property def values(self): """ @@ -74,3 +64,17 @@ def values(self, values): """ self._values = values + @property + def version(self): + """ + :return: The firmware version of the device + """ + return self._version + + @version.setter + def version(self, version): + """ + Set the firmware version of this device + :param version: the firmware version as a string + """ + self._version = version diff --git a/lewis_emulators/superlogics/interfaces/stream_interface.py b/lewis_emulators/superlogics/interfaces/stream_interface.py index 9f433047..acd6d27b 100644 --- a/lewis_emulators/superlogics/interfaces/stream_interface.py +++ b/lewis_emulators/superlogics/interfaces/stream_interface.py @@ -8,7 +8,8 @@ class SuperlogicsStreamInterface(StreamAdapter): """ commands = { - CmdBuilder("get_values").escape("#").arg("[0-9]+").build() + CmdBuilder("get_values").escape("#").arg("[0-9]+").build(), + CmdBuilder("get_version").escape("$").arg("[0-9]+").escape("F").build() } in_terminator = "\r" @@ -31,5 +32,13 @@ def get_values(self, address): Returns: List of values, one for each connected channel """ - formatted_values = map(lambda s: "+{0:.2f}".format(s), self._device.get_values()) + formatted_values = map(lambda s: "+{0:.2f}".format(s), self._device.values) return ",".join(formatted_values) + + def get_version(self, address): + """ + Get the firmware version from the device + :param address: the address to read the version from + :return: string representing the firmware version for the address + """ + return "!{0}{1}".format(address, self._device.version) From c4081e320d91aaea8e3a76bad56bafb04e2f3b0d Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Tue, 13 Jun 2017 16:45:35 +0100 Subject: [PATCH 0211/1466] Finish device emulator for SuperLogics --- lewis_emulators/superlogics/device.py | 82 +++++++++++++++---- .../interfaces/stream_interface.py | 12 ++- 2 files changed, 76 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/superlogics/device.py b/lewis_emulators/superlogics/device.py index 89480154..ebb101f7 100644 --- a/lewis_emulators/superlogics/device.py +++ b/lewis_emulators/superlogics/device.py @@ -17,8 +17,11 @@ def _initialize_data(self): """ - self._values = [0.]*8 - self._version = "A1.0" + self._disconnected = False + self._values_1 = [0.]*8 + self._values_2 = [0.]*8 + self._version_1 = "A1.0" + self._version_2 = "A1.0" def _get_state_handlers(self): @@ -51,30 +54,77 @@ def _get_transition_handlers(self): ]) @property - def values(self): + def values_1(self): """ - :return: All values on this channel + :return: All values on this address """ - return self._values + return self._values_1 - @values.setter - def values(self, values): + @values_1.setter + def values_1(self, values): """ - :param values: set all the values on this channel + :param values: set all the values on this address """ - self._values = values + self._values_1 = values @property - def version(self): + def values_2(self): """ - :return: The firmware version of the device + :return: All values on this address """ - return self._version + return self._values_2 - @version.setter - def version(self, version): + @values_2.setter + def values_2(self, values): """ - Set the firmware version of this device + :param values: set all the values on this address + """ + self._values_2 = values + + @property + def version_1(self): + """ + :return: The firmware version of address 1 of the device + """ + return self._version_1 + + @version_1.setter + def version_1(self, version): + """ + Set the firmware version of address 1 this device + :param version: the firmware version of address 1 as a string + """ + self._version_1 = version + + @property + def version_2(self): + """ + :return: The firmware version of address 2 of the device + """ + print self._version_2 + return self._version_2 + + @version_2.setter + def version_2(self, version): + """ + Set the firmware version on address 2 of this device :param version: the firmware version as a string """ - self._version = version + print version + self._version_2 = version + + @property + def disconnected(self): + """ + Get if the current device is in a "disconnected" state + :return: bool for if the device is disconnected + """ + return self._disconnected + + @disconnected.setter + def disconnected(self, value): + """ + Set if the device is disconnected (for testing purposes) + :param value: bool to set whether the device is disconnected + """ + self._disconnected = value diff --git a/lewis_emulators/superlogics/interfaces/stream_interface.py b/lewis_emulators/superlogics/interfaces/stream_interface.py index acd6d27b..c327dcb7 100644 --- a/lewis_emulators/superlogics/interfaces/stream_interface.py +++ b/lewis_emulators/superlogics/interfaces/stream_interface.py @@ -32,7 +32,11 @@ def get_values(self, address): Returns: List of values, one for each connected channel """ - formatted_values = map(lambda s: "+{0:.2f}".format(s), self._device.values) + if self._device.disconnected: + return None + + values = self._device.values_1 if address == "01" else self._device.values_2 + formatted_values = map(lambda s: "+{0:.2f}".format(s), values) return ",".join(formatted_values) def get_version(self, address): @@ -41,4 +45,8 @@ def get_version(self, address): :param address: the address to read the version from :return: string representing the firmware version for the address """ - return "!{0}{1}".format(address, self._device.version) + if self._device.disconnected: + return None + + version = self._device.version_1 if address == "01" else self._device.version_2 + return "!{0}{1}".format(address, version) From d1879b37de3a018660224ac748b91a717351c251 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 19 Jun 2017 17:08:47 +0100 Subject: [PATCH 0212/1466] Give stress rig waveform generator --- lewis_emulators/instron_stress_rig/channel.py | 26 +++++++++++++++++ lewis_emulators/instron_stress_rig/device.py | 29 +------------------ .../quart_counter_actions.py | 13 +++++++++ .../quart_counter_states.py | 8 +++++ .../instron_stress_rig/waveform_generator.py | 15 ++++++++++ .../waveform_generator_states.py | 11 +++++++ .../instron_stress_rig/waveform_types.py | 10 +++++++ 7 files changed, 84 insertions(+), 28 deletions(-) create mode 100644 lewis_emulators/instron_stress_rig/channel.py create mode 100644 lewis_emulators/instron_stress_rig/quart_counter_actions.py create mode 100644 lewis_emulators/instron_stress_rig/quart_counter_states.py create mode 100644 lewis_emulators/instron_stress_rig/waveform_generator.py create mode 100644 lewis_emulators/instron_stress_rig/waveform_generator_states.py create mode 100644 lewis_emulators/instron_stress_rig/waveform_types.py diff --git a/lewis_emulators/instron_stress_rig/channel.py b/lewis_emulators/instron_stress_rig/channel.py new file mode 100644 index 00000000..06beda25 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/channel.py @@ -0,0 +1,26 @@ +class Channel(object): + def __init__(self): + self.waveform_type = 0 + self.step_time = 0 + self.ramp_amplitude_setpoint = 0 + self.scale = 10 + self.value = 0 + self.type_1 = 0 + self.type_2 = 0 + + +class PositionChannel(Channel): + def __init__(self): + super(PositionChannel, self).__init__() + + +class StressChannel(Channel): + def __init__(self): + super(StressChannel, self).__init__() + self.area = 1 + + +class StrainChannel(Channel): + def __init__(self): + super(StrainChannel, self).__init__() + self.length = 1 \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index b5b8acd1..73d16786 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -1,6 +1,7 @@ from collections import OrderedDict from states import DefaultState, GoingToSetpointState from lewis.devices import StateMachineDevice +from channel import PositionChannel, StrainChannel, StressChannel import time @@ -157,31 +158,3 @@ def get_chan_type_1(self, channel): def get_chan_type_2(self, channel): return self.channels[channel].type_2 - - -class Channel(object): - def __init__(self): - self.waveform_type = 0 - self.step_time = 0 - self.ramp_amplitude_setpoint = 0 - self.scale = 10 - self.value = 0 - self.type_1 = 0 - self.type_2 = 0 - - -class PositionChannel(Channel): - def __init__(self): - super(PositionChannel, self).__init__() - - -class StressChannel(Channel): - def __init__(self): - super(StressChannel, self).__init__() - self.area = 1 - - -class StrainChannel(Channel): - def __init__(self): - super(StrainChannel, self).__init__() - self.length = 1 diff --git a/lewis_emulators/instron_stress_rig/quart_counter_actions.py b/lewis_emulators/instron_stress_rig/quart_counter_actions.py new file mode 100644 index 00000000..ee9075d1 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/quart_counter_actions.py @@ -0,0 +1,13 @@ +class QuarterCycleCounterActions(object): + NO_ACTION = 0 + ACTUATOR_OFF = 1 + STOP = 2 + HOLD = 3 + TRANSFER = 4 + LOG = 5 + RESET = 6 + SYS_STOP = 10 + SYS_HOLD = 11 + SYS_TRANSFER = 12 + SYS_LOG = 13 + SYS_RESET = 14 \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/quart_counter_states.py b/lewis_emulators/instron_stress_rig/quart_counter_states.py new file mode 100644 index 00000000..b9b89466 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/quart_counter_states.py @@ -0,0 +1,8 @@ +class QuarterCycleCounterStates(object): + OFF = 0 + PREPARED = 1 + ARMED = 2 + TRIPPED = 4 + PREPARED_WITH_GLOBAL_DISARM_INHIBITED = 6 + ARMED_WITH_GLOBAL_DISARM_INHIBITED = 7 + TRIPPED_WITH_GLOBAL_DISARM_INHIBITED = 9 diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py new file mode 100644 index 00000000..3dd98674 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -0,0 +1,15 @@ +from waveform_generator_states import WaveformGeneratorStates +from waveform_types import WaveformTypes +from quart_counter_actions import QuarterCycleCounterActions +from quart_counter_states import QuarterCycleCounterStates + +class WaveformGenerator(object): + def __init__(self): + self.state = WaveformGeneratorStates.STOPPED + self.amplitude = 1.0 + self.frequency = 1.0 + self.type = WaveformTypes.SINE + self.quart_action = QuarterCycleCounterActions.NO_ACTION + self.quart = 0 + self.quart_state = QuarterCycleCounterStates.OFF + diff --git a/lewis_emulators/instron_stress_rig/waveform_generator_states.py b/lewis_emulators/instron_stress_rig/waveform_generator_states.py new file mode 100644 index 00000000..b624703d --- /dev/null +++ b/lewis_emulators/instron_stress_rig/waveform_generator_states.py @@ -0,0 +1,11 @@ +class WaveformGeneratorStates(object): + STOPPED = 0 + RUNNING = 1 + HOLDING = 2 + FINISHING = 3 + ABORTED = 4 + DISABLED = 5 + SLAVED = 6 + SLAVE_LOCKED = 7 + SWEEPING = 8 + DWELLING = 9 diff --git a/lewis_emulators/instron_stress_rig/waveform_types.py b/lewis_emulators/instron_stress_rig/waveform_types.py new file mode 100644 index 00000000..2bed34c9 --- /dev/null +++ b/lewis_emulators/instron_stress_rig/waveform_types.py @@ -0,0 +1,10 @@ +class WaveformTypes(object): + SINE = 0 + TRIANGLE = 1 + SQUARE = 2 + HAVERSINE = 3 + HAVERTRIANGLE = 4 + HAVERSQUARE = 5 + EXTERNAL_SENSOR = 6 + EXTERNAL_AUX = 7 + SAWTOOTH = 8 From 9859316b444d153250cc6f54e8ab5771eb012a2e Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 20 Jun 2017 09:29:55 +0100 Subject: [PATCH 0213/1466] Hook up waveform generation start/stop/abort to interface --- lewis_emulators/instron_stress_rig/device.py | 16 +++++++++++++++ .../interfaces/stream_interface.py | 17 ++++++++++++++++ .../instron_stress_rig/waveform_generator.py | 20 +++++++++++++++++++ 3 files changed, 53 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 73d16786..b7f9dd25 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -2,6 +2,7 @@ from states import DefaultState, GoingToSetpointState from lewis.devices import StateMachineDevice from channel import PositionChannel, StrainChannel, StressChannel +from waveform_generator import WaveformGenerator import time @@ -31,6 +32,8 @@ def _initialize_data(self): # Maps a channel number to a channel object self.channels = {1: PositionChannel(), 2: StressChannel(), 3: StrainChannel()} + self._waveform_generator = WaveformGenerator() + def raise_exception_if_cannot_write(self): if self._control_mode != 1: raise Exception("Not in the correct control mode to execute that command!") @@ -158,3 +161,16 @@ def get_chan_type_1(self, channel): def get_chan_type_2(self, channel): return self.channels[channel].type_2 + + def get_waveform_status(self): + return self._waveform_generator.state + + def abort_waveform_generation(self): + self._waveform_generator.abort() + + def stop_waveform_generation(self): + self._waveform_generator.stop() + + def start_waveform_generation(self): + self._waveform_generator.start() + diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 0e5c59c0..91feb99e 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -19,6 +19,8 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_movement_type", "^C1,([0-3])$"), Cmd("get_step_time", "^Q86,([1-3])$"), Cmd("set_step_time", "^C86,([1-3]),([0-9]*.[0-9]*)$"), + + # Channel commands Cmd("get_chan_waveform_type", "^Q2,([1-3])$"), Cmd("set_chan_waveform_type", "^C2,([1-3]),([0-5])$"), Cmd("get_ramp_amplitude_setpoint", "^Q4,([1-3])$"), @@ -29,6 +31,12 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_chan_area", "^Q341,([1-3])$"), Cmd("set_chan_area", "^C341,([1-3]),([0-9]*.[0-9]*)$"), Cmd("get_chan_type", "^Q307,([1-3])$"), + + # Waveform commands + Cmd("get_waveform_status", "^Q200$"), + Cmd("abort_waveform_generation", "^C200,0$"), + Cmd("start_waveform_generation", "^C200,1$"), + Cmd("stop_waveform_generation", "^C200,4$"), } in_terminator = "\r\n" @@ -112,3 +120,12 @@ def get_chan_type(self, channel): type_1 = self._device.get_chan_type_1(int(channel)) type_2 = self._device.get_chan_type_2(int(channel)) return "{a},{b}".format(a=type_1, b=type_2) + + def get_waveform_status(self): + return self._device.get_waveform_status() + + def abort_waveform_generation(self): + return self._device.abort_waveform_generation() + + def stop_waveform_generation(self): + return self._device.stop_waveform_generation() diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 3dd98674..909af9ad 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -2,6 +2,9 @@ from waveform_types import WaveformTypes from quart_counter_actions import QuarterCycleCounterActions from quart_counter_states import QuarterCycleCounterStates +from multiprocessing import Pool +from time import sleep + class WaveformGenerator(object): def __init__(self): @@ -13,3 +16,20 @@ def __init__(self): self.quart = 0 self.quart_state = QuarterCycleCounterStates.OFF + def abort(self): + self.state = WaveformGeneratorStates.ABORTED + + def stop(self): + def finish(): + self.state = WaveformGeneratorStates.STOPPED + + def wait_for_finish(): + time_to_finish = 3 + sleep(time_to_finish) + + if self.state in [WaveformGeneratorStates.RUNNING, WaveformGeneratorStates.HOLDING]: + self.state = WaveformGeneratorStates.FINISHING + Pool(processes=1).apply_async(wait_for_finish, [], finish) + + def start(self): + self.state = WaveformGeneratorStates.RUNNING From ef4a6bafd8a8b89bf60053ca42cf375386a61090 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 20 Jun 2017 09:40:59 +0100 Subject: [PATCH 0214/1466] Don't spin off solo async tasks. Let the state device handle it --- lewis_emulators/instron_stress_rig/device.py | 7 +++-- .../interfaces/stream_interface.py | 6 ++--- lewis_emulators/instron_stress_rig/states.py | 2 ++ .../instron_stress_rig/waveform_generator.py | 26 +++++++++++-------- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index b7f9dd25..f539d72b 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -168,9 +168,12 @@ def get_waveform_status(self): def abort_waveform_generation(self): self._waveform_generator.abort() - def stop_waveform_generation(self): - self._waveform_generator.stop() + def finish_waveform_generation(self): + self._waveform_generator.finish() def start_waveform_generation(self): self._waveform_generator.start() + def stop_waveform_generation_if_requested(self): + if self._waveform_generator.time_to_stop(): + self._waveform_generator.stop() diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 91feb99e..ff10df4d 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -36,7 +36,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_waveform_status", "^Q200$"), Cmd("abort_waveform_generation", "^C200,0$"), Cmd("start_waveform_generation", "^C200,1$"), - Cmd("stop_waveform_generation", "^C200,4$"), + Cmd("request_stop_waveform_generation", "^C200,4$"), } in_terminator = "\r\n" @@ -127,5 +127,5 @@ def get_waveform_status(self): def abort_waveform_generation(self): return self._device.abort_waveform_generation() - def stop_waveform_generation(self): - return self._device.stop_waveform_generation() + def request_stop_waveform_generation(self): + return self._device.finish_waveform_generation() diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index 07d6483a..fce19a5b 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -12,6 +12,8 @@ def in_state(self, dt): print "Watchdog time expired, going back to front panel control mode" device.set_control_mode(0) + device.stop_waveform_generator_if_requested() + class GoingToSetpointState(DefaultState): def in_state(self, dt): diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 909af9ad..1b57ac9d 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -2,11 +2,12 @@ from waveform_types import WaveformTypes from quart_counter_actions import QuarterCycleCounterActions from quart_counter_states import QuarterCycleCounterStates -from multiprocessing import Pool -from time import sleep +from datetime import datetime, timedelta class WaveformGenerator(object): + STOP_DELAY = timedelta(seconds=3) + def __init__(self): self.state = WaveformGeneratorStates.STOPPED self.amplitude = 1.0 @@ -15,21 +16,24 @@ def __init__(self): self.quart_action = QuarterCycleCounterActions.NO_ACTION self.quart = 0 self.quart_state = QuarterCycleCounterStates.OFF + self.stop_requested_at_time = None def abort(self): self.state = WaveformGeneratorStates.ABORTED + self.stop_requested_at_time = None - def stop(self): - def finish(): - self.state = WaveformGeneratorStates.STOPPED + def finish(self): + self.stop_requested_at_time = datetime.now() + self.state = WaveformGeneratorStates.FINISHING - def wait_for_finish(): - time_to_finish = 3 - sleep(time_to_finish) + def time_to_stop(self): + return self.stop_requested_at_time is not None and \ + (datetime.now() - self.stop_requested_at_time) > WaveformGenerator.STOP_DELAY - if self.state in [WaveformGeneratorStates.RUNNING, WaveformGeneratorStates.HOLDING]: - self.state = WaveformGeneratorStates.FINISHING - Pool(processes=1).apply_async(wait_for_finish, [], finish) + def stop(self): + self.stop_requested_at_time = None + self.state = WaveformGeneratorStates.STOPPED def start(self): self.state = WaveformGeneratorStates.RUNNING + self.stop_requested_at_time = None From f45837f1041a5b017e408a96a730b63fd06091a3 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 20 Jun 2017 14:00:32 +0100 Subject: [PATCH 0215/1466] Fix typo --- lewis_emulators/instron_stress_rig/states.py | 2 +- .../instron_stress_rig/waveform_generator.py | 31 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index fce19a5b..96cd5a1e 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -12,7 +12,7 @@ def in_state(self, dt): print "Watchdog time expired, going back to front panel control mode" device.set_control_mode(0) - device.stop_waveform_generator_if_requested() + device.stop_waveform_generation_if_requested() class GoingToSetpointState(DefaultState): diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 1b57ac9d..9a9c45fb 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -1,7 +1,7 @@ -from waveform_generator_states import WaveformGeneratorStates +from waveform_generator_states import WaveformGeneratorStates as GenStates from waveform_types import WaveformTypes -from quart_counter_actions import QuarterCycleCounterActions -from quart_counter_states import QuarterCycleCounterStates +from quart_counter_actions import QuarterCycleCounterActions as QuartActions +from quart_counter_states import QuarterCycleCounterStates as QuartStates from datetime import datetime, timedelta @@ -9,31 +9,38 @@ class WaveformGenerator(object): STOP_DELAY = timedelta(seconds=3) def __init__(self): - self.state = WaveformGeneratorStates.STOPPED + self.state = GenStates.STOPPED self.amplitude = 1.0 self.frequency = 1.0 self.type = WaveformTypes.SINE - self.quart_action = QuarterCycleCounterActions.NO_ACTION + self.quart_action = QuartActions.NO_ACTION self.quart = 0 - self.quart_state = QuarterCycleCounterStates.OFF + self.quart_state = QuartStates.OFF self.stop_requested_at_time = None def abort(self): - self.state = WaveformGeneratorStates.ABORTED - self.stop_requested_at_time = None + if self._active(): + self.state = GenStates.ABORTED + self.stop_requested_at_time = None def finish(self): - self.stop_requested_at_time = datetime.now() - self.state = WaveformGeneratorStates.FINISHING + if self._active(): + self.stop_requested_at_time = datetime.now() + self.state = GenStates.FINISHING def time_to_stop(self): + print "Waveform generator state: " + str(self.state) return self.stop_requested_at_time is not None and \ (datetime.now() - self.stop_requested_at_time) > WaveformGenerator.STOP_DELAY def stop(self): self.stop_requested_at_time = None - self.state = WaveformGeneratorStates.STOPPED + self.state = GenStates.STOPPED def start(self): - self.state = WaveformGeneratorStates.RUNNING + self.state = GenStates.RUNNING self.stop_requested_at_time = None + + def _active(self): + return self.state in [GenStates.RUNNING, GenStates.HOLDING] + From 6b701a6be52ffb05062c4edd29213adbb33b97b3 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Wed, 21 Jun 2017 14:00:18 +0100 Subject: [PATCH 0216/1466] Add amplitude and frequency commands --- lewis_emulators/instron_stress_rig/device.py | 40 +++++++++++++++++++ .../interfaces/stream_interface.py | 26 ++++++++++++ .../instron_stress_rig/waveform_generator.py | 8 ++-- 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index f539d72b..0db86669 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -52,6 +52,10 @@ def set_channel_param(self, index, param, value): def get_channel_param(self, index, param): return getattr(self.channels[int(index)], str(param)) + # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 + def set_waveform_state(self, value): + self._waveform_generator.state = value + def _get_initial_state(self): return 'default' @@ -177,3 +181,39 @@ def start_waveform_generation(self): def stop_waveform_generation_if_requested(self): if self._waveform_generator.time_to_stop(): self._waveform_generator.stop() + + def get_waveform_type(self, channel): + try: + return self._waveform_generator.type[channel] + except NameError: + print "Unable to get waveform generator type. Channel: {0}".format(channel) + + def set_waveform_type(self, channel, value): + try: + self._waveform_generator.type[channel] = value + except NameError: + print "Unable to set waveform generator type. Channel: {0}, Value: {1}".format(channel, value) + + def get_waveform_amplitude(self, channel): + try: + return self._waveform_generator.amplitude[channel] + except NameError: + print "Unable to get waveform generator amplitude. Channel: {0}".format(channel) + + def set_waveform_amplitude(self, channel, value): + try: + self._waveform_generator.amplitude[channel] = value + except NameError: + print "Unable to set waveform generator amplitude. Channel: {0}, Value: {1}".format(channel, value) + + def get_waveform_frequency(self, channel): + try: + return self._waveform_generator.frequency[channel] + except NameError: + print "Unable to get waveform generator frequency. Channel: {0}".format(channel) + + def set_waveform_frequency(self, channel, value): + try: + self._waveform_generator.frequency[channel] = value + except NameError: + print "Unable to set waveform generator frequency. Channel: {0}, Value: {1}".format(channel, value) diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index ff10df4d..447f361e 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -37,6 +37,12 @@ class InstronStreamInterface(StreamAdapter): Cmd("abort_waveform_generation", "^C200,0$"), Cmd("start_waveform_generation", "^C200,1$"), Cmd("request_stop_waveform_generation", "^C200,4$"), + Cmd("get_waveform_type", "^Q201,([1-3])$"), + Cmd("set_waveform_type", "^C201,([1-3]),([0-8])$"), + Cmd("get_waveform_amplitude", "^Q202,([1-3])$"), + Cmd("set_waveform_amplitude", "^C202,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_waveform_frequency", "^Q203,([1-3])$"), + Cmd("set_waveform_frequency", "^C203,([1-3]),([0-9]*.[0-9]*)$"), } in_terminator = "\r\n" @@ -121,6 +127,8 @@ def get_chan_type(self, channel): type_2 = self._device.get_chan_type_2(int(channel)) return "{a},{b}".format(a=type_1, b=type_2) + # Waveform generation + def get_waveform_status(self): return self._device.get_waveform_status() @@ -129,3 +137,21 @@ def abort_waveform_generation(self): def request_stop_waveform_generation(self): return self._device.finish_waveform_generation() + + def get_waveform_type(self, channel): + return self._device.get_waveform_type(int(channel)) + + def set_waveform_type(self, channel, type): + return self._device.set_waveform_type(int(channel), int(type)) + + def get_waveform_amplitude(self, channel): + return self._device.get_waveform_amplitude(int(channel)) + + def set_waveform_amplitude(self, channel, value): + return self._device.set_waveform_amplitude(int(channel), float(value)) + + def get_waveform_frequency(self, channel): + return self._device.get_waveform_frequency(int(channel)) + + def set_waveform_frequency(self, channel, value): + return self._device.set_waveform_frequency(int(channel), float(value)) diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 9a9c45fb..4fba07f0 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -10,9 +10,9 @@ class WaveformGenerator(object): def __init__(self): self.state = GenStates.STOPPED - self.amplitude = 1.0 - self.frequency = 1.0 - self.type = WaveformTypes.SINE + self.amplitude = {i+1: 0.0 for i in range(3)} + self.frequency = {i+1: 0.0 for i in range(3)} + self.type = {i+1: WaveformTypes.SINE for i in range(3)} self.quart_action = QuartActions.NO_ACTION self.quart = 0 self.quart_state = QuartStates.OFF @@ -29,7 +29,6 @@ def finish(self): self.state = GenStates.FINISHING def time_to_stop(self): - print "Waveform generator state: " + str(self.state) return self.stop_requested_at_time is not None and \ (datetime.now() - self.stop_requested_at_time) > WaveformGenerator.STOP_DELAY @@ -43,4 +42,3 @@ def start(self): def _active(self): return self.state in [GenStates.RUNNING, GenStates.HOLDING] - From 92dbcb20273327869db5eb8654440bdb32b0b6c6 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 22 Jun 2017 11:57:04 +0100 Subject: [PATCH 0217/1466] Add quart commands --- lewis_emulators/instron_stress_rig/device.py | 18 ++++++++++++ .../interfaces/stream_interface.py | 24 ++++++++++++++++ .../quart_counter_states.py | 8 ------ ... => quart_cycle_event_detector_actions.py} | 2 +- .../quarter_cycle_event_detector.py | 28 +++++++++++++++++++ .../quarter_cycle_event_detector_states.py | 8 ++++++ lewis_emulators/instron_stress_rig/states.py | 11 ++++++-- .../instron_stress_rig/waveform_generator.py | 8 ++---- 8 files changed, 91 insertions(+), 16 deletions(-) delete mode 100644 lewis_emulators/instron_stress_rig/quart_counter_states.py rename lewis_emulators/instron_stress_rig/{quart_counter_actions.py => quart_cycle_event_detector_actions.py} (80%) create mode 100644 lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py create mode 100644 lewis_emulators/instron_stress_rig/quarter_cycle_event_detector_states.py diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 0db86669..f826a470 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -217,3 +217,21 @@ def set_waveform_frequency(self, channel, value): self._waveform_generator.frequency[channel] = value except NameError: print "Unable to set waveform generator frequency. Channel: {0}, Value: {1}".format(channel, value) + + def quarter_cycle_event(self): + self._waveform_generator.quart_counter.count() + + def arm_quarter_counter(self): + self._waveform_generator.quart_counter.arm() + + def get_quarter_counts(self): + return self._waveform_generator.quart_counter.counts + + def set_max_quarter_counts(self, val): + self._waveform_generator.quart_counter.max_counts = val + + def set_quarter_counter_off(self): + self._waveform_generator.quart_counter.off() + + def get_quarter_counter_status(self): + return self._waveform_generator.quart_counter.state diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 447f361e..e72a12aa 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -43,6 +43,13 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_waveform_amplitude", "^C202,([1-3]),([0-9]*.[0-9]*)$"), Cmd("get_waveform_frequency", "^Q203,([1-3])$"), Cmd("set_waveform_frequency", "^C203,([1-3]),([0-9]*.[0-9]*)$"), + + # Waveform (quarter counter event detector) commands + Cmd("arm_quarter_counter", "^C212,2$"), + Cmd("get_quarter_counts", "^Q210$"), + Cmd("set_max_quarter_counts", "^Q209,([0-9]+)$"), + Cmd("set_quarter_counter_off", "^C212,0$"), + Cmd("get_quarter_counter_status", "^Q212$"), } in_terminator = "\r\n" @@ -155,3 +162,20 @@ def get_waveform_frequency(self, channel): def set_waveform_frequency(self, channel, value): return self._device.set_waveform_frequency(int(channel), float(value)) + + # Waveform quarter counter + + def arm_quarter_counter(self): + self._device.arm_quarter_counter() + + def get_quarter_counts(self): + return self._device.get_quarter_counts() + + def set_max_quarter_counts(self, val): + self._device.set_max_quarter_counts(int(val)) + + def set_quarter_counter_off(self): + self._device.set_quarter_counter_off() + + def get_quarter_counter_status(self): + return self._device.get_quarter_counter_status() diff --git a/lewis_emulators/instron_stress_rig/quart_counter_states.py b/lewis_emulators/instron_stress_rig/quart_counter_states.py deleted file mode 100644 index b9b89466..00000000 --- a/lewis_emulators/instron_stress_rig/quart_counter_states.py +++ /dev/null @@ -1,8 +0,0 @@ -class QuarterCycleCounterStates(object): - OFF = 0 - PREPARED = 1 - ARMED = 2 - TRIPPED = 4 - PREPARED_WITH_GLOBAL_DISARM_INHIBITED = 6 - ARMED_WITH_GLOBAL_DISARM_INHIBITED = 7 - TRIPPED_WITH_GLOBAL_DISARM_INHIBITED = 9 diff --git a/lewis_emulators/instron_stress_rig/quart_counter_actions.py b/lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py similarity index 80% rename from lewis_emulators/instron_stress_rig/quart_counter_actions.py rename to lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py index ee9075d1..7f41badf 100644 --- a/lewis_emulators/instron_stress_rig/quart_counter_actions.py +++ b/lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py @@ -1,4 +1,4 @@ -class QuarterCycleCounterActions(object): +class QuarterCycleEventDetectorActions(object): NO_ACTION = 0 ACTUATOR_OFF = 1 STOP = 2 diff --git a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py new file mode 100644 index 00000000..70d04d8f --- /dev/null +++ b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py @@ -0,0 +1,28 @@ +from quarter_cycle_event_detector_states import QuarterCycleEventDetectorStates as QCEDStates +from quart_cycle_event_detector_actions import QuarterCycleEventDetectorActions as QCEDActions + + +class QuarterCycleEventDetector(object): + + def __init__(self): + self.counts = 0 + self.max_counts = 0 + self.state = QCEDStates.PREPARED + self.action = QCEDActions.NO_ACTION + + def reset(self): + self.__init__() + + def arm(self): + self.state = QCEDStates.ARMED + + def count(self): + if self.state == QCEDStates.ARMED: + self.counts += 1 + # This is intentionally == not >=. The counter won't trip if it already exceeds max_counts + if self.counts == self.max_counts: + self.state == QCEDStates.TRIPPED + + def off(self): + self.state = QCEDStates.OFF + self.counts = 0 diff --git a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector_states.py b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector_states.py new file mode 100644 index 00000000..723bc96b --- /dev/null +++ b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector_states.py @@ -0,0 +1,8 @@ +class QuarterCycleEventDetectorStates(object): + OFF = 0 + PREPARED = 1 + ARMED = 2 + TRIPPED = 4 + PREPARED_INHIBITED = 6 + ARMED_INHIBITED = 7 + TRIPPED_INHIBITED = 9 diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index 96cd5a1e..b5aca0c5 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -4,6 +4,8 @@ class DefaultState(State): + TIME_SINCE_LAST_QUART_COUNT = 0 + def in_state(self, dt): device = self._context device.set_current_time() @@ -14,12 +16,17 @@ def in_state(self, dt): device.stop_waveform_generation_if_requested() + GoingToSetpointState.TIME_SINCE_LAST_QUART_COUNT += dt + if GoingToSetpointState.TIME_SINCE_LAST_QUART_COUNT > 1.0: + GoingToSetpointState.TIME_SINCE_LAST_QUART_COUNT = 0.0 + device.quarter_cycle_event() + class GoingToSetpointState(DefaultState): + def in_state(self, dt): super(GoingToSetpointState, self).in_state(dt) device = self._context device.channels[device.control_channel].value = approaches.linear(device.channels[device.control_channel].value, device.channels[device.control_channel].ramp_amplitude_setpoint, - 0.001, dt) - + 0.001, dt) \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 4fba07f0..d8506035 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -1,8 +1,7 @@ from waveform_generator_states import WaveformGeneratorStates as GenStates from waveform_types import WaveformTypes -from quart_counter_actions import QuarterCycleCounterActions as QuartActions -from quart_counter_states import QuarterCycleCounterStates as QuartStates from datetime import datetime, timedelta +from quarter_cycle_event_detector import QuarterCycleEventDetector as QCED class WaveformGenerator(object): @@ -13,10 +12,8 @@ def __init__(self): self.amplitude = {i+1: 0.0 for i in range(3)} self.frequency = {i+1: 0.0 for i in range(3)} self.type = {i+1: WaveformTypes.SINE for i in range(3)} - self.quart_action = QuartActions.NO_ACTION - self.quart = 0 - self.quart_state = QuartStates.OFF self.stop_requested_at_time = None + self.quart_counter = QCED() def abort(self): if self._active(): @@ -39,6 +36,7 @@ def stop(self): def start(self): self.state = GenStates.RUNNING self.stop_requested_at_time = None + self.quart_counter.start() def _active(self): return self.state in [GenStates.RUNNING, GenStates.HOLDING] From 7ba62f2b489feee0b805e0fdcaa26a8c911d9f5c Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 22 Jun 2017 16:23:39 +0100 Subject: [PATCH 0218/1466] Add hold --- lewis_emulators/instron_stress_rig/device.py | 3 +++ .../interfaces/stream_interface.py | 17 ++++++++++++----- .../instron_stress_rig/waveform_generator.py | 5 ++++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index f826a470..3c0d23e4 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -218,6 +218,9 @@ def set_waveform_frequency(self, channel, value): except NameError: print "Unable to set waveform generator frequency. Channel: {0}, Value: {1}".format(channel, value) + def set_waveform_hold(self): + self._waveform_generator.hold() + def quarter_cycle_event(self): self._waveform_generator.quart_counter.count() diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index e72a12aa..444090ec 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -43,6 +43,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("set_waveform_amplitude", "^C202,([1-3]),([0-9]*.[0-9]*)$"), Cmd("get_waveform_frequency", "^Q203,([1-3])$"), Cmd("set_waveform_frequency", "^C203,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("set_waveform_hold", "^C213,3$"), # Waveform (quarter counter event detector) commands Cmd("arm_quarter_counter", "^C212,2$"), @@ -139,29 +140,35 @@ def get_chan_type(self, channel): def get_waveform_status(self): return self._device.get_waveform_status() + def start_waveform_generation(self): + self._device.start_waveform_generation() + def abort_waveform_generation(self): - return self._device.abort_waveform_generation() + self._device.abort_waveform_generation() def request_stop_waveform_generation(self): - return self._device.finish_waveform_generation() + self._device.finish_waveform_generation() def get_waveform_type(self, channel): return self._device.get_waveform_type(int(channel)) def set_waveform_type(self, channel, type): - return self._device.set_waveform_type(int(channel), int(type)) + self._device.set_waveform_type(int(channel), int(type)) def get_waveform_amplitude(self, channel): return self._device.get_waveform_amplitude(int(channel)) def set_waveform_amplitude(self, channel, value): - return self._device.set_waveform_amplitude(int(channel), float(value)) + self._device.set_waveform_amplitude(int(channel), float(value)) def get_waveform_frequency(self, channel): return self._device.get_waveform_frequency(int(channel)) def set_waveform_frequency(self, channel, value): - return self._device.set_waveform_frequency(int(channel), float(value)) + self._device.set_waveform_frequency(int(channel), float(value)) + + def set_waveform_hold(self): + self._device.set_waveform_hold() # Waveform quarter counter diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index d8506035..9fc3e46a 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -36,7 +36,10 @@ def stop(self): def start(self): self.state = GenStates.RUNNING self.stop_requested_at_time = None - self.quart_counter.start() + + def hold(self): + if self._active(): + self.state = GenStates.HOLDING def _active(self): return self.state in [GenStates.RUNNING, GenStates.HOLDING] From 0d2620a3b26f03813ad993ed28a9252976aab6b0 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 22 Jun 2017 16:37:56 +0100 Subject: [PATCH 0219/1466] Add no action log maintain --- lewis_emulators/instron_stress_rig/device.py | 3 +++ .../interfaces/stream_interface.py | 4 ++++ .../quart_cycle_event_detector_actions.py | 13 ------------- .../quarter_cycle_event_detector.py | 2 -- .../instron_stress_rig/waveform_generator.py | 4 ++++ 5 files changed, 11 insertions(+), 15 deletions(-) delete mode 100644 lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 3c0d23e4..8907a695 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -238,3 +238,6 @@ def set_quarter_counter_off(self): def get_quarter_counter_status(self): return self._waveform_generator.quart_counter.state + + def set_waveform_maintain_log(self): + return self._waveform_generator.maintain_log() diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 444090ec..8d77077d 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -44,6 +44,7 @@ class InstronStreamInterface(StreamAdapter): Cmd("get_waveform_frequency", "^Q203,([1-3])$"), Cmd("set_waveform_frequency", "^C203,([1-3]),([0-9]*.[0-9]*)$"), Cmd("set_waveform_hold", "^C213,3$"), + Cmd("set_waveform_maintain_log", "^C214,0$"), # Waveform (quarter counter event detector) commands Cmd("arm_quarter_counter", "^C212,2$"), @@ -186,3 +187,6 @@ def set_quarter_counter_off(self): def get_quarter_counter_status(self): return self._device.get_quarter_counter_status() + + def set_waveform_maintain_log(self): + self._device.set_waveform_maintain_log() \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py b/lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py deleted file mode 100644 index 7f41badf..00000000 --- a/lewis_emulators/instron_stress_rig/quart_cycle_event_detector_actions.py +++ /dev/null @@ -1,13 +0,0 @@ -class QuarterCycleEventDetectorActions(object): - NO_ACTION = 0 - ACTUATOR_OFF = 1 - STOP = 2 - HOLD = 3 - TRANSFER = 4 - LOG = 5 - RESET = 6 - SYS_STOP = 10 - SYS_HOLD = 11 - SYS_TRANSFER = 12 - SYS_LOG = 13 - SYS_RESET = 14 \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py index 70d04d8f..0d920478 100644 --- a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py +++ b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py @@ -1,5 +1,4 @@ from quarter_cycle_event_detector_states import QuarterCycleEventDetectorStates as QCEDStates -from quart_cycle_event_detector_actions import QuarterCycleEventDetectorActions as QCEDActions class QuarterCycleEventDetector(object): @@ -8,7 +7,6 @@ def __init__(self): self.counts = 0 self.max_counts = 0 self.state = QCEDStates.PREPARED - self.action = QCEDActions.NO_ACTION def reset(self): self.__init__() diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 9fc3e46a..891e9cd5 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -41,5 +41,9 @@ def hold(self): if self._active(): self.state = GenStates.HOLDING + def maintain_log(self): + # Does nothing in current emulator + pass + def _active(self): return self.state in [GenStates.RUNNING, GenStates.HOLDING] From 1f861ca2c9e1611c2c71ac877e473b80b3c04a60 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 22 Jun 2017 17:24:53 +0100 Subject: [PATCH 0220/1466] Ensure going to setpoint deactivates when setpoint is reached --- lewis_emulators/instron_stress_rig/states.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index 07d6483a..a4a8aeb6 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -12,7 +12,6 @@ def in_state(self, dt): print "Watchdog time expired, going back to front panel control mode" device.set_control_mode(0) - class GoingToSetpointState(DefaultState): def in_state(self, dt): super(GoingToSetpointState, self).in_state(dt) @@ -21,3 +20,7 @@ def in_state(self, dt): device.channels[device.control_channel].ramp_amplitude_setpoint, 0.001, dt) + def on_exit(self, dt): + device = self._context + device._movement_type = 3 + From 4b7c3b110cdb21919a0d8eece57134361a6e8d99 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 23 Jun 2017 15:25:44 +0100 Subject: [PATCH 0221/1466] Add realistic waveform generation --- lewis_emulators/instron_stress_rig/device.py | 8 ++- .../interfaces/stream_interface.py | 8 +-- .../quarter_cycle_event_detector.py | 6 ++ lewis_emulators/instron_stress_rig/states.py | 26 +++++--- .../instron_stress_rig/waveform_generator.py | 60 +++++++++++++++++-- 5 files changed, 91 insertions(+), 17 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 8907a695..09702ccb 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultState, GoingToSetpointState +from states import DefaultState, GoingToSetpointState, GeneratingWaveformState from lewis.devices import StateMachineDevice from channel import PositionChannel, StrainChannel, StressChannel from waveform_generator import WaveformGenerator @@ -42,6 +42,7 @@ def _get_state_handlers(self): return { 'default': DefaultState(), 'going': GoingToSetpointState(), + 'waveform': GeneratingWaveformState(), } # This is a workaround for https://github.com/DMSC-Instrument-Data/lewis/issues/248 @@ -63,6 +64,8 @@ def _get_transition_handlers(self): return OrderedDict([ (('default', 'going'), lambda: self._movement_type != 0 and self.channels[self.control_channel].value != self.channels[self.control_channel].ramp_amplitude_setpoint), (('going', 'default'), lambda: self._movement_type == 0 or self.channels[self.control_channel].value == self.channels[self.control_channel].ramp_amplitude_setpoint), + (('default', 'waveform'), lambda: self._waveform_generator.active()), + (('waveform', 'default'), lambda: not self._waveform_generator.active()) ]) def get_control_channel(self): @@ -241,3 +244,6 @@ def get_quarter_counter_status(self): def set_waveform_maintain_log(self): return self._waveform_generator.maintain_log() + + def get_waveform_value(self): + return self._waveform_generator.get_value(self.control_channel) diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 8d77077d..a1920d0b 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -39,10 +39,10 @@ class InstronStreamInterface(StreamAdapter): Cmd("request_stop_waveform_generation", "^C200,4$"), Cmd("get_waveform_type", "^Q201,([1-3])$"), Cmd("set_waveform_type", "^C201,([1-3]),([0-8])$"), - Cmd("get_waveform_amplitude", "^Q202,([1-3])$"), - Cmd("set_waveform_amplitude", "^C202,([1-3]),([0-9]*.[0-9]*)$"), - Cmd("get_waveform_frequency", "^Q203,([1-3])$"), - Cmd("set_waveform_frequency", "^C203,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_waveform_amplitude", "^Q203,([1-3])$"), + Cmd("set_waveform_amplitude", "^C203,([1-3]),([0-9]*.[0-9]*)$"), + Cmd("get_waveform_frequency", "^Q202,([1-3])$"), + Cmd("set_waveform_frequency", "^C202,([1-3]),([0-9]*.[0-9]*)$"), Cmd("set_waveform_hold", "^C213,3$"), Cmd("set_waveform_maintain_log", "^C214,0$"), diff --git a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py index 0d920478..600c5d5a 100644 --- a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py +++ b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py @@ -24,3 +24,9 @@ def count(self): def off(self): self.state = QCEDStates.OFF self.counts = 0 + + def cycles(self, fractional=True): + if fractional: + return self.counts/4.0 + else: + return self.counts//4 diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index b5aca0c5..05fa654a 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -4,7 +4,6 @@ class DefaultState(State): - TIME_SINCE_LAST_QUART_COUNT = 0 def in_state(self, dt): device = self._context @@ -16,11 +15,6 @@ def in_state(self, dt): device.stop_waveform_generation_if_requested() - GoingToSetpointState.TIME_SINCE_LAST_QUART_COUNT += dt - if GoingToSetpointState.TIME_SINCE_LAST_QUART_COUNT > 1.0: - GoingToSetpointState.TIME_SINCE_LAST_QUART_COUNT = 0.0 - device.quarter_cycle_event() - class GoingToSetpointState(DefaultState): @@ -29,4 +23,22 @@ def in_state(self, dt): device = self._context device.channels[device.control_channel].value = approaches.linear(device.channels[device.control_channel].value, device.channels[device.control_channel].ramp_amplitude_setpoint, - 0.001, dt) \ No newline at end of file + 0.001, dt) + +class GeneratingWaveformState(DefaultState): + + TIME_SINCE_LAST_QUART_COUNT = 0 + + @staticmethod + def increment_cycle(device, dt): + GeneratingWaveformState.TIME_SINCE_LAST_QUART_COUNT += dt + if GeneratingWaveformState.TIME_SINCE_LAST_QUART_COUNT > 1.0: + GeneratingWaveformState.TIME_SINCE_LAST_QUART_COUNT = 0.0 + device.quarter_cycle_event() + + def in_state(self, dt): + super(GeneratingWaveformState, self).in_state(dt) + device = self._context + + GeneratingWaveformState.increment_cycle(device,dt) + device.channels[device.control_channel].value = device.get_waveform_value() \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index 891e9cd5..e9e67707 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -2,6 +2,7 @@ from waveform_types import WaveformTypes from datetime import datetime, timedelta from quarter_cycle_event_detector import QuarterCycleEventDetector as QCED +import math class WaveformGenerator(object): @@ -10,18 +11,19 @@ class WaveformGenerator(object): def __init__(self): self.state = GenStates.STOPPED self.amplitude = {i+1: 0.0 for i in range(3)} - self.frequency = {i+1: 0.0 for i in range(3)} + self.frequency = {i+1: 1.0 for i in range(3)} self.type = {i+1: WaveformTypes.SINE for i in range(3)} self.stop_requested_at_time = None self.quart_counter = QCED() def abort(self): - if self._active(): + if self.active(): self.state = GenStates.ABORTED self.stop_requested_at_time = None + self.quart_counter.off() def finish(self): - if self._active(): + if self.active(): self.stop_requested_at_time = datetime.now() self.state = GenStates.FINISHING @@ -32,18 +34,66 @@ def time_to_stop(self): def stop(self): self.stop_requested_at_time = None self.state = GenStates.STOPPED + self.quart_counter.off() def start(self): self.state = GenStates.RUNNING self.stop_requested_at_time = None def hold(self): - if self._active(): + if self.active(): self.state = GenStates.HOLDING def maintain_log(self): # Does nothing in current emulator pass - def _active(self): + def active(self): return self.state in [GenStates.RUNNING, GenStates.HOLDING] + + def get_value(self, channel): + def sin(a, x, f): + return a*math.sin(math.pi*x*f) + + def square(a, x, f): + return math.copysign(a, sin(a, x, f)) + + def sawtooth(a, x, f): + return a*(x % 1.0/f) + + def triangle(a, x, f): + return a*(1 - 2*abs((f*x-0.5) % 2 - 1)) + + def haversine(a, x, f): + return a/2.0*(1.0 - math.cos(math.pi*f*x)) + + def havertriangle(a, x, f): + return a*(1 - abs(f*x % 2 - 1)) + + def haversquare(a, x, f): + return a/2.0*(1.0 + math.copysign(1.0, haversine(a, x, f)-0.5)) + + if self.active(): + amp = self.amplitude[channel] + freq = max(self.frequency[channel], 1.e-20) + wave_type = self.type[channel] + val = self.quart_counter.counts + + if not self.active(): + return 0.0 + elif wave_type == WaveformTypes.TRIANGLE: + return triangle(amp, val, freq) + elif wave_type == WaveformTypes.SAWTOOTH: + return sawtooth(amp, val, freq) + elif wave_type == WaveformTypes.SQUARE: + return square(amp, val, freq) + elif wave_type == WaveformTypes.HAVERSINE: + return haversine(amp, val, freq) + elif wave_type == WaveformTypes.HAVERSQUARE: + return havertriangle(amp, val, freq) + elif wave_type == WaveformTypes.HAVERTRIANGLE: + return haversquare(amp, val, freq) + else: + return sin(amp, val, freq) + + return 0.0 From 176d07385c49d61047b7996b659c65331a8dd4a9 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 23 Jun 2017 15:28:50 +0100 Subject: [PATCH 0222/1466] Functions the wrong way around --- lewis_emulators/instron_stress_rig/waveform_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index e9e67707..f580a1c4 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -90,9 +90,9 @@ def haversquare(a, x, f): elif wave_type == WaveformTypes.HAVERSINE: return haversine(amp, val, freq) elif wave_type == WaveformTypes.HAVERSQUARE: - return havertriangle(amp, val, freq) - elif wave_type == WaveformTypes.HAVERTRIANGLE: return haversquare(amp, val, freq) + elif wave_type == WaveformTypes.HAVERTRIANGLE: + return havertriangle(amp, val, freq) else: return sin(amp, val, freq) From 7dfac42b19b59465e3cce403358aeaf7b842f81d Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 23 Jun 2017 16:11:58 +0100 Subject: [PATCH 0223/1466] BODMAS --- lewis_emulators/instron_stress_rig/waveform_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/waveform_generator.py b/lewis_emulators/instron_stress_rig/waveform_generator.py index f580a1c4..92a23e5e 100644 --- a/lewis_emulators/instron_stress_rig/waveform_generator.py +++ b/lewis_emulators/instron_stress_rig/waveform_generator.py @@ -59,7 +59,7 @@ def square(a, x, f): return math.copysign(a, sin(a, x, f)) def sawtooth(a, x, f): - return a*(x % 1.0/f) + return a*(x % (1.0/f)) def triangle(a, x, f): return a*(1 - 2*abs((f*x-0.5) % 2 - 1)) From 19fe0d43c2e19693de887ad4aa5733d522625a28 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 13 Jul 2017 17:09:30 +0100 Subject: [PATCH 0224/1466] Renaming --- lewis_emulators/instron_stress_rig/device.py | 30 +++++++++---------- .../interfaces/stream_interface.py | 6 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index b5b8acd1..8d77fd52 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -18,7 +18,7 @@ def _initialize_data(self): self._watchdog_status = (0, 0) self._control_mode = 0 self._actuator_status = 0 - self._movement_type = 2 + self.movement_type = 0 self.current_time = 0 self.watchdog_refresh_time = 0 self.status = 7680 @@ -53,8 +53,8 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict([ - (('default', 'going'), lambda: self._movement_type != 0 and self.channels[self.control_channel].value != self.channels[self.control_channel].ramp_amplitude_setpoint), - (('going', 'default'), lambda: self._movement_type == 0 or self.channels[self.control_channel].value == self.channels[self.control_channel].ramp_amplitude_setpoint), + (('default', 'going'), lambda: self.movement_type != 0 and self.channels[self.control_channel].value != self.channels[self.control_channel].ramp_amplitude_setpoint), + (('going', 'default'), lambda: self.movement_type == 0 or self.channels[self.control_channel].value == self.channels[self.control_channel].ramp_amplitude_setpoint), ]) def get_control_channel(self): @@ -92,18 +92,18 @@ def set_actuator_status(self, status): self.raise_exception_if_cannot_write() self._actuator_status = int(status) if status == 0: - self._movement_type = 0 + self.movement_type = 0 def get_movement_type(self): - return self._movement_type + return self.movement_type def set_movement_type(self, mov_type): self.raise_exception_if_cannot_write() if self._waveform_mode == 0: - self._movement_type = mov_type + self.movement_type = mov_type else: - self._movement_type = mov_type + 3 + self.movement_type = mov_type + 3 def set_current_time(self): self.current_time = time.time() @@ -152,11 +152,11 @@ def set_chan_area(self, channel, value): assert isinstance(self.channels[channel], StressChannel), "Area only applies to stress channel" self.channels[channel].area = value - def get_chan_type_1(self, channel): - return self.channels[channel].type_1 + def get_chan_transducer_type(self, channel): + return self.channels[channel].transducer_type - def get_chan_type_2(self, channel): - return self.channels[channel].type_2 + def get_chan_type(self, channel): + return self.channels[channel].channel_type class Channel(object): @@ -166,22 +166,22 @@ def __init__(self): self.ramp_amplitude_setpoint = 0 self.scale = 10 self.value = 0 - self.type_1 = 0 - self.type_2 = 0 - + self.transducer_type = 0 class PositionChannel(Channel): def __init__(self): super(PositionChannel, self).__init__() - + self.channel_type = 3 class StressChannel(Channel): def __init__(self): super(StressChannel, self).__init__() self.area = 1 + self.channel_type = 2 class StrainChannel(Channel): def __init__(self): super(StrainChannel, self).__init__() self.length = 1 + self.channel_type = 4 diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 0e5c59c0..5f7f8b0f 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -109,6 +109,6 @@ def set_chan_area(self, channel, value): self._device.set_chan_area(int(channel), float(value)) def get_chan_type(self, channel): - type_1 = self._device.get_chan_type_1(int(channel)) - type_2 = self._device.get_chan_type_2(int(channel)) - return "{a},{b}".format(a=type_1, b=type_2) + transducer_type = self._device.get_chan_transducer_type(int(channel)) + chan_type = self._device.get_chan_type(int(channel)) + return "{a},{b}".format(a=transducer_type, b=chan_type) From 3eb336d8bcc1deccd26cc3b003f5f0972ce201f7 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 18 Jul 2017 12:04:37 +0100 Subject: [PATCH 0225/1466] Add skeletal emulator --- lewis_emulators/fermichopper/__init__.py | 3 + lewis_emulators/fermichopper/device.py | 23 +++ .../fermichopper/interfaces/__init__.py | 3 + .../interfaces/stream_interface.py | 139 ++++++++++++++++++ lewis_emulators/fermichopper/states.py | 4 + 5 files changed, 172 insertions(+) create mode 100644 lewis_emulators/fermichopper/__init__.py create mode 100644 lewis_emulators/fermichopper/device.py create mode 100644 lewis_emulators/fermichopper/interfaces/__init__.py create mode 100644 lewis_emulators/fermichopper/interfaces/stream_interface.py create mode 100644 lewis_emulators/fermichopper/states.py diff --git a/lewis_emulators/fermichopper/__init__.py b/lewis_emulators/fermichopper/__init__.py new file mode 100644 index 00000000..37fe266c --- /dev/null +++ b/lewis_emulators/fermichopper/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedMk2Chopper + +__all__ = ['SimulatedMk2Chopper2400'] diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py new file mode 100644 index 00000000..d92b99c8 --- /dev/null +++ b/lewis_emulators/fermichopper/device.py @@ -0,0 +1,23 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedFermichopper(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([]) + diff --git a/lewis_emulators/fermichopper/interfaces/__init__.py b/lewis_emulators/fermichopper/interfaces/__init__.py new file mode 100644 index 00000000..a4e1a1c3 --- /dev/null +++ b/lewis_emulators/fermichopper/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Mk2ChopperStreamInterface + +__all__ = ['Mk2ChopperStreamInterface'] diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py new file mode 100644 index 00000000..060690ef --- /dev/null +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -0,0 +1,139 @@ +from lewis.adapters.stream import StreamAdapter, Cmd +from ..chopper_type import ChopperType + + +def filled_int(val, length): + """ + Takes a value and returns a zero padded representation of the integer component. + + :param val: The original value. + :param length: Minimum length of the returned string + :return: Zero-padded integer representation (if possible) of string. Original string used if integer conversion + fails + """ + try: + converted_val = int(val) + except ValueError: + converted_val = val + return str(converted_val).zfill(length) + + +class Mk2ChopperStreamInterface(StreamAdapter): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("get_true_frequency", "^RF$"), + Cmd("get_demanded_frequency", "^RG$"), + Cmd("get_true_phase_delay", "^RP$"), + Cmd("get_demanded_phase_delay", "^RQ$"), + Cmd("get_true_phase_error", "^RE$"), + Cmd("get_demanded_phase_error_window", "^RW$"), + Cmd("get_chopper_interlocks", "^RC$"), + Cmd("get_spectral_interlocks", "^RS$"), + Cmd("get_error_flags", "^RX$"), + Cmd("read_all", "^RA$"), + Cmd("set_chopper_started", "^WS([0-9]+)$"), + Cmd("set_demanded_frequency", "^WM([0-9]+)$"), + Cmd("set_demanded_phase_delay", "^WP([0-9]+)$"), + Cmd("set_demanded_phase_error_window", "^WR([0-9]+)$") + } + + in_terminator = "\r" + out_terminator = "\r" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + return str(error) + + def get_demanded_frequency(self): + return "RG{0}".format(filled_int(self._device.get_demanded_frequency(), 3)) + + def get_true_frequency(self): + return "RF{0}".format(filled_int(self._device.get_true_frequency(), 3)) + + def get_demanded_phase_delay(self): + return "RQ{0}".format(filled_int(self._device.get_demanded_phase_delay(), 5)) + + def get_true_phase_delay(self): + return "RP{0}".format(filled_int(self._device.get_true_phase_delay(), 5)) + + def get_demanded_phase_error_window(self): + return "RW{0}".format(filled_int(self._device.get_demanded_phase_error_window(), 3)) + + def get_true_phase_error(self): + return "RE{0}".format(filled_int(self._device.get_true_phase_error(), 3)) + + def get_spectral_interlocks(self): + bits = [0]*8 + if self._device.get_manufacturer() == ChopperType.CORTINA: + bits[0] = 1 if self._device.inverter_ready() else 0 + bits[1] = 1 if self._device.motor_running() else 0 + bits[2] = 1 if self._device.in_sync() else 0 + elif self._device.get_manufacturer() == ChopperType.INDRAMAT: + bits[0] = 1 if self._device.motor_running() else 0 + bits[1] = 1 if self._device.reg_mode() else 0 + bits[2] = 1 if self._device.in_sync() else 0 + elif self._device.get_manufacturer() == ChopperType.SPECTRAL: + bits[2] = 1 if self._device.external_fault() else 0 + return "RS{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) + + def get_chopper_interlocks(self): + bits = [0]*8 + bits[0] = 1 if self._device.get_system_frequency() is 50 else 0 + bits[1] = 1 if self._device.clock_loss() else 0 + bits[2] = 1 if self._device.bearing_1_overheat() else 0 + bits[3] = 1 if self._device.bearing_2_overheat() else 0 + bits[4] = 1 if self._device.motor_overheat() else 0 + bits[5] = 1 if self._device.chopper_overspeed() else 0 + return "RC{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) + + def get_error_flags(self): + bits = [0]*8 + bits[0] = 1 if self._device.phase_delay_error() else 0 + bits[1] = 1 if self._device.phase_delay_correction_error() else 0 + bits[2] = 1 if self._device.phase_accuracy_window_error() else 0 + return "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) + + def get_manufacturer(self): + return self._type.get_manufacturer() + + def set_chopper_started(self, start_flag_raw): + try: + start_flag = int(start_flag_raw) + except ValueError: + pass + else: + if start_flag == 1: + self._device.start() + elif start_flag == 2: + self._device.stop() + return + + def set_demanded_frequency(self, new_frequency_raw): + return Mk2ChopperStreamInterface._set(new_frequency_raw, self.get_demanded_frequency, + self._device.set_demanded_frequency) + + def set_demanded_phase_delay(self, new_phase_delay_raw): + return Mk2ChopperStreamInterface._set(new_phase_delay_raw, self.get_demanded_phase_delay, + self._device.set_demanded_phase_delay) + + def set_demanded_phase_error_window(self, new_phase_error_window_raw): + return Mk2ChopperStreamInterface._set(new_phase_error_window_raw, self.get_demanded_phase_error_window, + self._device.set_demanded_phase_error_window) + + def read_all(self): + return "RA:Don't use, it causes the driver to lock up" + + @staticmethod + def _set(raw, device_get, device_set): + try: + int_value = int(raw) + except ValueError: + pass + else: + device_set(int_value) + return device_get() + + @staticmethod + def _string_from_bits(bits): + return "".join(str(n) for n in reversed(bits)) diff --git a/lewis_emulators/fermichopper/states.py b/lewis_emulators/fermichopper/states.py new file mode 100644 index 00000000..e9b6b3ee --- /dev/null +++ b/lewis_emulators/fermichopper/states.py @@ -0,0 +1,4 @@ +from lewis.core.statemachine import State + +class DefaultState(State): + pass From 395a2d88f0da5c7f54f168615c52c89ed3ca3821 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 18 Jul 2017 12:23:20 +0100 Subject: [PATCH 0226/1466] Fix a couple of errors in emulator --- lewis_emulators/fermichopper/__init__.py | 4 +- .../fermichopper/interfaces/__init__.py | 4 +- .../interfaces/stream_interface.py | 132 +----------------- 3 files changed, 10 insertions(+), 130 deletions(-) diff --git a/lewis_emulators/fermichopper/__init__.py b/lewis_emulators/fermichopper/__init__.py index 37fe266c..5a8a6feb 100644 --- a/lewis_emulators/fermichopper/__init__.py +++ b/lewis_emulators/fermichopper/__init__.py @@ -1,3 +1,3 @@ -from .device import SimulatedMk2Chopper +from .device import SimulatedFermichopper -__all__ = ['SimulatedMk2Chopper2400'] +__all__ = ['SimulatedFermichopper'] diff --git a/lewis_emulators/fermichopper/interfaces/__init__.py b/lewis_emulators/fermichopper/interfaces/__init__.py index a4e1a1c3..47446d1c 100644 --- a/lewis_emulators/fermichopper/interfaces/__init__.py +++ b/lewis_emulators/fermichopper/interfaces/__init__.py @@ -1,3 +1,3 @@ -from .stream_interface import Mk2ChopperStreamInterface +from .stream_interface import FermichopperStreamInterface -__all__ = ['Mk2ChopperStreamInterface'] +__all__ = ['FermichopperStreamInterface'] diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 060690ef..64f3d8fc 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -1,139 +1,19 @@ from lewis.adapters.stream import StreamAdapter, Cmd -from ..chopper_type import ChopperType -def filled_int(val, length): - """ - Takes a value and returns a zero padded representation of the integer component. - - :param val: The original value. - :param length: Minimum length of the returned string - :return: Zero-padded integer representation (if possible) of string. Original string used if integer conversion - fails - """ - try: - converted_val = int(val) - except ValueError: - converted_val = val - return str(converted_val).zfill(length) - - -class Mk2ChopperStreamInterface(StreamAdapter): +class FermichopperStreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation commands = { - Cmd("get_true_frequency", "^RF$"), - Cmd("get_demanded_frequency", "^RG$"), - Cmd("get_true_phase_delay", "^RP$"), - Cmd("get_demanded_phase_delay", "^RQ$"), - Cmd("get_true_phase_error", "^RE$"), - Cmd("get_demanded_phase_error_window", "^RW$"), - Cmd("get_chopper_interlocks", "^RC$"), - Cmd("get_spectral_interlocks", "^RS$"), - Cmd("get_error_flags", "^RX$"), - Cmd("read_all", "^RA$"), - Cmd("set_chopper_started", "^WS([0-9]+)$"), - Cmd("set_demanded_frequency", "^WM([0-9]+)$"), - Cmd("set_demanded_phase_delay", "^WP([0-9]+)$"), - Cmd("set_demanded_phase_error_window", "^WR([0-9]+)$") + Cmd("get_zero", "^i_want_zero$"), } - in_terminator = "\r" - out_terminator = "\r" + in_terminator = "" + out_terminator = "" def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) - def get_demanded_frequency(self): - return "RG{0}".format(filled_int(self._device.get_demanded_frequency(), 3)) - - def get_true_frequency(self): - return "RF{0}".format(filled_int(self._device.get_true_frequency(), 3)) - - def get_demanded_phase_delay(self): - return "RQ{0}".format(filled_int(self._device.get_demanded_phase_delay(), 5)) - - def get_true_phase_delay(self): - return "RP{0}".format(filled_int(self._device.get_true_phase_delay(), 5)) - - def get_demanded_phase_error_window(self): - return "RW{0}".format(filled_int(self._device.get_demanded_phase_error_window(), 3)) - - def get_true_phase_error(self): - return "RE{0}".format(filled_int(self._device.get_true_phase_error(), 3)) - - def get_spectral_interlocks(self): - bits = [0]*8 - if self._device.get_manufacturer() == ChopperType.CORTINA: - bits[0] = 1 if self._device.inverter_ready() else 0 - bits[1] = 1 if self._device.motor_running() else 0 - bits[2] = 1 if self._device.in_sync() else 0 - elif self._device.get_manufacturer() == ChopperType.INDRAMAT: - bits[0] = 1 if self._device.motor_running() else 0 - bits[1] = 1 if self._device.reg_mode() else 0 - bits[2] = 1 if self._device.in_sync() else 0 - elif self._device.get_manufacturer() == ChopperType.SPECTRAL: - bits[2] = 1 if self._device.external_fault() else 0 - return "RS{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) - - def get_chopper_interlocks(self): - bits = [0]*8 - bits[0] = 1 if self._device.get_system_frequency() is 50 else 0 - bits[1] = 1 if self._device.clock_loss() else 0 - bits[2] = 1 if self._device.bearing_1_overheat() else 0 - bits[3] = 1 if self._device.bearing_2_overheat() else 0 - bits[4] = 1 if self._device.motor_overheat() else 0 - bits[5] = 1 if self._device.chopper_overspeed() else 0 - return "RC{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) - - def get_error_flags(self): - bits = [0]*8 - bits[0] = 1 if self._device.phase_delay_error() else 0 - bits[1] = 1 if self._device.phase_delay_correction_error() else 0 - bits[2] = 1 if self._device.phase_accuracy_window_error() else 0 - return "RX{0:8s}".format(Mk2ChopperStreamInterface._string_from_bits(bits)) - - def get_manufacturer(self): - return self._type.get_manufacturer() - - def set_chopper_started(self, start_flag_raw): - try: - start_flag = int(start_flag_raw) - except ValueError: - pass - else: - if start_flag == 1: - self._device.start() - elif start_flag == 2: - self._device.stop() - return - - def set_demanded_frequency(self, new_frequency_raw): - return Mk2ChopperStreamInterface._set(new_frequency_raw, self.get_demanded_frequency, - self._device.set_demanded_frequency) - - def set_demanded_phase_delay(self, new_phase_delay_raw): - return Mk2ChopperStreamInterface._set(new_phase_delay_raw, self.get_demanded_phase_delay, - self._device.set_demanded_phase_delay) - - def set_demanded_phase_error_window(self, new_phase_error_window_raw): - return Mk2ChopperStreamInterface._set(new_phase_error_window_raw, self.get_demanded_phase_error_window, - self._device.set_demanded_phase_error_window) - - def read_all(self): - return "RA:Don't use, it causes the driver to lock up" - - @staticmethod - def _set(raw, device_get, device_set): - try: - int_value = int(raw) - except ValueError: - pass - else: - device_set(int_value) - return device_get() - - @staticmethod - def _string_from_bits(bits): - return "".join(str(n) for n in reversed(bits)) + def get_zero(self): + return 0 From 165ed4e7410620912af8b3ecbb85c1f3c183f53d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 18 Jul 2017 14:01:26 +0100 Subject: [PATCH 0227/1466] Lewis can't handle not having a termination character --- .../fermichopper/interfaces/stream_interface.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 64f3d8fc..d92d3f69 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -5,15 +5,16 @@ class FermichopperStreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation commands = { - Cmd("get_zero", "^i_want_zero$"), + Cmd("get_data", "^#0000000\$$"), } - in_terminator = "" - out_terminator = "" + in_terminator = "\n" + out_terminator = "\n" def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) - def get_zero(self): - return 0 + def get_data(self): + # This is "known good" example data from the documentation. + return "#10003F4#2003F0B#3177002#4177003#55F9019#60001F7#75F8B2C#80001F9#90012FC#A00C81C#B020004#C012C19#D012C1A#E012C1B$" From d817a35655b7d02affd204e4fbf2abe92c5a6799 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 18 Jul 2017 17:31:52 +0100 Subject: [PATCH 0228/1466] Emulator --- lewis_emulators/fermichopper/interfaces/stream_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index d92d3f69..dbe8cc59 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -5,7 +5,7 @@ class FermichopperStreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation commands = { - Cmd("get_data", "^#0000000\$$"), + Cmd("get_data", "^.*$"), } in_terminator = "\n" @@ -17,4 +17,5 @@ def handle_error(self, request, error): def get_data(self): # This is "known good" example data from the documentation. - return "#10003F4#2003F0B#3177002#4177003#55F9019#60001F7#75F8B2C#80001F9#90012FC#A00C81C#B020004#C012C19#D012C1A#E012C1B$" + return "#10003F4" + From 83124238b2e77cddf5d1468cd16ce4d6cc9e449c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 19 Jul 2017 09:36:24 +0100 Subject: [PATCH 0229/1466] Implement checksum function in emulator --- .../interfaces/stream_interface.py | 70 ++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index dbe8cc59..b2090a67 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -1,11 +1,69 @@ from lewis.adapters.stream import StreamAdapter, Cmd +class JulichChecksum(object): + @staticmethod + def hex_value(char): + if char == '0': + return 0x30 + elif char == '1': + return 0x31 + elif char == '2': + return 0x32 + elif char == '3': + return 0x33 + elif char == '4': + return 0x34 + elif char == '5': + return 0x35 + elif char == '6': + return 0x36 + elif char == '7': + return 0x37 + elif char == '8': + return 0x38 + elif char == '9': + return 0x39 + elif char == 'A': + return 0x41 + elif char == 'B': + return 0x42 + elif char == 'C': + return 0x43 + elif char == 'D': + return 0x44 + elif char == 'E': + return 0x45 + elif char == 'F': + return 0x46 + else: + assert False, "Invalid character - can't calculate hex value!" + + @staticmethod + def julich_checksum(initialbyte, data): + assert len(initialbyte) == 1, "Checksum was called with more than one initial byte." + assert len(data) == 4, "Checksum was called with more than four data bytes" + + alldata = list(data) + [initialbyte] + + if all(x == '0' for x in alldata): + total = 0 + else: + total = sum(JulichChecksum.hex_value(i) for i in alldata) + + return hex(total).upper()[2:] + + @staticmethod + def verify_checksum(initialbyte, data, actual_checksum): + assert JulichChecksum.julich_checksum(initialbyte, data) == actual_checksum, "Checksum did not match" + + class FermichopperStreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation commands = { - Cmd("get_data", "^.*$"), + Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), + Cmd("execute_command", "^#1([0-9A-F]{4})([0-9A-F]{2})\$$"), } in_terminator = "\n" @@ -15,7 +73,15 @@ def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) - def get_data(self): + def get_all_data(self, checksum): + # This is "known good" example data from the documentation. + return "#10003F4" + + def execute_command(self, command, checksum): # This is "known good" example data from the documentation. + print "Command was {command} and checksum was {checksum}".format(command=command, checksum=checksum) + + JulichChecksum.verify_checksum('1', command, checksum) + return "#10003F4" From 1937c5e3f769bf24e465253c2a3aeeb10dd2a502 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 19 Jul 2017 10:48:03 +0100 Subject: [PATCH 0230/1466] Simplify and document checksum implementation --- lewis_emulators/fermichopper/device.py | 7 ++ .../interfaces/stream_interface.py | 99 +++++++++---------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index d92b99c8..24cdc859 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -9,6 +9,7 @@ def _initialize_data(self): """ Initialize all of the device's attributes. """ + self.property = 5 def _get_state_handlers(self): return { @@ -21,3 +22,9 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict([]) + def set_property(self, value): + self.property = value + + def get_property(self, value): + return self.property + diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index b2090a67..283fa4f0 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -3,59 +3,48 @@ class JulichChecksum(object): @staticmethod - def hex_value(char): - if char == '0': - return 0x30 - elif char == '1': - return 0x31 - elif char == '2': - return 0x32 - elif char == '3': - return 0x33 - elif char == '4': - return 0x34 - elif char == '5': - return 0x35 - elif char == '6': - return 0x36 - elif char == '7': - return 0x37 - elif char == '8': - return 0x38 - elif char == '9': - return 0x39 - elif char == 'A': - return 0x41 - elif char == 'B': - return 0x42 - elif char == 'C': - return 0x43 - elif char == 'D': - return 0x44 - elif char == 'E': - return 0x45 - elif char == 'F': - return 0x46 - else: - assert False, "Invalid character - can't calculate hex value!" + def _hex_value(char): + """ + Converts an uppercase hexed character to it's ASCII identifier + :param char: The character to convert + :return: the ascii code of the given character + """ + assert char in list("0123456789ABCDEF"), "Invalid character - can't calculate hex value!" + return ord(char) @staticmethod - def julich_checksum(initialbyte, data): - assert len(initialbyte) == 1, "Checksum was called with more than one initial byte." - assert len(data) == 4, "Checksum was called with more than four data bytes" + def calculate(data): + """ + Calculates the Julich checksum of the given data + :param data: the input data (list of chars, length 5) + :return: the Julich checksum of the given input data + """ + assert len(data) == 5, "Unexpected data length." + return "00" if all(x == '0' for x in data) else hex(sum(JulichChecksum._hex_value(i) for i in data)).upper()[-2:] - alldata = list(data) + [initialbyte] - - if all(x == '0' for x in alldata): - total = 0 - else: - total = sum(JulichChecksum.hex_value(i) for i in alldata) - - return hex(total).upper()[2:] + @staticmethod + def verify(initialbyte, data, actual_checksum): + """ + Verifies that the checksum of received data is correct. + :param initialbyte: The first byte (str, length 1) + :param data: The data bytes (str, length 4) + :param actual_checksum: The transmitted checksum (str, length 2) + :return: Nothing + :raises: AssertionError: If the checksum didn't match or the inputs were invalid + """ + assert len(initialbyte) == 1, "Initial byte should have length 1" + assert len(data) == 4, "Data should have length 4" + assert len(actual_checksum) == 2, "Actual checksum should have length 2" + assert JulichChecksum.calculate([initialbyte] + list(data)) == actual_checksum, "Checksum did not match" @staticmethod - def verify_checksum(initialbyte, data, actual_checksum): - assert JulichChecksum.julich_checksum(initialbyte, data) == actual_checksum, "Checksum did not match" + def append_checksum(data): + """ + Utility method for appending the Julich checksum to the input data + :param data: the input data + :return: the input data with it's checksum appended + """ + return data + JulichChecksum.calculate(data) class FermichopperStreamInterface(StreamAdapter): @@ -74,14 +63,18 @@ def handle_error(self, request, error): return str(error) def get_all_data(self, checksum): - # This is "known good" example data from the documentation. + JulichChecksum.verify('0', '0000', checksum) return "#10003F4" def execute_command(self, command, checksum): - # This is "known good" example data from the documentation. - print "Command was {command} and checksum was {checksum}".format(command=command, checksum=checksum) + JulichChecksum.verify('1', command, checksum) - JulichChecksum.verify_checksum('1', command, checksum) + valid_commands = ["0001", "0002", "0003", "0006", "0007"] - return "#10003F4" + assert command in valid_commands, "Invalid command." + + if command == "0001": + self._device.set_property(4) + + return "#" + JulichChecksum.append_checksum('2003F') From 7d0d5158e8ae76ae88781ca52324d12f2f990278 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 19 Jul 2017 11:26:21 +0100 Subject: [PATCH 0231/1466] Add some very basic behaviour to emulator --- lewis_emulators/fermichopper/device.py | 10 +++++----- .../fermichopper/interfaces/stream_interface.py | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index 24cdc859..706d05bf 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -9,7 +9,7 @@ def _initialize_data(self): """ Initialize all of the device's attributes. """ - self.property = 5 + self.last_command = "0000" def _get_state_handlers(self): return { @@ -22,9 +22,9 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict([]) - def set_property(self, value): - self.property = value + def set_last_command(self, value): + self.last_command = value - def get_property(self, value): - return self.property + def get_last_command(self): + return self.last_command diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 283fa4f0..ab1de56b 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -64,7 +64,7 @@ def handle_error(self, request, error): def get_all_data(self, checksum): JulichChecksum.verify('0', '0000', checksum) - return "#10003F4" + return "#" + JulichChecksum.append_checksum('1' + self._device.get_last_command()) def execute_command(self, command, checksum): JulichChecksum.verify('1', command, checksum) @@ -73,8 +73,5 @@ def execute_command(self, command, checksum): assert command in valid_commands, "Invalid command." - if command == "0001": - self._device.set_property(4) - - return "#" + JulichChecksum.append_checksum('2003F') + self._device.set_last_command(command) From 6a5a3bfc584a9899f4640fb556bbd40e772b5064 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 21 Jul 2017 09:45:34 +0100 Subject: [PATCH 0232/1466] Update checksum algorithm to MERLIN version --- .../interfaces/stream_interface.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index ab1de56b..bc592361 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -9,33 +9,32 @@ def _hex_value(char): :param char: The character to convert :return: the ascii code of the given character """ - assert char in list("0123456789ABCDEF"), "Invalid character - can't calculate hex value!" + assert char in list("#0123456789ABCDEF"), "Invalid character - can't calculate hex value!" return ord(char) @staticmethod - def calculate(data): + def _calculate(alldata): """ Calculates the Julich checksum of the given data - :param data: the input data (list of chars, length 5) + :param alldata: the input data (list of chars, length 5) :return: the Julich checksum of the given input data """ - assert len(data) == 5, "Unexpected data length." - return "00" if all(x == '0' for x in data) else hex(sum(JulichChecksum._hex_value(i) for i in data)).upper()[-2:] + return "00" if all(x in ['0', '#'] for x in alldata) else hex(sum(JulichChecksum._hex_value(i) for i in alldata)).upper()[-2:] @staticmethod - def verify(initialbyte, data, actual_checksum): + def verify(header, data, actual_checksum): """ Verifies that the checksum of received data is correct. - :param initialbyte: The first byte (str, length 1) + :param header: The leading # and the first byte (str, length 2) :param data: The data bytes (str, length 4) :param actual_checksum: The transmitted checksum (str, length 2) :return: Nothing :raises: AssertionError: If the checksum didn't match or the inputs were invalid """ - assert len(initialbyte) == 1, "Initial byte should have length 1" + assert len(header) == 2, "Header should have length 2" assert len(data) == 4, "Data should have length 4" assert len(actual_checksum) == 2, "Actual checksum should have length 2" - assert JulichChecksum.calculate([initialbyte] + list(data)) == actual_checksum, "Checksum did not match" + assert JulichChecksum._calculate(list(header) + list(data)) == actual_checksum, "Checksum did not match" @staticmethod def append_checksum(data): @@ -44,7 +43,8 @@ def append_checksum(data): :param data: the input data :return: the input data with it's checksum appended """ - return data + JulichChecksum.calculate(data) + assert len(data) == 6, "Unexpected data length." + return data + JulichChecksum._calculate(data) class FermichopperStreamInterface(StreamAdapter): @@ -53,21 +53,25 @@ class FermichopperStreamInterface(StreamAdapter): commands = { Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), Cmd("execute_command", "^#1([0-9A-F]{4})([0-9A-F]{2})\$$"), + # Cmd("bla", "^.*$"), # Catch-all command for debugging } in_terminator = "\n" out_terminator = "\n" + # def bla(self): + # pass + def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) def get_all_data(self, checksum): - JulichChecksum.verify('0', '0000', checksum) - return "#" + JulichChecksum.append_checksum('1' + self._device.get_last_command()) + JulichChecksum.verify('#0', '0000', checksum) + return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) + "#2003F0B" def execute_command(self, command, checksum): - JulichChecksum.verify('1', command, checksum) + JulichChecksum.verify('#1', command, checksum) valid_commands = ["0001", "0002", "0003", "0006", "0007"] From e23bdf0d1fb3ac66ff885c35b6509c574d1d75b8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 21 Jul 2017 10:48:31 +0100 Subject: [PATCH 0233/1466] Provide dummy readback for chopper speed --- lewis_emulators/fermichopper/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index bc592361..ef8931bc 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -68,7 +68,7 @@ def handle_error(self, request, error): def get_all_data(self, checksum): JulichChecksum.verify('#0', '0000', checksum) - return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) + "#2003F0B" + return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) + "#2003F0B#300061C" def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From d28523179ac23a729c326a7cd451bc268ba7def1 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 21 Jul 2017 15:26:55 +0100 Subject: [PATCH 0234/1466] Return mock data packets up to 9 --- .../fermichopper/interfaces/stream_interface.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index ef8931bc..e1f9ee23 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -53,13 +53,14 @@ class FermichopperStreamInterface(StreamAdapter): commands = { Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), Cmd("execute_command", "^#1([0-9A-F]{4})([0-9A-F]{2})\$$"), - # Cmd("bla", "^.*$"), # Catch-all command for debugging + # Cmd("catch_all", "^.*$"), # Catch-all command for debugging } in_terminator = "\n" out_terminator = "\n" - # def bla(self): + # Catch all command for debugging if the IOC sends strange characters in the checksum. + # def catch_all(self): # pass def handle_error(self, request, error): @@ -68,7 +69,15 @@ def handle_error(self, request, error): def get_all_data(self, checksum): JulichChecksum.verify('#0', '0000', checksum) - return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) + "#2003F0B#300061C" + return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) \ + + JulichChecksum.append_checksum("#2006F") \ + + JulichChecksum.append_checksum("#30006") \ + + JulichChecksum.append_checksum("#4464F") \ + + JulichChecksum.append_checksum("#55208") \ + + JulichChecksum.append_checksum("#60000") \ + + JulichChecksum.append_checksum("#75209") \ + + JulichChecksum.append_checksum("#80000") \ + + JulichChecksum.append_checksum("#9002A") def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From 2de043a58a0073c05d12cc610c09bcb96fa58253 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 21 Jul 2017 16:53:32 +0100 Subject: [PATCH 0235/1466] Add code --- .../fermichopper/interfaces/stream_interface.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index e1f9ee23..d907b785 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -77,7 +77,12 @@ def get_all_data(self, checksum): + JulichChecksum.append_checksum("#60000") \ + JulichChecksum.append_checksum("#75209") \ + JulichChecksum.append_checksum("#80000") \ - + JulichChecksum.append_checksum("#9002A") + + JulichChecksum.append_checksum("#9002A") \ + + JulichChecksum.append_checksum("#A01EB") \ + + JulichChecksum.append_checksum("#B01F0") \ + + JulichChecksum.append_checksum("#C01F9") \ + + JulichChecksum.append_checksum("#D01FB") \ + + JulichChecksum.append_checksum("#E020C") def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From 1f197f07dcc0db8d8b20c8d515f34826fee790f8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 24 Jul 2017 09:27:31 +0100 Subject: [PATCH 0236/1466] Provide dummy data to be read from IOC --- .../fermichopper/interfaces/stream_interface.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index d907b785..a2b808bd 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -5,11 +5,12 @@ class JulichChecksum(object): @staticmethod def _hex_value(char): """ - Converts an uppercase hexed character to it's ASCII identifier + Converts an uppercase octadecimal character or a hash to it's ASCII identifier. + :param char: The character to convert :return: the ascii code of the given character """ - assert char in list("#0123456789ABCDEF"), "Invalid character - can't calculate hex value!" + assert char in list("#0123456789ABCDEFGH"), "Invalid character - can't calculate hex value!" return ord(char) @staticmethod @@ -82,7 +83,10 @@ def get_all_data(self, checksum): + JulichChecksum.append_checksum("#B01F0") \ + JulichChecksum.append_checksum("#C01F9") \ + JulichChecksum.append_checksum("#D01FB") \ - + JulichChecksum.append_checksum("#E020C") + + JulichChecksum.append_checksum("#E020C") \ + + JulichChecksum.append_checksum("#F0296") \ + + JulichChecksum.append_checksum("#G0198") \ + + JulichChecksum.append_checksum("#H0176") def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From 3a45ac3a19a77d58444994313ceb4b1537169ce3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 24 Jul 2017 10:23:04 +0100 Subject: [PATCH 0237/1466] Return a sensible status code --- lewis_emulators/fermichopper/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index a2b808bd..51963c22 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -71,7 +71,7 @@ def handle_error(self, request, error): def get_all_data(self, checksum): JulichChecksum.verify('#0', '0000', checksum) return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) \ - + JulichChecksum.append_checksum("#2006F") \ + + JulichChecksum.append_checksum("#2003F") \ + JulichChecksum.append_checksum("#30006") \ + JulichChecksum.append_checksum("#4464F") \ + JulichChecksum.append_checksum("#55208") \ From 5c0f368304f93430642651974426889daceb8b70 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 24 Jul 2017 10:52:39 +0100 Subject: [PATCH 0238/1466] Correct accepted commands --- lewis_emulators/fermichopper/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 51963c22..cc05cbd2 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -91,7 +91,7 @@ def get_all_data(self, checksum): def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) - valid_commands = ["0001", "0002", "0003", "0006", "0007"] + valid_commands = ["0001", "0002", "0003", "0004", "0005"] assert command in valid_commands, "Invalid command." From b3faad77ec49fef72437360a24e29d972851dc19 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 24 Jul 2017 16:06:04 +0100 Subject: [PATCH 0239/1466] Add functionality for setting device speed --- lewis_emulators/fermichopper/device.py | 10 ++++++++++ .../fermichopper/interfaces/stream_interface.py | 9 +++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index 706d05bf..452cee10 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -10,6 +10,7 @@ def _initialize_data(self): Initialize all of the device's attributes. """ self.last_command = "0000" + self.speed_setpoint = 0 def _get_state_handlers(self): return { @@ -28,3 +29,12 @@ def set_last_command(self, value): def get_last_command(self): return self.last_command + def set_speed(self, value): + self.speed_setpoint = value + + def get_speed_setpoint(self): + return self.speed_setpoint + + def get_speed(self): + # TODO + return self.speed_setpoint diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index cc05cbd2..daafc3b2 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -54,6 +54,7 @@ class FermichopperStreamInterface(StreamAdapter): commands = { Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), Cmd("execute_command", "^#1([0-9A-F]{4})([0-9A-F]{2})\$$"), + Cmd("set_speed", "^#3([0-9A-F]{4})([0-9A-F]{2})\$$"), # Cmd("catch_all", "^.*$"), # Catch-all command for debugging } @@ -72,8 +73,8 @@ def get_all_data(self, checksum): JulichChecksum.verify('#0', '0000', checksum) return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) \ + JulichChecksum.append_checksum("#2003F") \ - + JulichChecksum.append_checksum("#30006") \ - + JulichChecksum.append_checksum("#4464F") \ + + JulichChecksum.append_checksum("#3000{:01X}".format(12 - (self._device.get_speed_setpoint()/50))) \ + + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_speed())) \ + JulichChecksum.append_checksum("#55208") \ + JulichChecksum.append_checksum("#60000") \ + JulichChecksum.append_checksum("#75209") \ @@ -97,3 +98,7 @@ def execute_command(self, command, checksum): self._device.set_last_command(command) + def set_speed(self, command, checksum): + JulichChecksum.verify("#3", command, checksum) + assert int(command, 16) in range(0,12), "Chopper speed unexpected" + self._device.set_speed((12-int(command, 16))*50) From 6008f546c0447d9b47b2fc88aba4173bcbd6cc46 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 25 Jul 2017 10:26:53 +0100 Subject: [PATCH 0240/1466] Chopper delay settable --- lewis_emulators/fermichopper/device.py | 17 +++++++++++++++++ .../interfaces/stream_interface.py | 19 +++++++++++++++---- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index 452cee10..d1eb2b3f 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -11,6 +11,11 @@ def _initialize_data(self): """ self.last_command = "0000" self.speed_setpoint = 0 + self._allowed_speed_setpoints = (50*i for i in range(1, 13)) + + self.delay_highword = 0 + self.delay_lowword = 0 + self.delay = 0 def _get_state_handlers(self): return { @@ -30,6 +35,7 @@ def get_last_command(self): return self.last_command def set_speed(self, value): + assert value in self._allowed_speed_setpoints self.speed_setpoint = value def get_speed_setpoint(self): @@ -38,3 +44,14 @@ def get_speed_setpoint(self): def get_speed(self): # TODO return self.speed_setpoint + + def set_delay_highword(self, value): + self.delay_highword = value + self.update_delay() + + def set_delay_lowword(self, value): + self.delay_lowword = value + self.update_delay() + + def update_delay(self): + self.delay = (self.delay_highword * 65536 + self.delay_lowword)/50400 diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index daafc3b2..ccc7c3d6 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -1,5 +1,7 @@ from lewis.adapters.stream import StreamAdapter, Cmd +import math + class JulichChecksum(object): @staticmethod @@ -55,7 +57,9 @@ class FermichopperStreamInterface(StreamAdapter): Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), Cmd("execute_command", "^#1([0-9A-F]{4})([0-9A-F]{2})\$$"), Cmd("set_speed", "^#3([0-9A-F]{4})([0-9A-F]{2})\$$"), - # Cmd("catch_all", "^.*$"), # Catch-all command for debugging + Cmd("set_delay_highword", "^#6([0-9A-F]{4})([0-9A-F]{2})\$$"), + Cmd("set_delay_lowword", "^#5([0-9A-F]{4})([0-9A-F]{2})\$$"), + # Cmd("catch_all", "^#6.*$"), # Catch-all command for debugging } in_terminator = "\n" @@ -75,8 +79,8 @@ def get_all_data(self, checksum): + JulichChecksum.append_checksum("#2003F") \ + JulichChecksum.append_checksum("#3000{:01X}".format(12 - (self._device.get_speed_setpoint()/50))) \ + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_speed())) \ - + JulichChecksum.append_checksum("#55208") \ - + JulichChecksum.append_checksum("#60000") \ + + JulichChecksum.append_checksum("#5{:04X}".format(int(math.floor((self._device.delay * 50.4) % 65536)))) \ + + JulichChecksum.append_checksum("#6{:04X}".format(int(math.floor((self._device.delay * 50.4) / 65536)))) \ + JulichChecksum.append_checksum("#75209") \ + JulichChecksum.append_checksum("#80000") \ + JulichChecksum.append_checksum("#9002A") \ @@ -100,5 +104,12 @@ def execute_command(self, command, checksum): def set_speed(self, command, checksum): JulichChecksum.verify("#3", command, checksum) - assert int(command, 16) in range(0,12), "Chopper speed unexpected" self._device.set_speed((12-int(command, 16))*50) + + def set_delay_highword(self, command, checksum): + JulichChecksum.verify('#6', command, checksum) + self._device.set_delay_highword(int(command, 16)) + + def set_delay_lowword(self, command, checksum): + JulichChecksum.verify('#5', command, checksum) + self._device.set_delay_lowword(int(command, 16)) From 35867bb6d4a491aa461c1781155020b87b8d6467 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 25 Jul 2017 11:33:11 +0100 Subject: [PATCH 0241/1466] Add ability to change gate width --- lewis_emulators/fermichopper/device.py | 8 ++++++++ .../fermichopper/interfaces/stream_interface.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index d1eb2b3f..09833f85 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -17,6 +17,8 @@ def _initialize_data(self): self.delay_lowword = 0 self.delay = 0 + self.gatewidth = 0 + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -55,3 +57,9 @@ def set_delay_lowword(self, value): def update_delay(self): self.delay = (self.delay_highword * 65536 + self.delay_lowword)/50400 + + def set_gate_width(self, value): + self.gatewidth = value + + def get_gate_width(self): + return self.gatewidth diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index ccc7c3d6..1f5fdccf 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -59,15 +59,16 @@ class FermichopperStreamInterface(StreamAdapter): Cmd("set_speed", "^#3([0-9A-F]{4})([0-9A-F]{2})\$$"), Cmd("set_delay_highword", "^#6([0-9A-F]{4})([0-9A-F]{2})\$$"), Cmd("set_delay_lowword", "^#5([0-9A-F]{4})([0-9A-F]{2})\$$"), - # Cmd("catch_all", "^#6.*$"), # Catch-all command for debugging + Cmd("set_gate_width", "^#9([0-9A-F]{4})([0-9A-F]{2})\$$"), + Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging } in_terminator = "\n" out_terminator = "\n" # Catch all command for debugging if the IOC sends strange characters in the checksum. - # def catch_all(self): - # pass + def catch_all(self): + pass def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) @@ -83,7 +84,7 @@ def get_all_data(self, checksum): + JulichChecksum.append_checksum("#6{:04X}".format(int(math.floor((self._device.delay * 50.4) / 65536)))) \ + JulichChecksum.append_checksum("#75209") \ + JulichChecksum.append_checksum("#80000") \ - + JulichChecksum.append_checksum("#9002A") \ + + JulichChecksum.append_checksum("#9{:04X}".format(int(math.floor(self._device.get_gate_width() * 50.4)))) \ + JulichChecksum.append_checksum("#A01EB") \ + JulichChecksum.append_checksum("#B01F0") \ + JulichChecksum.append_checksum("#C01F9") \ @@ -113,3 +114,7 @@ def set_delay_highword(self, command, checksum): def set_delay_lowword(self, command, checksum): JulichChecksum.verify('#5', command, checksum) self._device.set_delay_lowword(int(command, 16)) + + def set_gate_width(self, command, checksum): + JulichChecksum.verify('#9', command, checksum) + self._device.set_gate_width(int(command, 16) / 50.4) \ No newline at end of file From 4bd2ea26d82841e7af2a908641ab1cb16be16b0a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 25 Jul 2017 11:45:07 +0100 Subject: [PATCH 0242/1466] Fix bug which only let you send integer delays --- lewis_emulators/fermichopper/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index 09833f85..37d8d9b6 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -56,7 +56,7 @@ def set_delay_lowword(self, value): self.update_delay() def update_delay(self): - self.delay = (self.delay_highword * 65536 + self.delay_lowword)/50400 + self.delay = (self.delay_highword * 65536 + self.delay_lowword)/50400.0 def set_gate_width(self, value): self.gatewidth = value From bf142e6ab79612039f39b2a86788bbe5dc9aaf2b Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 25 Jul 2017 11:46:04 +0100 Subject: [PATCH 0243/1466] Resolve merge --- lewis_emulators/instron_stress_rig/channel.py | 8 +++-- lewis_emulators/instron_stress_rig/device.py | 32 +------------------ 2 files changed, 6 insertions(+), 34 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/channel.py b/lewis_emulators/instron_stress_rig/channel.py index 06beda25..f7ef6d67 100644 --- a/lewis_emulators/instron_stress_rig/channel.py +++ b/lewis_emulators/instron_stress_rig/channel.py @@ -5,22 +5,24 @@ def __init__(self): self.ramp_amplitude_setpoint = 0 self.scale = 10 self.value = 0 - self.type_1 = 0 - self.type_2 = 0 + self.transducer_type = 0 class PositionChannel(Channel): def __init__(self): super(PositionChannel, self).__init__() + self.channel_type = 3 class StressChannel(Channel): def __init__(self): super(StressChannel, self).__init__() self.area = 1 + self.channel_type = 2 class StrainChannel(Channel): def __init__(self): super(StrainChannel, self).__init__() - self.length = 1 \ No newline at end of file + self.length = 1 + self.channel_type = 4 \ No newline at end of file diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 3c9f4824..242fb0df 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -227,7 +227,6 @@ def set_waveform_hold(self): def quarter_cycle_event(self): self._waveform_generator.quart_counter.count() -<<<<<<< HEAD def arm_quarter_counter(self): self._waveform_generator.quart_counter.arm() @@ -242,38 +241,9 @@ def set_quarter_counter_off(self): def get_quarter_counter_status(self): return self._waveform_generator.quart_counter.state -======= -class Channel(object): - def __init__(self): - self.waveform_type = 0 - self.step_time = 0 - self.ramp_amplitude_setpoint = 0 - self.scale = 10 - self.value = 0 - self.transducer_type = 0 - -class PositionChannel(Channel): - def __init__(self): - super(PositionChannel, self).__init__() - self.channel_type = 3 - -class StressChannel(Channel): - def __init__(self): - super(StressChannel, self).__init__() - self.area = 1 - self.channel_type = 2 ->>>>>>> Ticket2112_stress_rig_channel_commands def set_waveform_maintain_log(self): return self._waveform_generator.maintain_log() - -<<<<<<< HEAD + def get_waveform_value(self): return self._waveform_generator.get_value(self.control_channel) -======= -class StrainChannel(Channel): - def __init__(self): - super(StrainChannel, self).__init__() - self.length = 1 - self.channel_type = 4 ->>>>>>> Ticket2112_stress_rig_channel_commands From 83e5d5411d35426e43500bf2965ca5ae95ca30ba Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 25 Jul 2017 12:05:50 +0100 Subject: [PATCH 0244/1466] Add possibility to set temperatures via backdoor --- lewis_emulators/fermichopper/device.py | 11 ++++++++++- .../fermichopper/interfaces/stream_interface.py | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index 37d8d9b6..e792a9e9 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -19,6 +19,9 @@ def _initialize_data(self): self.gatewidth = 0 + self.electronics_temp = 30.0 + self.motor_temp = 30.0 + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -44,7 +47,7 @@ def get_speed_setpoint(self): return self.speed_setpoint def get_speed(self): - # TODO + # TODO - gradually ramp to speed? return self.speed_setpoint def set_delay_highword(self, value): @@ -63,3 +66,9 @@ def set_gate_width(self, value): def get_gate_width(self): return self.gatewidth + + def get_electronics_temp(self): + return self.electronics_temp + + def get_motor_temp(self): + return self.motor_temp diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 1f5fdccf..404af49b 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -91,8 +91,8 @@ def get_all_data(self, checksum): + JulichChecksum.append_checksum("#D01FB") \ + JulichChecksum.append_checksum("#E020C") \ + JulichChecksum.append_checksum("#F0296") \ - + JulichChecksum.append_checksum("#G0198") \ - + JulichChecksum.append_checksum("#H0176") + + JulichChecksum.append_checksum("#G{:04X}".format(int(math.floor((self._device.get_electronics_temp()+25.0) / 0.14663)))) \ + + JulichChecksum.append_checksum("#H{:04X}".format(int(math.floor((self._device.get_motor_temp()+12.124) / 0.1263)))) def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From e013bbc3f359d89699fd264eceaf737d44fa5f4a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 25 Jul 2017 13:24:56 +0100 Subject: [PATCH 0245/1466] Add ability to set autozero voltages via backdoor --- lewis_emulators/fermichopper/device.py | 14 +++++++++++ .../interfaces/stream_interface.py | 24 +++++++++---------- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index e792a9e9..d930c5a5 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -22,6 +22,14 @@ def _initialize_data(self): self.electronics_temp = 30.0 self.motor_temp = 30.0 + self.voltage = 0 + self.current = 0 + + self.autozero_1_lower = 0 + self.autozero_2_lower = 0 + self.autozero_1_upper = 0 + self.autozero_2_upper = 0 + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -72,3 +80,9 @@ def get_electronics_temp(self): def get_motor_temp(self): return self.motor_temp + + def get_voltage(self): + return self.voltage + + def get_current(self): + return self.current diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 404af49b..dbae1fd7 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -85,14 +85,14 @@ def get_all_data(self, checksum): + JulichChecksum.append_checksum("#75209") \ + JulichChecksum.append_checksum("#80000") \ + JulichChecksum.append_checksum("#9{:04X}".format(int(math.floor(self._device.get_gate_width() * 50.4)))) \ - + JulichChecksum.append_checksum("#A01EB") \ - + JulichChecksum.append_checksum("#B01F0") \ - + JulichChecksum.append_checksum("#C01F9") \ - + JulichChecksum.append_checksum("#D01FB") \ - + JulichChecksum.append_checksum("#E020C") \ - + JulichChecksum.append_checksum("#F0296") \ - + JulichChecksum.append_checksum("#G{:04X}".format(int(math.floor((self._device.get_electronics_temp()+25.0) / 0.14663)))) \ - + JulichChecksum.append_checksum("#H{:04X}".format(int(math.floor((self._device.get_motor_temp()+12.124) / 0.1263)))) + + JulichChecksum.append_checksum("#A{:04X}".format(int(round(self._device.get_current() / 0.002016)))) \ + + JulichChecksum.append_checksum("#B{:04X}".format(int(round((self._device.autozero_1_upper + 22.86647) / 0.04486)))) \ + + JulichChecksum.append_checksum("#C{:04X}".format(int(round((self._device.autozero_2_upper + 22.86647) / 0.04486)))) \ + + JulichChecksum.append_checksum("#D{:04X}".format(int(round((self._device.autozero_1_lower + 22.86647) / 0.04486)))) \ + + JulichChecksum.append_checksum("#E{:04X}".format(int(round((self._device.autozero_2_lower + 22.86647) / 0.04486)))) \ + + JulichChecksum.append_checksum("#F{:04X}".format(int(round(self._device.get_voltage() / 0.4274)))) \ + + JulichChecksum.append_checksum("#G{:04X}".format(int(round((self._device.get_electronics_temp()+25.0) / 0.14663)))) \ + + JulichChecksum.append_checksum("#H{:04X}".format(int(round((self._device.get_motor_temp()+12.124) / 0.1263)))) def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) @@ -107,14 +107,14 @@ def set_speed(self, command, checksum): JulichChecksum.verify("#3", command, checksum) self._device.set_speed((12-int(command, 16))*50) - def set_delay_highword(self, command, checksum): - JulichChecksum.verify('#6', command, checksum) - self._device.set_delay_highword(int(command, 16)) - def set_delay_lowword(self, command, checksum): JulichChecksum.verify('#5', command, checksum) self._device.set_delay_lowword(int(command, 16)) + def set_delay_highword(self, command, checksum): + JulichChecksum.verify('#6', command, checksum) + self._device.set_delay_highword(int(command, 16)) + def set_gate_width(self, command, checksum): JulichChecksum.verify('#9', command, checksum) self._device.set_gate_width(int(command, 16) / 50.4) \ No newline at end of file From 7d4b0bce122eecbaee04428f57253cca6719fff0 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 25 Jul 2017 14:40:09 +0100 Subject: [PATCH 0246/1466] Implement realistic behaviour on entering a broken state --- lewis_emulators/fermichopper/device.py | 50 +++++++++++++++---- .../interfaces/stream_interface.py | 12 ++--- lewis_emulators/fermichopper/states.py | 36 +++++++++++++ 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index d930c5a5..fe74c5a7 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultState +from states import DefaultState, GoingState, BrokenState, StoppedState, StoppingState from lewis.devices import StateMachineDevice @@ -10,6 +10,7 @@ def _initialize_data(self): Initialize all of the device's attributes. """ self.last_command = "0000" + self.speed = 0 self.speed_setpoint = 0 self._allowed_speed_setpoints = (50*i for i in range(1, 13)) @@ -30,33 +31,64 @@ def _initialize_data(self): self.autozero_1_upper = 0 self.autozero_2_upper = 0 + self.drive = False + self.runmode = False + self.magneticbearing = False + def _get_state_handlers(self): return { 'default': DefaultState(), + 'stopping': StoppingState(), + 'going': GoingState(), + 'stopped': StoppedState(), + 'broken': BrokenState() } def _get_initial_state(self): return 'default' def _get_transition_handlers(self): - return OrderedDict([]) - - def set_last_command(self, value): - self.last_command = value + return OrderedDict([ + (('default', 'stopped'), lambda: not self.runmode), + (('default', 'going'), lambda: self.runmode), + (('stopped', 'going'), lambda: self.runmode), + (('going', 'broken'), lambda: self.speed > 10 and not self.magneticbearing), + (('going', 'stopping'), lambda: self.runmode is False), + (('stopping', 'stopped'), lambda: self.speed == 0), + (('stopping', 'broken'), lambda: self.speed > 10 and not self.magneticbearing), + ]) + + def do_command(self, command): + self.last_command = command + + if command == "0001": + self.drive = True + self.runmode = False + elif command == "0002": + self.drive = False + elif command == "0003": + self.drive = True + self.runmode = True + elif command == "0004": + self.magneticbearing = True + elif command == "0005": + self.magneticbearing = False def get_last_command(self): return self.last_command - def set_speed(self, value): + def set_speed_setpoint(self, value): assert value in self._allowed_speed_setpoints self.speed_setpoint = value def get_speed_setpoint(self): return self.speed_setpoint - def get_speed(self): - # TODO - gradually ramp to speed? - return self.speed_setpoint + def set_true_speed(self, value): + self.speed = value + + def get_true_speed(self): + return self.speed def set_delay_highword(self, value): self.delay_highword = value diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index dbae1fd7..ed3e027a 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -60,15 +60,15 @@ class FermichopperStreamInterface(StreamAdapter): Cmd("set_delay_highword", "^#6([0-9A-F]{4})([0-9A-F]{2})\$$"), Cmd("set_delay_lowword", "^#5([0-9A-F]{4})([0-9A-F]{2})\$$"), Cmd("set_gate_width", "^#9([0-9A-F]{4})([0-9A-F]{2})\$$"), - Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + # Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging } in_terminator = "\n" out_terminator = "\n" # Catch all command for debugging if the IOC sends strange characters in the checksum. - def catch_all(self): - pass + # def catch_all(self): + # pass def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) @@ -79,7 +79,7 @@ def get_all_data(self, checksum): return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) \ + JulichChecksum.append_checksum("#2003F") \ + JulichChecksum.append_checksum("#3000{:01X}".format(12 - (self._device.get_speed_setpoint()/50))) \ - + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_speed())) \ + + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_true_speed())) \ + JulichChecksum.append_checksum("#5{:04X}".format(int(math.floor((self._device.delay * 50.4) % 65536)))) \ + JulichChecksum.append_checksum("#6{:04X}".format(int(math.floor((self._device.delay * 50.4) / 65536)))) \ + JulichChecksum.append_checksum("#75209") \ @@ -101,11 +101,11 @@ def execute_command(self, command, checksum): assert command in valid_commands, "Invalid command." - self._device.set_last_command(command) + self._device.do_command(command) def set_speed(self, command, checksum): JulichChecksum.verify("#3", command, checksum) - self._device.set_speed((12-int(command, 16))*50) + self._device.set_speed_setpoint((12-int(command, 16))*50) def set_delay_lowword(self, command, checksum): JulichChecksum.verify('#5', command, checksum) diff --git a/lewis_emulators/fermichopper/states.py b/lewis_emulators/fermichopper/states.py index e9b6b3ee..b8fd89f6 100644 --- a/lewis_emulators/fermichopper/states.py +++ b/lewis_emulators/fermichopper/states.py @@ -1,4 +1,40 @@ from lewis.core.statemachine import State +from lewis.core import approaches class DefaultState(State): pass + +class StoppingState(State): + def in_state(self, dt): + device = self._context + + rate = 0 + + if not device.magneticbearing: + rate += 1 + if device.drive: + rate += 50 + + device.set_true_speed(approaches.linear(device.get_true_speed(), 0, rate, dt)) + +class GoingState(State): + def in_state(self, dt): + device = self._context + + rate = 0 + + if device.drive: + rate += 50 + if not device.magneticbearing: + rate -= 1 + + device.set_true_speed(approaches.linear(device.get_true_speed(), device.get_speed_setpoint(), rate, dt)) + +class StoppedState(State): + def in_state(self, dt): + pass + +class BrokenState(State): + def in_state(self, dt): + # Fail hard - This crashes the emulator (which is the realistic behaviour in this situation...) + assert False, "The device is broken. Game over." From 4261128d89a94cf7178acef5efe44f8fd8f859d5 Mon Sep 17 00:00:00 2001 From: Attah Date: Tue, 25 Jul 2017 15:09:46 +0100 Subject: [PATCH 0247/1466] Kepco Device Emulator --- lewis_emulators/kepco/.idea/kepco.iml | 11 + lewis_emulators/kepco/.idea/misc.xml | 4 + lewis_emulators/kepco/.idea/modules.xml | 8 + lewis_emulators/kepco/.idea/workspace.xml | 568 ++++++++++++++++++ lewis_emulators/kepco/__init__.py | 3 + lewis_emulators/kepco/device.py | 111 ++++ lewis_emulators/kepco/interfaces/__init__.py | 3 + .../kepco/interfaces/stream_interface.py | 65 ++ 8 files changed, 773 insertions(+) create mode 100644 lewis_emulators/kepco/.idea/kepco.iml create mode 100644 lewis_emulators/kepco/.idea/misc.xml create mode 100644 lewis_emulators/kepco/.idea/modules.xml create mode 100644 lewis_emulators/kepco/.idea/workspace.xml create mode 100644 lewis_emulators/kepco/__init__.py create mode 100644 lewis_emulators/kepco/device.py create mode 100644 lewis_emulators/kepco/interfaces/__init__.py create mode 100644 lewis_emulators/kepco/interfaces/stream_interface.py diff --git a/lewis_emulators/kepco/.idea/kepco.iml b/lewis_emulators/kepco/.idea/kepco.iml new file mode 100644 index 00000000..174168df --- /dev/null +++ b/lewis_emulators/kepco/.idea/kepco.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/kepco/.idea/misc.xml b/lewis_emulators/kepco/.idea/misc.xml new file mode 100644 index 00000000..e2f5a1e6 --- /dev/null +++ b/lewis_emulators/kepco/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lewis_emulators/kepco/.idea/modules.xml b/lewis_emulators/kepco/.idea/modules.xml new file mode 100644 index 00000000..1e1a9944 --- /dev/null +++ b/lewis_emulators/kepco/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/kepco/.idea/workspace.xml b/lewis_emulators/kepco/.idea/workspace.xml new file mode 100644 index 00000000..5093bad2 --- /dev/null +++ b/lewis_emulators/kepco/.idea/workspace.xml @@ -0,0 +1,568 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1500476248675 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/kepco/__init__.py b/lewis_emulators/kepco/__init__.py new file mode 100644 index 00000000..bdac428f --- /dev/null +++ b/lewis_emulators/kepco/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedKepco + +__all__ = ['SimulatedKepco'] diff --git a/lewis_emulators/kepco/device.py b/lewis_emulators/kepco/device.py new file mode 100644 index 00000000..4b61bb07 --- /dev/null +++ b/lewis_emulators/kepco/device.py @@ -0,0 +1,111 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedKepco(StateMachineDevice): + """ + Simulated Kepco + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self._voltage = 10.0 + self._current = 10.0 + self._setpoint_voltage = 10.0 + self._setpoint_current = 10.0 + self._output_mode = 0 + self._output_status = 0 + + @property + def voltage(self): + """ + Returns: the Voltage + """ + return self._voltage + + @voltage.setter + def voltage(self, voltage): + """ + :param VOLTAGE: Write the Voltage + """ + self._voltage = voltage + + + @property + def current(self): + """ + :return: get the Current + """ + return self._current + + @current.setter + def current(self, current): + """ + :param write the current: + """ + self._current = current + + + @property + def setpoint_voltage(self): + """ + Returns: the Setpoint Voltage + """ + return self._setpoint_voltage + + @setpoint_voltage.setter + def setpoint_voltage(self, setpoint_voltage): + """ + :param SETPOINTVOLTAGE: set the Setpoint Voltage + :return: + """ + self._setpoint_voltage = setpoint_voltage + + @property + def setpoint_current(self): + """ + Returns: the Setpoint Current + """ + return self._setpoint_current + + @setpoint_current.setter + def setpoint_current(self, setpoint_current): + """ + :param SETpoint CURRENT: set the setpoint current + :return: + """ + self._setpoint_current = setpoint_current + + @property + def output_mode(self): + """ + :return: Returns the output mode + """ + return self._output_mode + + @output_mode.setter + def output_mode(self, mode): + """ + :param mode: Set output mode + """ + self._output_mode = mode + + @property + def output_status(self): + """ + :return: Output status + """ + return self._output_status + + @output_status.setter + def output_status(self, status): + """ + :param status: set Output status + """ + self._output_status = status + + diff --git a/lewis_emulators/kepco/interfaces/__init__.py b/lewis_emulators/kepco/interfaces/__init__.py new file mode 100644 index 00000000..7e76362b --- /dev/null +++ b/lewis_emulators/kepco/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import KepcoStreamInterface + +__all__ = ['KepcoStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/kepco/interfaces/stream_interface.py b/lewis_emulators/kepco/interfaces/stream_interface.py new file mode 100644 index 00000000..9579f1d9 --- /dev/null +++ b/lewis_emulators/kepco/interfaces/stream_interface.py @@ -0,0 +1,65 @@ +from lewis.adapters.stream import StreamAdapter +from utils.command_builder import CmdBuilder + +from lewis.core.logging import has_log + + +@has_log +class KepcoStreamInterface(StreamAdapter): + + in_terminator="\r\n" + out_terminator="\r\n" + + commands = { + CmdBuilder("write_voltage").escape("VOLT ").float().build(), + CmdBuilder("read_actual_voltage").escape("MEAS:VOLT?").build(), + CmdBuilder("read_actual_current").escape("MEAS:CURR?").build(), + CmdBuilder("write_current").escape("CURR ").float().build(), + CmdBuilder("read_setpoint_voltage").escape("VOLT?").build(), + CmdBuilder("read_setpoint_current").escape("CURR?").build(), + CmdBuilder("set_output_mode").escape("FUNC:MODE ").arg("VOLT|CURR").build(), + CmdBuilder("read_output_mode").escape("FUNC:MODE?").build(), + CmdBuilder("read_output_status").escape("OUTP?").build(), + CmdBuilder("set_output_status").escape("OUTP ").arg("0|1").build() + } + + def handle_error(self,request, error): + self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) + print("An error occurred at request" + repr(request) + ": " + repr(error)) + + def read_actual_voltage(self): + return "{0}".format(self._device.voltage) + + def read_actual_current(self): + return "{0}".format(self._device.current) + + def write_voltage(self, voltage): + self._device.setpoint_voltage = voltage + + def write_current(self, current): + self._device.setpoint_current = current + + def read_setpoint_voltage(self): + return "{0}".format(self._device.setpoint_voltage) + + def read_setpoint_current(self): + return "{0}".format(self._device.setpoint_current) + + def set_output_mode(self, mode): + self._device.output_mode = mode + + def read_output_mode(self): + return "{0}".format(self._device.output_mode) + + def set_output_status(self, status): + self._device.output_status = status + + def read_output_status(self): + return "{0}".format(self._device.output_status) + + + + + + + From 3e9e045b73da19a2afd90640451ee8fc2344e95a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 26 Jul 2017 09:40:08 +0100 Subject: [PATCH 0248/1466] Add a better status output --- .../interfaces/stream_interface.py | 22 +++++++++++++++++-- lewis_emulators/fermichopper/states.py | 10 +++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index ed3e027a..6fa73cd5 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -70,6 +70,24 @@ class FermichopperStreamInterface(StreamAdapter): # def catch_all(self): # pass + def build_status_code(self): + status = 0 + + if True: + status += 1 + if self._device.get_true_speed() == self._device.get_speed_setpoint(): + status += 2 + if self._device.magneticbearing: + status += 8 + if self._device.get_voltage() > 0: + status += 16 + if self._device.speed > 600: + status += 1024 + if self._device.speed > 10 and not self._device.magneticbearing: + status += 2048 + + return status + def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) @@ -77,9 +95,9 @@ def handle_error(self, request, error): def get_all_data(self, checksum): JulichChecksum.verify('#0', '0000', checksum) return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) \ - + JulichChecksum.append_checksum("#2003F") \ + + JulichChecksum.append_checksum("#2{:04X}".format(self.build_status_code())) \ + JulichChecksum.append_checksum("#3000{:01X}".format(12 - (self._device.get_speed_setpoint()/50))) \ - + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_true_speed())) \ + + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_true_speed()*60)) \ + JulichChecksum.append_checksum("#5{:04X}".format(int(math.floor((self._device.delay * 50.4) % 65536)))) \ + JulichChecksum.append_checksum("#6{:04X}".format(int(math.floor((self._device.delay * 50.4) / 65536)))) \ + JulichChecksum.append_checksum("#75209") \ diff --git a/lewis_emulators/fermichopper/states.py b/lewis_emulators/fermichopper/states.py index b8fd89f6..327a2541 100644 --- a/lewis_emulators/fermichopper/states.py +++ b/lewis_emulators/fermichopper/states.py @@ -15,7 +15,8 @@ def in_state(self, dt): if device.drive: rate += 50 - device.set_true_speed(approaches.linear(device.get_true_speed(), 0, rate, dt)) + # device.set_true_speed(approaches.linear(device.get_true_speed(), 0, rate*1000, dt)) + device.set_true_speed(0) class GoingState(State): def in_state(self, dt): @@ -25,10 +26,11 @@ def in_state(self, dt): if device.drive: rate += 50 - if not device.magneticbearing: - rate -= 1 + if not device.magneticbearing: + rate -= 1 - device.set_true_speed(approaches.linear(device.get_true_speed(), device.get_speed_setpoint(), rate, dt)) + # device.set_true_speed(approaches.linear(device.get_true_speed(), device.get_speed_setpoint(), rate*1000, dt)) + device.set_true_speed(device.get_speed_setpoint()) class StoppedState(State): def in_state(self, dt): From 00e7a106bc58b91351846da156f1e8206f2c672b Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Wed, 26 Jul 2017 10:15:35 +0100 Subject: [PATCH 0249/1466] created emulator for ag33220a --- lewis_emulators/ag33220a/__init__.py | 3 + lewis_emulators/ag33220a/device.py | 11 ++ .../ag33220a/interfaces/__init__.py | 3 + .../ag33220a/interfaces/stream_interface.py | 100 ++++++++++++++++++ 4 files changed, 117 insertions(+) create mode 100644 lewis_emulators/ag33220a/__init__.py create mode 100644 lewis_emulators/ag33220a/device.py create mode 100644 lewis_emulators/ag33220a/interfaces/__init__.py create mode 100644 lewis_emulators/ag33220a/interfaces/stream_interface.py diff --git a/lewis_emulators/ag33220a/__init__.py b/lewis_emulators/ag33220a/__init__.py new file mode 100644 index 00000000..2588d335 --- /dev/null +++ b/lewis_emulators/ag33220a/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedAG33220A + +__all__ = ['SimulatedAG33220A'] diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py new file mode 100644 index 00000000..e2bc877a --- /dev/null +++ b/lewis_emulators/ag33220a/device.py @@ -0,0 +1,11 @@ +from lewis.devices import Device + + +class SimulatedAG33220A(Device): + voltage = 1 + frequency = 1000 + offset = 0 + units = "VPP" + function = "SIN" + output = 0 + pass diff --git a/lewis_emulators/ag33220a/interfaces/__init__.py b/lewis_emulators/ag33220a/interfaces/__init__.py new file mode 100644 index 00000000..e84bb41d --- /dev/null +++ b/lewis_emulators/ag33220a/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import AG33220AStreamInterface + +__all__ = ['AG33220AStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py new file mode 100644 index 00000000..8e0aeaa5 --- /dev/null +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -0,0 +1,100 @@ +from lewis.adapters.stream import StreamAdapter, Cmd +import random + +SP_COMM = "spv" +UNITS_COMM = "uiu" +READING_COMM = "r" + + +class AG33220AStreamInterface(StreamAdapter): + + commands = { + Cmd("get_voltage", "^VOLT\?$"), + Cmd("set_voltage", "^VOLT " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("get_freq", "^FREQ\?$"), + Cmd("set_freq", "^FREQ " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("get_offset", "^VOLT:OFFS\?$"), + Cmd("set_offset", "^VOLT:OFFS " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("get_units", "^VOLT:UNIT\?$"), + Cmd("set_units", "^VOLT:UNIT " + "(VPP|VRMS|DBM)$", argument_mappings=[str]), + Cmd("get_function", "^FUNC\?$"), + Cmd("set_function", "^FUNC " + "(SIN|SQU|RAMP|PULS|NOIS|DC|USER)$", argument_mappings=[str]), + Cmd("get_output", "^OUTP\?$"), + Cmd("set_output", "^OUTP " + "(0|1|ON|OFF)$", argument_mappings=[str]), + + } + + in_terminator = "\n" + out_terminator = "\n" + + #out_echo = "33220A"#*{}*:{};{}" + #out_response = "!{}!o!" + +# def create_response(self,): + #def create_response(self, command, params=" ", data=None): + # out = self.out_echo.format(self._device.address, command, params) + self.out_terminator + # if data: + # out += data + self.out_terminator + # return out + + def get_voltage(self): +# print "Device voltage is: " + str(self._device.voltage) + return self._device.voltage + + def set_voltage(self, new_voltage): + self._device.voltage = new_voltage +# print "Device voltage is: " + str(self._device.voltage) +# return self.create_response("VOLT") + + def get_freq(self): +# print "1. Device frequency is: " + str(self._device.frequency) + return self._device.frequency + + def set_freq(self, new_frequency): + self._device.frequency = new_frequency +# print "2. Device frequency is: " + str(self._device.frequency) + + def get_offset(self): + #print "1. Device offset is: " + str(self._device.offset) + return self._device.offset + + def set_offset(self, new_offset): + self._device.offset = new_offset + #print "2. Device offset is: " + str(self._device.offset) + + def get_units(self): + return self._device.units + + def set_units(self, new_units): + self._device.units = new_units + + def get_function(self): + return self._device.funtion + + def set_function(self, new_function): + self._device.function = new_function + + def get_output(self): + print str(self._device.output) + "...3" + return self._device.output + + def set_output(self, new_output): + try: + new_output = int(new_output) + except: + new_output = ["OFF", "ON"].index(new_output) + self._device.output = new_output + + + + +################### +# def get_reading(self): +# rand = random.random() * 100.0 +# min_width = 10 +# data_str = "READ:" + "{:0.3f}".format(rand).ljust(min_width) + ";" + str(self._device.setpoint_mode) +# return self.create_response(READING_COMM + " ", data=data_str) +# +# def handle_error(self, request, error): +# print "An error occurred at request " + repr(request) + ": " + repr(error) +#################### From 78917d31978093338eecff55706687b0dae6be70 Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Wed, 26 Jul 2017 11:03:09 +0100 Subject: [PATCH 0250/1466] Added more commands for AG33220A emulator & fixed function command --- lewis_emulators/ag33220a/device.py | 6 ++++ .../ag33220a/interfaces/stream_interface.py | 30 +++---------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index e2bc877a..c8188e24 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -2,6 +2,12 @@ class SimulatedAG33220A(Device): + make = "Agilent Technologies,33220A" + firmwareRevNum = "MY44033103,2.02" + bootKernelRevisionNumber = "2.02" + asicRevNum = "22" + printedCircuitBoardRevNum = "2" + idn = make+"-"+firmwareRevNum+"-"+bootKernelRevisionNumber+"-"+asicRevNum+"-"+printedCircuitBoardRevNum voltage = 1 frequency = 1000 offset = 0 diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index 8e0aeaa5..9525ccb2 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -1,10 +1,4 @@ from lewis.adapters.stream import StreamAdapter, Cmd -import random - -SP_COMM = "spv" -UNITS_COMM = "uiu" -READING_COMM = "r" - class AG33220AStreamInterface(StreamAdapter): @@ -21,46 +15,30 @@ class AG33220AStreamInterface(StreamAdapter): Cmd("set_function", "^FUNC " + "(SIN|SQU|RAMP|PULS|NOIS|DC|USER)$", argument_mappings=[str]), Cmd("get_output", "^OUTP\?$"), Cmd("set_output", "^OUTP " + "(0|1|ON|OFF)$", argument_mappings=[str]), + Cmd("get_idn", "^\*IDN\?$"), } in_terminator = "\n" out_terminator = "\n" - #out_echo = "33220A"#*{}*:{};{}" - #out_response = "!{}!o!" - -# def create_response(self,): - #def create_response(self, command, params=" ", data=None): - # out = self.out_echo.format(self._device.address, command, params) + self.out_terminator - # if data: - # out += data + self.out_terminator - # return out - def get_voltage(self): -# print "Device voltage is: " + str(self._device.voltage) return self._device.voltage def set_voltage(self, new_voltage): self._device.voltage = new_voltage -# print "Device voltage is: " + str(self._device.voltage) -# return self.create_response("VOLT") def get_freq(self): -# print "1. Device frequency is: " + str(self._device.frequency) return self._device.frequency def set_freq(self, new_frequency): self._device.frequency = new_frequency -# print "2. Device frequency is: " + str(self._device.frequency) def get_offset(self): - #print "1. Device offset is: " + str(self._device.offset) return self._device.offset def set_offset(self, new_offset): self._device.offset = new_offset - #print "2. Device offset is: " + str(self._device.offset) def get_units(self): return self._device.units @@ -69,13 +47,12 @@ def set_units(self, new_units): self._device.units = new_units def get_function(self): - return self._device.funtion + return self._device.function def set_function(self, new_function): self._device.function = new_function def get_output(self): - print str(self._device.output) + "...3" return self._device.output def set_output(self, new_output): @@ -85,7 +62,8 @@ def set_output(self, new_output): new_output = ["OFF", "ON"].index(new_output) self._device.output = new_output - + def get_idn(self): + return self._device.idn ################### From 5c2978f505e4baec1b1776dbd99f49d8a1fdd603 Mon Sep 17 00:00:00 2001 From: Attah Date: Wed, 26 Jul 2017 11:06:06 +0100 Subject: [PATCH 0251/1466] Update to IOC --- lewis_emulators/kepco/__init__.py | 1 + lewis_emulators/kepco/device.py | 37 ++++++++++++++++++- lewis_emulators/kepco/interfaces/__init__.py | 2 +- .../kepco/interfaces/stream_interface.py | 14 +++++-- 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/kepco/__init__.py b/lewis_emulators/kepco/__init__.py index bdac428f..82be7039 100644 --- a/lewis_emulators/kepco/__init__.py +++ b/lewis_emulators/kepco/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedKepco __all__ = ['SimulatedKepco'] + diff --git a/lewis_emulators/kepco/device.py b/lewis_emulators/kepco/device.py index 4b61bb07..25feb4cd 100644 --- a/lewis_emulators/kepco/device.py +++ b/lewis_emulators/kepco/device.py @@ -1,6 +1,6 @@ -from collections import OrderedDict - from lewis.devices import StateMachineDevice + +from collections import OrderedDict from .states import DefaultState @@ -19,6 +19,38 @@ def _initialize_data(self): self._setpoint_current = 10.0 self._output_mode = 0 self._output_status = 0 + self._idn = "000000000000000000000000000000000000000" + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() + + @property + def idn(self): + """ + + :return: IDN + """ + return self._idn + + @idn.setter + def idn(self, idn): + + self._idn = idn @property def voltage(self): @@ -109,3 +141,4 @@ def output_status(self, status): self._output_status = status + diff --git a/lewis_emulators/kepco/interfaces/__init__.py b/lewis_emulators/kepco/interfaces/__init__.py index 7e76362b..1aed9c0f 100644 --- a/lewis_emulators/kepco/interfaces/__init__.py +++ b/lewis_emulators/kepco/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import KepcoStreamInterface -__all__ = ['KepcoStreamInterface'] \ No newline at end of file +__all__ = ['KepcoStreamInterface'] diff --git a/lewis_emulators/kepco/interfaces/stream_interface.py b/lewis_emulators/kepco/interfaces/stream_interface.py index 9579f1d9..f207b61c 100644 --- a/lewis_emulators/kepco/interfaces/stream_interface.py +++ b/lewis_emulators/kepco/interfaces/stream_interface.py @@ -20,8 +20,9 @@ class KepcoStreamInterface(StreamAdapter): CmdBuilder("set_output_mode").escape("FUNC:MODE ").arg("VOLT|CURR").build(), CmdBuilder("read_output_mode").escape("FUNC:MODE?").build(), CmdBuilder("read_output_status").escape("OUTP?").build(), - CmdBuilder("set_output_status").escape("OUTP ").arg("0|1").build() - } + CmdBuilder("set_output_status").escape("OUTP ").arg("0|1").build(), + CmdBuilder("get_IDN").escape("*IDN?").build() + } def handle_error(self,request, error): self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) @@ -46,7 +47,7 @@ def read_setpoint_current(self): return "{0}".format(self._device.setpoint_current) def set_output_mode(self, mode): - self._device.output_mode = mode + self._device.output_mode = mode def read_output_mode(self): return "{0}".format(self._device.output_mode) @@ -57,6 +58,13 @@ def set_output_status(self, status): def read_output_status(self): return "{0}".format(self._device.output_status) + def get_IDN(self): + self.log.error("IDN I{0}".format(self._device.idn)) + return "{0}".format(self._device.idn) + + + + From ea7c2210594dbd4b579a259b16db4640ab297e56 Mon Sep 17 00:00:00 2001 From: Attah Date: Wed, 26 Jul 2017 11:13:27 +0100 Subject: [PATCH 0252/1466] Deleting .idea folder --- lewis_emulators/kepco/.idea/kepco.iml | 11 - lewis_emulators/kepco/.idea/misc.xml | 4 - lewis_emulators/kepco/.idea/modules.xml | 8 - lewis_emulators/kepco/.idea/workspace.xml | 568 ---------------------- 4 files changed, 591 deletions(-) delete mode 100644 lewis_emulators/kepco/.idea/kepco.iml delete mode 100644 lewis_emulators/kepco/.idea/misc.xml delete mode 100644 lewis_emulators/kepco/.idea/modules.xml delete mode 100644 lewis_emulators/kepco/.idea/workspace.xml diff --git a/lewis_emulators/kepco/.idea/kepco.iml b/lewis_emulators/kepco/.idea/kepco.iml deleted file mode 100644 index 174168df..00000000 --- a/lewis_emulators/kepco/.idea/kepco.iml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/lewis_emulators/kepco/.idea/misc.xml b/lewis_emulators/kepco/.idea/misc.xml deleted file mode 100644 index e2f5a1e6..00000000 --- a/lewis_emulators/kepco/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/lewis_emulators/kepco/.idea/modules.xml b/lewis_emulators/kepco/.idea/modules.xml deleted file mode 100644 index 1e1a9944..00000000 --- a/lewis_emulators/kepco/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/lewis_emulators/kepco/.idea/workspace.xml b/lewis_emulators/kepco/.idea/workspace.xml deleted file mode 100644 index 5093bad2..00000000 --- a/lewis_emulators/kepco/.idea/workspace.xml +++ /dev/null @@ -1,568 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1500476248675 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From 88cc850abcc616e0a5b9005dbf2c30792e9741f4 Mon Sep 17 00:00:00 2001 From: Attah Date: Wed, 26 Jul 2017 11:55:36 +0100 Subject: [PATCH 0253/1466] Adding Kepco device states --- lewis_emulators/kepco/states.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lewis_emulators/kepco/states.py diff --git a/lewis_emulators/kepco/states.py b/lewis_emulators/kepco/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/kepco/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' From 6f78c03d17d4f033a561c040ffb3727a2fe5e41c Mon Sep 17 00:00:00 2001 From: Attah Date: Wed, 26 Jul 2017 12:13:00 +0100 Subject: [PATCH 0254/1466] Review changes --- lewis_emulators/kepco/device.py | 12 ++---------- .../kepco/interfaces/stream_interface.py | 13 ------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/lewis_emulators/kepco/device.py b/lewis_emulators/kepco/device.py index 25feb4cd..12381856 100644 --- a/lewis_emulators/kepco/device.py +++ b/lewis_emulators/kepco/device.py @@ -1,9 +1,7 @@ from lewis.devices import StateMachineDevice - from collections import OrderedDict from .states import DefaultState - class SimulatedKepco(StateMachineDevice): """ Simulated Kepco @@ -42,8 +40,7 @@ def _get_transition_handlers(self): @property def idn(self): """ - - :return: IDN + :return: IDN- Identificaiton String """ return self._idn @@ -62,11 +59,10 @@ def voltage(self): @voltage.setter def voltage(self, voltage): """ - :param VOLTAGE: Write the Voltage + :param voltage: Write the Voltage """ self._voltage = voltage - @property def current(self): """ @@ -81,7 +77,6 @@ def current(self, current): """ self._current = current - @property def setpoint_voltage(self): """ @@ -139,6 +134,3 @@ def output_status(self, status): :param status: set Output status """ self._output_status = status - - - diff --git a/lewis_emulators/kepco/interfaces/stream_interface.py b/lewis_emulators/kepco/interfaces/stream_interface.py index f207b61c..d717e14c 100644 --- a/lewis_emulators/kepco/interfaces/stream_interface.py +++ b/lewis_emulators/kepco/interfaces/stream_interface.py @@ -1,9 +1,7 @@ from lewis.adapters.stream import StreamAdapter from utils.command_builder import CmdBuilder - from lewis.core.logging import has_log - @has_log class KepcoStreamInterface(StreamAdapter): @@ -59,15 +57,4 @@ def read_output_status(self): return "{0}".format(self._device.output_status) def get_IDN(self): - self.log.error("IDN I{0}".format(self._device.idn)) return "{0}".format(self._device.idn) - - - - - - - - - - From 38ad6000629d3ff46e5568e580a84d9444565c9c Mon Sep 17 00:00:00 2001 From: Attah Date: Wed, 26 Jul 2017 12:15:30 +0100 Subject: [PATCH 0255/1466] doc string for idn --- lewis_emulators/kepco/device.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/kepco/device.py b/lewis_emulators/kepco/device.py index 12381856..b0438b29 100644 --- a/lewis_emulators/kepco/device.py +++ b/lewis_emulators/kepco/device.py @@ -46,7 +46,10 @@ def idn(self): @idn.setter def idn(self, idn): - + """ + :param idn: + :return: sets IDN- Identificaiton String + """ self._idn = idn @property From 2ff5c78aa01b5bd25820d57646ba2d0dc8cb86eb Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Wed, 26 Jul 2017 13:32:10 +0100 Subject: [PATCH 0256/1466] Added formatting for floats and some extra commands --- lewis_emulators/ag33220a/device.py | 4 ++- .../ag33220a/interfaces/stream_interface.py | 31 ++++++++++++++++--- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index c8188e24..eb4cd610 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -8,10 +8,12 @@ class SimulatedAG33220A(Device): asicRevNum = "22" printedCircuitBoardRevNum = "2" idn = make+"-"+firmwareRevNum+"-"+bootKernelRevisionNumber+"-"+asicRevNum+"-"+printedCircuitBoardRevNum - voltage = 1 + voltage = 0.1 frequency = 1000 offset = 0 units = "VPP" function = "SIN" output = 0 + voltageHigh = 0.05 + voltageLow = -0.05 pass diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index 9525ccb2..1044f99b 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -1,4 +1,6 @@ from lewis.adapters.stream import StreamAdapter, Cmd +from math import log10, floor +from decimal import Decimal class AG33220AStreamInterface(StreamAdapter): @@ -16,26 +18,34 @@ class AG33220AStreamInterface(StreamAdapter): Cmd("get_output", "^OUTP\?$"), Cmd("set_output", "^OUTP " + "(0|1|ON|OFF)$", argument_mappings=[str]), Cmd("get_idn", "^\*IDN\?$"), - + Cmd("get_voltage_high", "^VOLT:HIGH\?$"), + Cmd("set_voltage_high", "^VOLT:HIGH" + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("get_voltage_low", "^VOLT:LOW\?$"), + Cmd("set_voltage_low", "^VOLT:LOW" + "([\-0-9.]+)$", argument_mappings=[float]), } - in_terminator = "\n" + in_terminator = "\n" # \r\n for putty out_terminator = "\n" + # Takes in a value and returns a value in the form of x.xxxxxxxxxxxxxEYY + def float_output(self, value): + value = float('%s' % float('%.4g' % value)) + return "{:.13E}".format(value) + def get_voltage(self): - return self._device.voltage + return self.float_output(self._device.voltage) def set_voltage(self, new_voltage): self._device.voltage = new_voltage def get_freq(self): - return self._device.frequency + return self.float_output(self._device.frequency) def set_freq(self, new_frequency): self._device.frequency = new_frequency def get_offset(self): - return self._device.offset + return self.float_output(self._device.offset) def set_offset(self, new_offset): self._device.offset = new_offset @@ -65,6 +75,17 @@ def set_output(self, new_output): def get_idn(self): return self._device.idn + def get_voltage_high(self): + return self.float_output(self._device.voltageHigh) + + def set_voltage_high(self, new_voltage_high): + self._device.voltageHigh = new_voltage_high + + def get_voltage_low(self): + return self.float_output(self._device.voltageLow) + + def set_voltage_low(self, new_voltage_low): + self._device.voltageLow = new_voltage_low ################### # def get_reading(self): From 441bd234e40deae4ddb85edb6cc22369e17c0096 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 26 Jul 2017 14:12:28 +0100 Subject: [PATCH 0257/1466] Tidy up implementation of checksum. Return real data for delay --- .../interfaces/stream_interface.py | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 6fa73cd5..50e48267 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -4,16 +4,6 @@ class JulichChecksum(object): - @staticmethod - def _hex_value(char): - """ - Converts an uppercase octadecimal character or a hash to it's ASCII identifier. - - :param char: The character to convert - :return: the ascii code of the given character - """ - assert char in list("#0123456789ABCDEFGH"), "Invalid character - can't calculate hex value!" - return ord(char) @staticmethod def _calculate(alldata): @@ -22,7 +12,8 @@ def _calculate(alldata): :param alldata: the input data (list of chars, length 5) :return: the Julich checksum of the given input data """ - return "00" if all(x in ['0', '#'] for x in alldata) else hex(sum(JulichChecksum._hex_value(i) for i in alldata)).upper()[-2:] + assert all(i in list("#0123456789ABCDEFGH") for i in alldata), "Invalid character can't calculate checksum" + return "00" if all(x in ['0', '#'] for x in alldata) else hex(sum(ord(i) for i in alldata)).upper()[-2:] @staticmethod def verify(header, data, actual_checksum): @@ -40,7 +31,7 @@ def verify(header, data, actual_checksum): assert JulichChecksum._calculate(list(header) + list(data)) == actual_checksum, "Checksum did not match" @staticmethod - def append_checksum(data): + def append(data): """ Utility method for appending the Julich checksum to the input data :param data: the input data @@ -75,7 +66,7 @@ def build_status_code(self): if True: status += 1 - if self._device.get_true_speed() == self._device.get_speed_setpoint(): + if True: status += 2 if self._device.magneticbearing: status += 8 @@ -94,23 +85,24 @@ def handle_error(self, request, error): def get_all_data(self, checksum): JulichChecksum.verify('#0', '0000', checksum) - return JulichChecksum.append_checksum('#1' + self._device.get_last_command()) \ - + JulichChecksum.append_checksum("#2{:04X}".format(self.build_status_code())) \ - + JulichChecksum.append_checksum("#3000{:01X}".format(12 - (self._device.get_speed_setpoint()/50))) \ - + JulichChecksum.append_checksum("#4{:04X}".format(self._device.get_true_speed()*60)) \ - + JulichChecksum.append_checksum("#5{:04X}".format(int(math.floor((self._device.delay * 50.4) % 65536)))) \ - + JulichChecksum.append_checksum("#6{:04X}".format(int(math.floor((self._device.delay * 50.4) / 65536)))) \ - + JulichChecksum.append_checksum("#75209") \ - + JulichChecksum.append_checksum("#80000") \ - + JulichChecksum.append_checksum("#9{:04X}".format(int(math.floor(self._device.get_gate_width() * 50.4)))) \ - + JulichChecksum.append_checksum("#A{:04X}".format(int(round(self._device.get_current() / 0.002016)))) \ - + JulichChecksum.append_checksum("#B{:04X}".format(int(round((self._device.autozero_1_upper + 22.86647) / 0.04486)))) \ - + JulichChecksum.append_checksum("#C{:04X}".format(int(round((self._device.autozero_2_upper + 22.86647) / 0.04486)))) \ - + JulichChecksum.append_checksum("#D{:04X}".format(int(round((self._device.autozero_1_lower + 22.86647) / 0.04486)))) \ - + JulichChecksum.append_checksum("#E{:04X}".format(int(round((self._device.autozero_2_lower + 22.86647) / 0.04486)))) \ - + JulichChecksum.append_checksum("#F{:04X}".format(int(round(self._device.get_voltage() / 0.4274)))) \ - + JulichChecksum.append_checksum("#G{:04X}".format(int(round((self._device.get_electronics_temp()+25.0) / 0.14663)))) \ - + JulichChecksum.append_checksum("#H{:04X}".format(int(round((self._device.get_motor_temp()+12.124) / 0.1263)))) + + return JulichChecksum.append('#1' + self._device.get_last_command()) \ + + JulichChecksum.append("#2{:04X}".format(self.build_status_code())) \ + + JulichChecksum.append("#3000{:01X}".format(12 - (self._device.get_speed_setpoint() / 50))) \ + + JulichChecksum.append("#4{:04X}".format(self._device.get_true_speed() * 60)) \ + + JulichChecksum.append("#5{:04X}".format(int(round((self._device.delay * 50.4) % 65536)))) \ + + JulichChecksum.append("#6{:04X}".format(int(round((self._device.delay * 50.4) / 65536)))) \ + + JulichChecksum.append("#7{:04X}".format(int(round((self._device.delay * 50.4) % 65536)))) \ + + JulichChecksum.append("#8{:04X}".format(int(round((self._device.delay * 50.4) / 65536)))) \ + + JulichChecksum.append("#9{:04X}".format(int(round(self._device.get_gate_width() * 50.4)))) \ + + JulichChecksum.append("#A{:04X}".format(int(round(self._device.get_current() / 0.002016)))) \ + + JulichChecksum.append("#B{:04X}".format(int(round((self._device.autozero_1_upper + 22.86647) / 0.04486)))) \ + + JulichChecksum.append("#C{:04X}".format(int(round((self._device.autozero_2_upper + 22.86647) / 0.04486)))) \ + + JulichChecksum.append("#D{:04X}".format(int(round((self._device.autozero_1_lower + 22.86647) / 0.04486)))) \ + + JulichChecksum.append("#E{:04X}".format(int(round((self._device.autozero_2_lower + 22.86647) / 0.04486)))) \ + + JulichChecksum.append("#F{:04X}".format(int(round(self._device.get_voltage() / 0.4274)))) \ + + JulichChecksum.append("#G{:04X}".format(int(round((self._device.get_electronics_temp() + 25.0) / 0.14663)))) \ + + JulichChecksum.append("#H{:04X}".format(int(round((self._device.get_motor_temp() + 12.124) / 0.1263)))) def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From 73da0cdf76a3471053776246844b7019e2f051c7 Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Wed, 26 Jul 2017 17:19:34 +0100 Subject: [PATCH 0258/1466] Added boundaries for voltage and frequency --- .../ag33220a/interfaces/stream_interface.py | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index 1044f99b..6d56106e 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -1,14 +1,13 @@ from lewis.adapters.stream import StreamAdapter, Cmd -from math import log10, floor -from decimal import Decimal + class AG33220AStreamInterface(StreamAdapter): commands = { Cmd("get_voltage", "^VOLT\?$"), - Cmd("set_voltage", "^VOLT " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_voltage", "^VOLT " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), Cmd("get_freq", "^FREQ\?$"), - Cmd("set_freq", "^FREQ " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_freq", "^FREQ " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), Cmd("get_offset", "^VOLT:OFFS\?$"), Cmd("set_offset", "^VOLT:OFFS " + "([\-0-9.]+)$", argument_mappings=[float]), Cmd("get_units", "^VOLT:UNIT\?$"), @@ -24,25 +23,44 @@ class AG33220AStreamInterface(StreamAdapter): Cmd("set_voltage_low", "^VOLT:LOW" + "([\-0-9.]+)$", argument_mappings=[float]), } - in_terminator = "\n" # \r\n for putty - out_terminator = "\n" + in_terminator = "\r\n" # \r\n for putty + out_terminator = "\r\n" # Takes in a value and returns a value in the form of x.xxxxxxxxxxxxxEYY def float_output(self, value): - value = float('%s' % float('%.4g' % value)) - return "{:.13E}".format(value) + value = float('%s' % float('%.4g' % float(value))) + return "{:+.13E}".format(value) + + # If the value is above or below the upper or lower bound + # then the upper or lower bound will be returned respectively + # otherwise the value is returned + def limit(self,value,upper_bound,lower_bound): + if value >= upper_bound: + return upper_bound + elif value < lower_bound: + return lower_bound + else: + return value def get_voltage(self): return self.float_output(self._device.voltage) def set_voltage(self, new_voltage): - self._device.voltage = new_voltage + try: + self._device.voltage = self.limit(float(new_voltage), 10, 0.01) + except: + self._device.voltage = {"MIN": 0.01, "MAX": 10}[new_voltage] def get_freq(self): return self.float_output(self._device.frequency) def set_freq(self, new_frequency): - self._device.frequency = new_frequency + lower_bound = {"SIN": 10**-6, "SQU": 10**-6, "RAMP": 10**-6, "PULS": 5*10**-4, "NOIS": 1*10**-6, "USER": 10**-6}[self._device.function] + upper_bound = {"SIN": 2*10**7, "SQU": 2*10**7, "RAMP": 2*10**5, "PULS": 5*10**6, "NOIS": 2*10**7, "USER": 6*10**6}[self._device.function] + try: + self._device.frequency = self.limit(float(new_frequency), upper_bound, lower_bound) + except: + self._device.frequency = {"MIN": lower_bound, "MAX": upper_bound}[new_frequency] def get_offset(self): return self.float_output(self._device.offset) @@ -61,6 +79,7 @@ def get_function(self): def set_function(self, new_function): self._device.function = new_function + self.set_freq(self._device.frequency) def get_output(self): return self._device.output @@ -87,6 +106,9 @@ def get_voltage_low(self): def set_voltage_low(self, new_voltage_low): self._device.voltageLow = new_voltage_low + def handle_error(self, request, error): + print(str(error)) + ################### # def get_reading(self): # rand = random.random() * 100.0 From 0d2771ef186601481642e325b5c1db4d368019c9 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 27 Jul 2017 11:17:11 +0100 Subject: [PATCH 0259/1466] Added package name --- lewis_emulators/kepco/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/kepco/interfaces/stream_interface.py b/lewis_emulators/kepco/interfaces/stream_interface.py index d717e14c..fb9a6be7 100644 --- a/lewis_emulators/kepco/interfaces/stream_interface.py +++ b/lewis_emulators/kepco/interfaces/stream_interface.py @@ -1,5 +1,5 @@ from lewis.adapters.stream import StreamAdapter -from utils.command_builder import CmdBuilder +from lewis_emulators.utils.command_builder import CmdBuilder from lewis.core.logging import has_log @has_log From 8478e36aa55847ba485a18b5a78bd63e2d051478 Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Thu, 27 Jul 2017 11:38:27 +0100 Subject: [PATCH 0260/1466] Added automatic frequency limitation when changing function. --- lewis_emulators/ag33220a/interfaces/stream_interface.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index 6d56106e..90f0d189 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -23,8 +23,8 @@ class AG33220AStreamInterface(StreamAdapter): Cmd("set_voltage_low", "^VOLT:LOW" + "([\-0-9.]+)$", argument_mappings=[float]), } - in_terminator = "\r\n" # \r\n for putty - out_terminator = "\r\n" + in_terminator = "\n" # \r\n for putty + out_terminator = "\n" # Takes in a value and returns a value in the form of x.xxxxxxxxxxxxxEYY def float_output(self, value): @@ -78,8 +78,10 @@ def get_function(self): return self._device.function def set_function(self, new_function): + print(new_function,"\n",self._device.function) self._device.function = new_function self.set_freq(self._device.frequency) + print(new_function, "\n\n", self._device.function) def get_output(self): return self._device.output From 7d5a879c2a773a4cf74feb753d3ca020aa492800 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 27 Jul 2017 13:16:35 +0100 Subject: [PATCH 0261/1466] Better status --- lewis_emulators/fermichopper/device.py | 2 +- lewis_emulators/fermichopper/interfaces/stream_interface.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index fe74c5a7..b23764a4 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -12,7 +12,7 @@ def _initialize_data(self): self.last_command = "0000" self.speed = 0 self.speed_setpoint = 0 - self._allowed_speed_setpoints = (50*i for i in range(1, 13)) + self._allowed_speed_setpoints = list(50*i for i in range(1, 13)) self.delay_highword = 0 self.delay_lowword = 0 diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 50e48267..7cd31bf8 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -66,7 +66,7 @@ def build_status_code(self): if True: status += 1 - if True: + if self._device.get_true_speed() == self._device.get_speed_setpoint(): status += 2 if self._device.magneticbearing: status += 8 @@ -115,7 +115,7 @@ def execute_command(self, command, checksum): def set_speed(self, command, checksum): JulichChecksum.verify("#3", command, checksum) - self._device.set_speed_setpoint((12-int(command, 16))*50) + self._device.set_speed_setpoint(int((12-int(command, 16))*50)) def set_delay_lowword(self, command, checksum): JulichChecksum.verify('#5', command, checksum) From 46645e6a346762251f2f45103b66a25105924dfa Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 27 Jul 2017 13:53:48 +0100 Subject: [PATCH 0262/1466] Allow changing drive parameters --- lewis_emulators/fermichopper/device.py | 19 ++++++++++++++++- .../interfaces/stream_interface.py | 21 +++++++++++++------ 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index b23764a4..e07bad5b 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -2,7 +2,6 @@ from states import DefaultState, GoingState, BrokenState, StoppedState, StoppingState from lewis.devices import StateMachineDevice - class SimulatedFermichopper(StateMachineDevice): def _initialize_data(self): @@ -35,6 +34,8 @@ def _initialize_data(self): self.runmode = False self.magneticbearing = False + self.parameters = None + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -59,6 +60,10 @@ def _get_transition_handlers(self): ]) def do_command(self, command): + + valid_commands = ["0001", "0002", "0003", "0004", "0005", "0006", "0007", "0008"] + assert command in valid_commands, "Invalid command." + self.last_command = command if command == "0001": @@ -73,6 +78,12 @@ def do_command(self, command): self.magneticbearing = True elif command == "0005": self.magneticbearing = False + elif command == "0006": + self.parameters = ChopperParameters.MERLIN_LARGE + elif command == "0007": + self.parameters = ChopperParameters.HET_MARI + elif command == "0008": + self.parameters = ChopperParameters.MERLIN_SMALL def get_last_command(self): return self.last_command @@ -118,3 +129,9 @@ def get_voltage(self): def get_current(self): return self.current + +class ChopperParameters(object): + MERLIN_SMALL = 1 + MERLIN_LARGE = 2 + HET_MARI = 3 + diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 7cd31bf8..1dcdc03f 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -2,6 +2,8 @@ import math +from ..device import ChopperParameters + class JulichChecksum(object): @@ -64,7 +66,7 @@ class FermichopperStreamInterface(StreamAdapter): def build_status_code(self): status = 0 - if True: + if True: # Microcontroller OK? status += 1 if self._device.get_true_speed() == self._device.get_speed_setpoint(): status += 2 @@ -72,10 +74,22 @@ def build_status_code(self): status += 8 if self._device.get_voltage() > 0: status += 16 + if True: # Drive inverter on? + status += 32 + if self._device.parameters == ChopperParameters.MERLIN_LARGE: + status += 64 + if False: # Interlock open? + status += 128 + if self._device.parameters == ChopperParameters.HET_MARI: + status += 256 + if self._device.parameters == ChopperParameters.MERLIN_SMALL: + status += 512 if self._device.speed > 600: status += 1024 if self._device.speed > 10 and not self._device.magneticbearing: status += 2048 + if any(abs(voltage) > 3 for voltage in [self._device.autozero_1_lower, self._device.autozero_2_lower, self._device.autozero_1_upper, self._device.autozero_2_upper,]): + status += 4096 return status @@ -106,11 +120,6 @@ def get_all_data(self, checksum): def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) - - valid_commands = ["0001", "0002", "0003", "0004", "0005"] - - assert command in valid_commands, "Invalid command." - self._device.do_command(command) def set_speed(self, command, checksum): From 6b826efe28b0dc45a5d0c24b5203123edc99dde6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 27 Jul 2017 15:04:39 +0100 Subject: [PATCH 0263/1466] Fix bug in stream interface --- .../fermichopper/interfaces/stream_interface.py | 4 ++-- lewis_emulators/fermichopper/states.py | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 1dcdc03f..1f3d1ae9 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -74,7 +74,7 @@ def build_status_code(self): status += 8 if self._device.get_voltage() > 0: status += 16 - if True: # Drive inverter on? + if self._device.drive: status += 32 if self._device.parameters == ChopperParameters.MERLIN_LARGE: status += 64 @@ -103,7 +103,7 @@ def get_all_data(self, checksum): return JulichChecksum.append('#1' + self._device.get_last_command()) \ + JulichChecksum.append("#2{:04X}".format(self.build_status_code())) \ + JulichChecksum.append("#3000{:01X}".format(12 - (self._device.get_speed_setpoint() / 50))) \ - + JulichChecksum.append("#4{:04X}".format(self._device.get_true_speed() * 60)) \ + + JulichChecksum.append("#4{:04X}".format(int(round(self._device.get_true_speed() * 60)))) \ + JulichChecksum.append("#5{:04X}".format(int(round((self._device.delay * 50.4) % 65536)))) \ + JulichChecksum.append("#6{:04X}".format(int(round((self._device.delay * 50.4) / 65536)))) \ + JulichChecksum.append("#7{:04X}".format(int(round((self._device.delay * 50.4) % 65536)))) \ diff --git a/lewis_emulators/fermichopper/states.py b/lewis_emulators/fermichopper/states.py index 327a2541..3005bd97 100644 --- a/lewis_emulators/fermichopper/states.py +++ b/lewis_emulators/fermichopper/states.py @@ -1,9 +1,11 @@ from lewis.core.statemachine import State from lewis.core import approaches + class DefaultState(State): pass + class StoppingState(State): def in_state(self, dt): device = self._context @@ -15,8 +17,9 @@ def in_state(self, dt): if device.drive: rate += 50 - # device.set_true_speed(approaches.linear(device.get_true_speed(), 0, rate*1000, dt)) - device.set_true_speed(0) + device.set_true_speed(approaches.linear(device.get_true_speed(), 0, rate, dt)) + # device.set_true_speed(0) + class GoingState(State): def in_state(self, dt): @@ -29,13 +32,18 @@ def in_state(self, dt): if not device.magneticbearing: rate -= 1 - # device.set_true_speed(approaches.linear(device.get_true_speed(), device.get_speed_setpoint(), rate*1000, dt)) - device.set_true_speed(device.get_speed_setpoint()) + device.set_true_speed(approaches.linear(device.get_true_speed(), device.get_speed_setpoint(), rate, dt)) + # device.set_true_speed(device.get_speed_setpoint()) + class StoppedState(State): def in_state(self, dt): pass + def on_entry(self, dt): + self._context.drive = False + + class BrokenState(State): def in_state(self, dt): # Fail hard - This crashes the emulator (which is the realistic behaviour in this situation...) From 0bce36a024faaaf594c50adb55a341a51eae0f55 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 27 Jul 2017 15:34:24 +0100 Subject: [PATCH 0264/1466] Ramp instead of instant change --- lewis_emulators/fermichopper/states.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lewis_emulators/fermichopper/states.py b/lewis_emulators/fermichopper/states.py index 3005bd97..1842d4f2 100644 --- a/lewis_emulators/fermichopper/states.py +++ b/lewis_emulators/fermichopper/states.py @@ -18,7 +18,6 @@ def in_state(self, dt): rate += 50 device.set_true_speed(approaches.linear(device.get_true_speed(), 0, rate, dt)) - # device.set_true_speed(0) class GoingState(State): @@ -33,7 +32,6 @@ def in_state(self, dt): rate -= 1 device.set_true_speed(approaches.linear(device.get_true_speed(), device.get_speed_setpoint(), rate, dt)) - # device.set_true_speed(device.get_speed_setpoint()) class StoppedState(State): From cb473bb742d7b8c81971e8074465de04242e4190 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 27 Jul 2017 15:34:30 +0100 Subject: [PATCH 0265/1466] Add a command to reinitialize the emulator's state --- lewis_emulators/instron_stress_rig/device.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 242fb0df..5bff426c 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -57,6 +57,9 @@ def get_channel_param(self, index, param): def set_waveform_state(self, value): self._waveform_generator.state = value + def reset(self): + self._initialize_data() + def _get_initial_state(self): return 'default' From 11dc771c5cdeadd829b69c6083bc3b7d02ce7baa Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Thu, 27 Jul 2017 17:05:33 +0100 Subject: [PATCH 0266/1466] Set movement type when going state exits --- lewis_emulators/instron_stress_rig/states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/states.py b/lewis_emulators/instron_stress_rig/states.py index a4a8aeb6..44352ac1 100644 --- a/lewis_emulators/instron_stress_rig/states.py +++ b/lewis_emulators/instron_stress_rig/states.py @@ -22,5 +22,5 @@ def in_state(self, dt): def on_exit(self, dt): device = self._context - device._movement_type = 3 + device.movement_type = 0 From d26fa4a5c86983546be20aa9d9ae3eca059ce3d3 Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Fri, 28 Jul 2017 16:43:26 +0100 Subject: [PATCH 0267/1466] Placed complex functions into device.py and volt high/low, amplitude and offset now are affected by one another. --- lewis_emulators/ag33220a/device.py | 109 ++++++++++++++++-- .../ag33220a/interfaces/stream_interface.py | 80 ++++--------- 2 files changed, 126 insertions(+), 63 deletions(-) diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index eb4cd610..b59c9951 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -3,17 +3,110 @@ class SimulatedAG33220A(Device): make = "Agilent Technologies,33220A" - firmwareRevNum = "MY44033103,2.02" - bootKernelRevisionNumber = "2.02" - asicRevNum = "22" - printedCircuitBoardRevNum = "2" - idn = make+"-"+firmwareRevNum+"-"+bootKernelRevisionNumber+"-"+asicRevNum+"-"+printedCircuitBoardRevNum - voltage = 0.1 + firmware_rev_num = "MY44033103,2.02" + boot_kernel_revision_number = "2.02" + asic_rev_num = "22" + printed_circuit_board_rev_num = "2" + idn = make+"-"+firmware_rev_num+"-"+boot_kernel_revision_number+"-"+asic_rev_num+"-"+printed_circuit_board_rev_num + amplitude = 0.1 frequency = 1000 offset = 0 units = "VPP" function = "SIN" output = 0 - voltageHigh = 0.05 - voltageLow = -0.05 + voltage_high = 0.05 + voltage_low = -0.05 + amplitude_lower_bound = 0.01 + amplitude_upper_bound = 10 + offset_lower_bound = -4.995 + offset_upper_bound = 4.995 + voltage_lower_bound = -5 + voltage_upper_bound = 5 + voltage_low_lower_bound = -5 + voltage_low_upper_bound = 4.99 + voltage_high_lower_bound = -4.99 + voltage_high_upper_bound = 5 + voltage_precision = 0.01 + + + + + def limit_or_min_max(self, value, minimum, maximum): + try: + return self.limit(float(value), minimum, maximum) + except: + return {"MIN": minimum, "MAX": maximum}[value] + + # If the value is above or below the upper or lower bound + # then the upper or lower bound will be returned respectively + # otherwise the value is returned + def limit(self, value, lower_bound, upper_bound): + if value >= upper_bound: + return upper_bound + elif value < lower_bound: + return lower_bound + else: + return value + + def set_new_amplitude(self, new_amplitude): + new_amplitude = self.limit_or_min_max(new_amplitude, self.amplitude_lower_bound, self.amplitude_upper_bound) + if 0.5 * new_amplitude + self.offset > self.voltage_upper_bound or self.offset - 0.5 * new_amplitude < self.voltage_lower_bound: + if 0.5 * new_amplitude + self.offset > self.voltage_upper_bound: + offset_difference = 0.5 * new_amplitude + self.offset - self.voltage_upper_bound + elif self.offset - 0.5 * new_amplitude < self.voltage_lower_bound: + offset_difference = -0.5 * new_amplitude + self.offset + self.voltage_upper_bound + self.offset -= offset_difference + self.voltage_high -= offset_difference + self.voltage_low -= offset_difference + else: + self.update_volt_high_and_low(new_amplitude, self.offset) + self.amplitude = new_amplitude + + def set_new_frequency(self, new_frequency): + self.frequency = self.limit_or_min_max(new_frequency, self.frequency_lower_bound(), self.frequency_upper_bound()) + + def set_new_voltage_high(self,new_voltage_high): + new_voltage_high = self.limit(new_voltage_high, self.voltage_high_lower_bound, self.voltage_high_upper_bound) + if new_voltage_high <= self.voltage_low: + self.voltage_low = self.limit(new_voltage_high - self.voltage_precision, self.voltage_low_lower_bound, new_voltage_high) + self.update_volt_and_offs(self.voltage_low, new_voltage_high) + + def set_new_voltage_low(self,new_voltage_low): + new_voltage_low = self.limit(new_voltage_low, self.voltage_low_lower_bound, self.voltage_low_upper_bound) + if new_voltage_low >= self.voltage_high: + self.set_voltage_high(self.limit(new_voltage_low + self.voltage_precision, new_voltage_low, self.voltage_high_upper_bound)) + self.update_volt_and_offs(new_voltage_low, self.voltage_high) + + def update_volt_and_offs(self, new_low, new_high): + self.voltage_high = new_high + self.voltage_low = new_low + self.amplitude = self.voltage_high-self.voltage_low + self.offset = (self.voltage_high+self.voltage_low)/2 + + def set_offs_and_update_voltage(self, new_offset): + new_offset = self.limit_or_min_max(new_offset, self.offset_lower_bound, self.offset_upper_bound) + if new_offset+self.voltage_high > self.voltage_upper_bound: + self.amplitude = 2/(self.voltage_upper_bound-new_offset) + self.voltage_high = self.voltage_upper_bound + self.voltage_low = self.voltage_upper_bound - self.amplitude + elif new_offset+self.voltage_low < self.voltage_lower_bound: + self.amplitude = 2/(self.voltage_lower_bound-new_offset) + self.voltage_low = self.voltage_lower_bound + self.voltage_high = self.voltage_lower_bound + self.amplitude + else: + self.update_volt_high_and_low(self.amplitude, new_offset) + self.offset = new_offset + + def update_volt_high_and_low(self, new_volt, new_offs): + self.offset = new_offs + self.amplitude = new_volt + self.voltage_high = new_offs + new_volt / 2 + self.voltage_low = new_offs - new_volt / 2 + + def frequency_lower_bound(self): + return {"SIN": 10 ** -6, "SQU": 10 ** -6, "RAMP": 10 ** -6, "PULS": 5 * 10 ** -4, "NOIS": 1 * 10 ** -6, "USER": 10 ** -6}[self.function] + + def frequency_upper_bound(self): + return {"SIN": 2 * 10 ** 7, "SQU": 2 * 10 ** 7, "RAMP": 2 * 10 ** 5, "PULS": 5 * 10 ** 6, "NOIS": 2 * 10 ** 7,"USER": 6 * 10 ** 6}[self.function] + pass diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index 90f0d189..7e00c204 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -1,15 +1,16 @@ from lewis.adapters.stream import StreamAdapter, Cmd +import traceback class AG33220AStreamInterface(StreamAdapter): commands = { - Cmd("get_voltage", "^VOLT\?$"), - Cmd("set_voltage", "^VOLT " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), - Cmd("get_freq", "^FREQ\?$"), - Cmd("set_freq", "^FREQ " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("get_amplitude", "^VOLT\?$"), + Cmd("set_amplitude", "^VOLT " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("get_frequency", "^FREQ\?$"), + Cmd("set_frequency", "^FREQ " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), Cmd("get_offset", "^VOLT:OFFS\?$"), - Cmd("set_offset", "^VOLT:OFFS " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_offset", "^VOLT:OFFS " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), Cmd("get_units", "^VOLT:UNIT\?$"), Cmd("set_units", "^VOLT:UNIT " + "(VPP|VRMS|DBM)$", argument_mappings=[str]), Cmd("get_function", "^FUNC\?$"), @@ -18,55 +19,37 @@ class AG33220AStreamInterface(StreamAdapter): Cmd("set_output", "^OUTP " + "(0|1|ON|OFF)$", argument_mappings=[str]), Cmd("get_idn", "^\*IDN\?$"), Cmd("get_voltage_high", "^VOLT:HIGH\?$"), - Cmd("set_voltage_high", "^VOLT:HIGH" + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_voltage_high", "^VOLT:HIGH " + "([\-0-9.]+)$", argument_mappings=[float]), Cmd("get_voltage_low", "^VOLT:LOW\?$"), - Cmd("set_voltage_low", "^VOLT:LOW" + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_voltage_low", "^VOLT:LOW " + "([\-0-9.]+)$", argument_mappings=[float]), } in_terminator = "\n" # \r\n for putty out_terminator = "\n" - # Takes in a value and returns a value in the form of x.xxxxxxxxxxxxxEYY + + # Takes in a value and returns a value in the form of x.xxx0000000000Eyy def float_output(self, value): value = float('%s' % float('%.4g' % float(value))) return "{:+.13E}".format(value) - # If the value is above or below the upper or lower bound - # then the upper or lower bound will be returned respectively - # otherwise the value is returned - def limit(self,value,upper_bound,lower_bound): - if value >= upper_bound: - return upper_bound - elif value < lower_bound: - return lower_bound - else: - return value - - def get_voltage(self): - return self.float_output(self._device.voltage) - - def set_voltage(self, new_voltage): - try: - self._device.voltage = self.limit(float(new_voltage), 10, 0.01) - except: - self._device.voltage = {"MIN": 0.01, "MAX": 10}[new_voltage] + def get_amplitude(self): + return self.float_output(self._device.amplitude) - def get_freq(self): + def set_amplitude(self, new_amplitude): + self._device.set_new_amplitude(new_amplitude) + + def get_frequency(self): return self.float_output(self._device.frequency) - def set_freq(self, new_frequency): - lower_bound = {"SIN": 10**-6, "SQU": 10**-6, "RAMP": 10**-6, "PULS": 5*10**-4, "NOIS": 1*10**-6, "USER": 10**-6}[self._device.function] - upper_bound = {"SIN": 2*10**7, "SQU": 2*10**7, "RAMP": 2*10**5, "PULS": 5*10**6, "NOIS": 2*10**7, "USER": 6*10**6}[self._device.function] - try: - self._device.frequency = self.limit(float(new_frequency), upper_bound, lower_bound) - except: - self._device.frequency = {"MIN": lower_bound, "MAX": upper_bound}[new_frequency] + def set_frequency(self, new_frequency): + self._device.set_new_frequency(new_frequency) def get_offset(self): return self.float_output(self._device.offset) def set_offset(self, new_offset): - self._device.offset = new_offset + self._device.set_offs_and_update_voltage(new_offset) def get_units(self): return self._device.units @@ -78,10 +61,8 @@ def get_function(self): return self._device.function def set_function(self, new_function): - print(new_function,"\n",self._device.function) self._device.function = new_function - self.set_freq(self._device.frequency) - print(new_function, "\n\n", self._device.function) + self.set_frequency(self._device.frequency) def get_output(self): return self._device.output @@ -97,27 +78,16 @@ def get_idn(self): return self._device.idn def get_voltage_high(self): - return self.float_output(self._device.voltageHigh) + return self.float_output(self._device.voltage_high) def set_voltage_high(self, new_voltage_high): - self._device.voltageHigh = new_voltage_high + self._device.set_new_voltage_high(new_voltage_high) def get_voltage_low(self): - return self.float_output(self._device.voltageLow) + return self.float_output(self._device.voltage_low) def set_voltage_low(self, new_voltage_low): - self._device.voltageLow = new_voltage_low + self._device.set_new_voltage_low(new_voltage_low) def handle_error(self, request, error): - print(str(error)) - -################### -# def get_reading(self): -# rand = random.random() * 100.0 -# min_width = 10 -# data_str = "READ:" + "{:0.3f}".format(rand).ljust(min_width) + ";" + str(self._device.setpoint_mode) -# return self.create_response(READING_COMM + " ", data=data_str) -# -# def handle_error(self, request, error): -# print "An error occurred at request " + repr(request) + ": " + repr(error) -#################### + print traceback.format_exc() From 0b69fd9d8ef6ec5d6349ad38229c9c36b0ee2488 Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Mon, 31 Jul 2017 10:21:57 +0100 Subject: [PATCH 0268/1466] Added Javadocs and fixed issues with setting voltage lower above voltage higher --- lewis_emulators/ag33220a/device.py | 97 +++++++++++++++---- .../ag33220a/interfaces/stream_interface.py | 4 +- 2 files changed, 81 insertions(+), 20 deletions(-) diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index b59c9951..cba092af 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -2,6 +2,9 @@ class SimulatedAG33220A(Device): + """ + Simulated AG33220A + """ make = "Agilent Technologies,33220A" firmware_rev_num = "MY44033103,2.02" boot_kernel_revision_number = "2.02" @@ -28,28 +31,36 @@ class SimulatedAG33220A(Device): voltage_high_upper_bound = 5 voltage_precision = 0.01 + def limit(self, value, minimum, maximum): + """ + Limits an input number between two given numbers + or sets the value to the maximum or minimum. + :param value: the value to be limited + :param minimum: the smallest that the value can be + :param maximum: the largest that the value can be - - def limit_or_min_max(self, value, minimum, maximum): + :return: the value after it has been limited + """ try: - return self.limit(float(value), minimum, maximum) + value = float(value) + if value >= maximum: + return maximum + elif value < minimum: + return minimum + else: + return value except: return {"MIN": minimum, "MAX": maximum}[value] - # If the value is above or below the upper or lower bound - # then the upper or lower bound will be returned respectively - # otherwise the value is returned - def limit(self, value, lower_bound, upper_bound): - if value >= upper_bound: - return upper_bound - elif value < lower_bound: - return lower_bound - else: - return value - def set_new_amplitude(self, new_amplitude): - new_amplitude = self.limit_or_min_max(new_amplitude, self.amplitude_lower_bound, self.amplitude_upper_bound) + """ + Changing the amplitude to the new amplitude whilst also changing + voltage high, voltage low, and offset if voltage high or low is outside the boundary. + + :param new_amplitude; the amplitude to set the devices amplitude to + """ + new_amplitude = self.limit(new_amplitude, self.amplitude_lower_bound, self.amplitude_upper_bound) if 0.5 * new_amplitude + self.offset > self.voltage_upper_bound or self.offset - 0.5 * new_amplitude < self.voltage_lower_bound: if 0.5 * new_amplitude + self.offset > self.voltage_upper_bound: offset_difference = 0.5 * new_amplitude + self.offset - self.voltage_upper_bound @@ -63,28 +74,61 @@ def set_new_amplitude(self, new_amplitude): self.amplitude = new_amplitude def set_new_frequency(self, new_frequency): - self.frequency = self.limit_or_min_max(new_frequency, self.frequency_lower_bound(), self.frequency_upper_bound()) + """ + Sets the frequency within limits between upper and lower bound + which are different for each function. + + :param new_frequency: the frequency which wants to be set + """ + self.frequency = self.limit(new_frequency, self.frequency_lower_bound(), self.frequency_upper_bound()) def set_new_voltage_high(self,new_voltage_high): + """ + Sets a new voltage high which then changes the voltage low if voltage high + is set to a value lower than the voltage low. + The voltage offset and amplitude are then updated. + + :param new_voltage_high: the value of voltage high which is to be set + """ new_voltage_high = self.limit(new_voltage_high, self.voltage_high_lower_bound, self.voltage_high_upper_bound) if new_voltage_high <= self.voltage_low: self.voltage_low = self.limit(new_voltage_high - self.voltage_precision, self.voltage_low_lower_bound, new_voltage_high) self.update_volt_and_offs(self.voltage_low, new_voltage_high) def set_new_voltage_low(self,new_voltage_low): + """ + Sets a new voltage low which then changes the voltage high if voltage low + is set to a value higher than the voltage high. + The voltage offset and amplitude are then updated. + + :param new_voltage_low: the value of voltage low which is to be set + """ new_voltage_low = self.limit(new_voltage_low, self.voltage_low_lower_bound, self.voltage_low_upper_bound) if new_voltage_low >= self.voltage_high: - self.set_voltage_high(self.limit(new_voltage_low + self.voltage_precision, new_voltage_low, self.voltage_high_upper_bound)) + self.voltage_high = self.limit(new_voltage_low + self.voltage_precision, new_voltage_low, self.voltage_high_upper_bound) self.update_volt_and_offs(new_voltage_low, self.voltage_high) def update_volt_and_offs(self, new_low, new_high): + """ + Updates the value of amplitude and offset if there is a change in + voltage low or voltage high. + + :param new_low: the value of voltage low + :param new_high: the value of voltage high + """ self.voltage_high = new_high self.voltage_low = new_low self.amplitude = self.voltage_high-self.voltage_low self.offset = (self.voltage_high+self.voltage_low)/2 def set_offs_and_update_voltage(self, new_offset): - new_offset = self.limit_or_min_max(new_offset, self.offset_lower_bound, self.offset_upper_bound) + """ + Sets the value of offset and updates the amplitude, voltage low and + voltage high for a new value of the offset. + + :param new_offset: the new offset to be set + """ + new_offset = self.limit(new_offset, self.offset_lower_bound, self.offset_upper_bound) if new_offset+self.voltage_high > self.voltage_upper_bound: self.amplitude = 2/(self.voltage_upper_bound-new_offset) self.voltage_high = self.voltage_upper_bound @@ -98,15 +142,32 @@ def set_offs_and_update_voltage(self, new_offset): self.offset = new_offset def update_volt_high_and_low(self, new_volt, new_offs): + """ + Updates the value of voltage high and low for a given value of + amplitude and offset. + + :param new_volt: the value of the amplitude + :param new_offs: the value of the offset + """ self.offset = new_offs self.amplitude = new_volt self.voltage_high = new_offs + new_volt / 2 self.voltage_low = new_offs - new_volt / 2 def frequency_lower_bound(self): + """ + Returning the appropriate lower bound for the frequency for a given function. + + :return: lower bound for the frequency for a given function + """ return {"SIN": 10 ** -6, "SQU": 10 ** -6, "RAMP": 10 ** -6, "PULS": 5 * 10 ** -4, "NOIS": 1 * 10 ** -6, "USER": 10 ** -6}[self.function] def frequency_upper_bound(self): + """ + Returning the appropriate upper bound for the frequency for a given function. + + :return: upper bound for the frequency for a given function + """ return {"SIN": 2 * 10 ** 7, "SQU": 2 * 10 ** 7, "RAMP": 2 * 10 ** 5, "PULS": 5 * 10 ** 6, "NOIS": 2 * 10 ** 7,"USER": 6 * 10 ** 6}[self.function] pass diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index 7e00c204..b7d1b4d2 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -19,9 +19,9 @@ class AG33220AStreamInterface(StreamAdapter): Cmd("set_output", "^OUTP " + "(0|1|ON|OFF)$", argument_mappings=[str]), Cmd("get_idn", "^\*IDN\?$"), Cmd("get_voltage_high", "^VOLT:HIGH\?$"), - Cmd("set_voltage_high", "^VOLT:HIGH " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_voltage_high", "^VOLT:HIGH " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), Cmd("get_voltage_low", "^VOLT:LOW\?$"), - Cmd("set_voltage_low", "^VOLT:LOW " + "([\-0-9.]+)$", argument_mappings=[float]), + Cmd("set_voltage_low", "^VOLT:LOW " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), } in_terminator = "\n" # \r\n for putty From ba7e451df6bce0c05ee8e00f45567e1b22b687c9 Mon Sep 17 00:00:00 2001 From: Jack Draper Date: Mon, 31 Jul 2017 10:50:29 +0100 Subject: [PATCH 0269/1466] Added DC to functions --- lewis_emulators/ag33220a/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index cba092af..5ea51b6e 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -160,7 +160,7 @@ def frequency_lower_bound(self): :return: lower bound for the frequency for a given function """ - return {"SIN": 10 ** -6, "SQU": 10 ** -6, "RAMP": 10 ** -6, "PULS": 5 * 10 ** -4, "NOIS": 1 * 10 ** -6, "USER": 10 ** -6}[self.function] + return {"SIN": 10 ** -6, "SQU": 10 ** -6, "RAMP": 10 ** -6, "PULS": 5 * 10 ** -4, "NOIS": 10 ** -6, "USER": 10 ** -6}[self.function] def frequency_upper_bound(self): """ From 66712a2565af3fd02ef2106e0164a9cb7fb6593e Mon Sep 17 00:00:00 2001 From: Southren Date: Tue, 1 Aug 2017 16:46:16 +0100 Subject: [PATCH 0270/1466] Eilidh's emulator files for the TDK Genesys PSU. --- .gitignore | 4 ++ lewis_emulators/TDKLambdaGenesys/__init__.py | 3 + lewis_emulators/TDKLambdaGenesys/device.py | 72 +++++++++++++++++++ .../TDKLambdaGenesys/interfaces/__init__.py | 4 ++ .../interfaces/stream_interface.py | 63 ++++++++++++++++ lewis_emulators/TDKLambdaGenesys/states.py | 6 ++ 6 files changed, 152 insertions(+) create mode 100644 lewis_emulators/TDKLambdaGenesys/__init__.py create mode 100644 lewis_emulators/TDKLambdaGenesys/device.py create mode 100644 lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py create mode 100644 lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py create mode 100644 lewis_emulators/TDKLambdaGenesys/states.py diff --git a/.gitignore b/.gitignore index 700c2cc0..030f2a84 100644 --- a/.gitignore +++ b/.gitignore @@ -92,3 +92,7 @@ ENV/ *.pid *.port .project + + +#pycharm +.idea/ diff --git a/lewis_emulators/TDKLambdaGenesys/__init__.py b/lewis_emulators/TDKLambdaGenesys/__init__.py new file mode 100644 index 00000000..8f67f7e7 --- /dev/null +++ b/lewis_emulators/TDKLambdaGenesys/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedTDKLambdaGenesys + +__all__ = ['SimulatedTDKLambdaGenesys'] \ No newline at end of file diff --git a/lewis_emulators/TDKLambdaGenesys/device.py b/lewis_emulators/TDKLambdaGenesys/device.py new file mode 100644 index 00000000..f8711114 --- /dev/null +++ b/lewis_emulators/TDKLambdaGenesys/device.py @@ -0,0 +1,72 @@ +from lewis.devices import StateMachineDevice +from collections import OrderedDict +from .states import DefaultState + +class SimulatedTDKLambdaGenesys(StateMachineDevice): + + def _initialize_data(self): + + self._voltage = 10.0 + self._current = 2.0 + self._setpoint_voltage = 10.0 + self._setpoint_current = 2.0 + self._powerstate = "Off" + + + def _get_state_handlers(self): + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + return DefaultState.NAME + + def _get_transition_handlers(self): + return OrderedDict() + + @property + # return current actual voltage + def voltage(self): + return self._voltage + + @property + # return the set voltage + def setpoint_voltage(self): + return self._setpoint_voltage + + @property + def current(self): + return self._current + + @property + def setpoint_current(self): + return self._setpoint_current + + @property + def powerstate(self): + return self._powerstate + + # setters + @voltage.setter + def voltage(self, voltage): + self._voltage = voltage + + @setpoint_voltage.setter + def setpoint_voltage(self, spv): + self._setpoint_voltage = spv + + @current.setter + def current(self, c): + self._current = c + + @setpoint_current.setter + def setpoint_current(self, c): + self._setpoint_current = c + + @powerstate.setter + def powerstate(self, p): + self._powerstate = p + + + + + + diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py b/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py new file mode 100644 index 00000000..384c8100 --- /dev/null +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py @@ -0,0 +1,4 @@ +from .stream_interface import TDKLambdaGenesysStreamInterface + + +__all__ = ['TDKLambdaGenesysStreamInterface'] \ No newline at end of file diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py new file mode 100644 index 00000000..20b0f0b1 --- /dev/null +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py @@ -0,0 +1,63 @@ +from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.core.logging import has_log +from utils.command_builder import CmdBuilder + +class TDKLambdaGenesysStreamInterface(StreamAdapter): + + commands = { + + CmdBuilder("write_voltage").escape("VOLT ").float().build(), + CmdBuilder("read_setpoint_voltage").escape("PV?").build(), + CmdBuilder("read_voltage").escape("MV?").build(), + + CmdBuilder("write_current").escape("PC").float().build(), + CmdBuilder("read_setpoint_current").escape("PC?").build(), + CmdBuilder("read_current").escape("MC?").build(), + + #CmdBuilder("remote").escape("RMT 1").build(), + #CmdBuilder("reset").escape("RST").build(), + #CmdBuilder("write_powerstate").escape("OFF|ON").build(), + #CmdBuilder("write_powerstate").escape("OUT?"), + #CmdBuilder("write_address").escape("ADR").float().build(), + } + + in_terminator = "\r" + out_terminator = "\r" + + def handle_error(self,request, error): + self.log.error("Beep boop. Error occurred at " + repr(request) + ": " + repr(error)) + print("Beep boop. Error occurred at " + repr(request) + ": " + repr(error)) + + def read_voltage(self): + return self._device.voltage + + def read_setpoint_voltage(self): + return self._device.setpoint_voltage + + def write_voltage(self, v): + self._device.setpoint_voltage = v + return "VOLTAGE SET TO: " + v + + def read_current(self): + return self._device.current + + def read_setpoint_current(self): + return self._device.setpoint_current + + def write_current(self, c): + self._device.setpoint_current = c + return "VOLTAGE SET TO: " + c + + def read_powerstate(self): + return self._device.power_state + + def write_powerstate(self, p): + self._device.power_state = p + return "POWER SET TO " + p + + + + + + + diff --git a/lewis_emulators/TDKLambdaGenesys/states.py b/lewis_emulators/TDKLambdaGenesys/states.py new file mode 100644 index 00000000..10887c12 --- /dev/null +++ b/lewis_emulators/TDKLambdaGenesys/states.py @@ -0,0 +1,6 @@ +from lewis.core.statemachine import State + +class DefaultState(State): + + # default state + NAME = 'Default' From 8392e3687b832963359fafd0971766a0e69b4d14 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 1 Aug 2017 17:06:22 +0100 Subject: [PATCH 0271/1466] Add skeletal cybaman emulator --- lewis_emulators/cybaman/__init__.py | 3 ++ lewis_emulators/cybaman/device.py | 36 +++++++++++++++++++ .../cybaman/interfaces/__init__.py | 3 ++ .../cybaman/interfaces/stream_interface.py | 24 +++++++++++++ lewis_emulators/cybaman/states.py | 8 +++++ 5 files changed, 74 insertions(+) create mode 100644 lewis_emulators/cybaman/__init__.py create mode 100644 lewis_emulators/cybaman/device.py create mode 100644 lewis_emulators/cybaman/interfaces/__init__.py create mode 100644 lewis_emulators/cybaman/interfaces/stream_interface.py create mode 100644 lewis_emulators/cybaman/states.py diff --git a/lewis_emulators/cybaman/__init__.py b/lewis_emulators/cybaman/__init__.py new file mode 100644 index 00000000..5116ed8f --- /dev/null +++ b/lewis_emulators/cybaman/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedCybaman + +__all__ = ['SimulatedCybaman'] diff --git a/lewis_emulators/cybaman/device.py b/lewis_emulators/cybaman/device.py new file mode 100644 index 00000000..b53d2e69 --- /dev/null +++ b/lewis_emulators/cybaman/device.py @@ -0,0 +1,36 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedCybaman(StateMachineDevice): + """ + Simulated cyber man. + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + pass + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() + + diff --git a/lewis_emulators/cybaman/interfaces/__init__.py b/lewis_emulators/cybaman/interfaces/__init__.py new file mode 100644 index 00000000..d0d53af0 --- /dev/null +++ b/lewis_emulators/cybaman/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import CybamanStreamInterface + +__all__ = ['CybamanStreamInterface'] diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py new file mode 100644 index 00000000..d0d4bc44 --- /dev/null +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -0,0 +1,24 @@ +from lewis.adapters.stream import StreamAdapter +from lewis_emulators.utils.command_builder import CmdBuilder + + +class CybamanStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + """ + If command is not recognised print and error. + + :param request: requested string + :param error: problem + :return: + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) diff --git a/lewis_emulators/cybaman/states.py b/lewis_emulators/cybaman/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/cybaman/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' From e79036d67953e058426625d2201d0e8cd2f0fd78 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 1 Aug 2017 17:20:40 +0100 Subject: [PATCH 0272/1466] Connect emulator to labview (use correct terminator) --- lewis_emulators/cybaman/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index d0d4bc44..13994f86 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -10,8 +10,8 @@ class CybamanStreamInterface(StreamAdapter): commands = { } - in_terminator = "\r\n" - out_terminator = "\r\n" + in_terminator = "\r" + out_terminator = "" def handle_error(self, request, error): """ From c8f11f0b34745d1874763d39a5d1738e37a31b5a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 2 Aug 2017 11:23:12 +0100 Subject: [PATCH 0273/1466] Use correct terminator (ACK) --- .../cybaman/interfaces/stream_interface.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index 13994f86..ab374636 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -1,5 +1,4 @@ -from lewis.adapters.stream import StreamAdapter -from lewis_emulators.utils.command_builder import CmdBuilder +from lewis.adapters.stream import StreamAdapter, Cmd class CybamanStreamInterface(StreamAdapter): @@ -7,11 +6,14 @@ class CybamanStreamInterface(StreamAdapter): Stream interface for the serial port """ + ACK = chr(0x06) # ACK character + commands = { + Cmd("initialize", "^A$"), } in_terminator = "\r" - out_terminator = "" + out_terminator = ACK def handle_error(self, request, error): """ @@ -22,3 +24,7 @@ def handle_error(self, request, error): :return: """ print "An error occurred at request " + repr(request) + ": " + repr(error) + + def initialize(self): + print "Initializing..." + return "" From 539b4df9544204f997e69901d535ef15ffda65ea Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 2 Aug 2017 14:47:52 +0100 Subject: [PATCH 0274/1466] Add home, reset functions --- lewis_emulators/cybaman/device.py | 18 +++++- .../cybaman/interfaces/stream_interface.py | 62 +++++++++++++++++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/cybaman/device.py b/lewis_emulators/cybaman/device.py index b53d2e69..b9a9d98b 100644 --- a/lewis_emulators/cybaman/device.py +++ b/lewis_emulators/cybaman/device.py @@ -13,7 +13,13 @@ def _initialize_data(self): """ Sets the initial state of the device. """ - pass + self.a = 0 + self.b = 0 + self.c = 0 + + self.home_position_axis_a = 66 + self.home_position_axis_b = 77 + self.home_position_axis_c = 88 def _get_state_handlers(self): """ @@ -33,4 +39,14 @@ def _get_transition_handlers(self): """ return OrderedDict() + def home_axis_a(self): + self.a = self.home_position_axis_a + + def home_axis_b(self): + self.b = self.home_position_axis_b + + def home_axis_c(self): + self.c = self.home_position_axis_c + + diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index ab374636..2a6f17f3 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -1,4 +1,5 @@ from lewis.adapters.stream import StreamAdapter, Cmd +from time import sleep class CybamanStreamInterface(StreamAdapter): @@ -6,14 +7,22 @@ class CybamanStreamInterface(StreamAdapter): Stream interface for the serial port """ - ACK = chr(0x06) # ACK character - commands = { Cmd("initialize", "^A$"), + Cmd("get_a", "^M101$"), + Cmd("get_b", "^M201$"), + Cmd("get_c", "^M301$"), + Cmd("set_all", "^OPEN PROG 10 CLEAR\nG1 A ([0-9]*\.?[0-9]*) B ([0-9]*\.?[0-9]*) C ([0-9]*\.?[0-9]*) TM([0-9]*)$"), + Cmd("ignore", "^CLOSE$"), + Cmd("ignore", "^B10R$"), + Cmd("reset", "^\$\$\$$"), + Cmd("home_a", "^B9001R$"), + Cmd("home_b", "^B9002R$"), + Cmd("home_c", "^B9003R$"), } in_terminator = "\r" - out_terminator = ACK + out_terminator = chr(0x06) # ACK character def handle_error(self, request, error): """ @@ -25,6 +34,51 @@ def handle_error(self, request, error): """ print "An error occurred at request " + repr(request) + ": " + repr(error) + def wait_before_response(self): + sleep(0.1) + + def ignore(self): + self.wait_before_response() + return "" + def initialize(self): - print "Initializing..." + self.wait_before_response() + return "" + + def get_a(self): + self.wait_before_response() + print "Returning input A as {}".format(self._device.a) + return self._device.a*3577 + + def get_b(self): + self.wait_before_response() + print "Returning input B as {}".format(self._device.b) + return self._device.b*3663 + + def get_c(self): + self.wait_before_response() + print "Returning input C as {}".format(self._device.c) + return self._device.c*3663 + + def set_all(self, a, b, c, tm): + self.wait_before_response() + self._device.a = float(a) + self._device.b = float(b) + self._device.c = float(c) + return "" + + def reset(self): + self._device._initialize_data() + return "" + + def home_a(self): + self._device.home_axis_a() + return "" + + def home_b(self): + self._device.home_axis_b() + return "" + + def home_c(self): + self._device.home_axis_c() return "" From 23864836ae5a5e93a9302e95e410b64bff9b84b6 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:27:50 +0100 Subject: [PATCH 0275/1466] Update __init__.py Added blank line at end of file --- lewis_emulators/TDKLambdaGenesys/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/TDKLambdaGenesys/__init__.py b/lewis_emulators/TDKLambdaGenesys/__init__.py index 8f67f7e7..be04b16e 100644 --- a/lewis_emulators/TDKLambdaGenesys/__init__.py +++ b/lewis_emulators/TDKLambdaGenesys/__init__.py @@ -1,3 +1,3 @@ from .device import SimulatedTDKLambdaGenesys -__all__ = ['SimulatedTDKLambdaGenesys'] \ No newline at end of file +__all__ = ['SimulatedTDKLambdaGenesys'] From bd2bc84d64d090030cefdd33c2a593714f484614 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:29:22 +0100 Subject: [PATCH 0276/1466] Update device.py Formatted line spacing --- lewis_emulators/TDKLambdaGenesys/device.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lewis_emulators/TDKLambdaGenesys/device.py b/lewis_emulators/TDKLambdaGenesys/device.py index f8711114..77df748d 100644 --- a/lewis_emulators/TDKLambdaGenesys/device.py +++ b/lewis_emulators/TDKLambdaGenesys/device.py @@ -2,6 +2,7 @@ from collections import OrderedDict from .states import DefaultState + class SimulatedTDKLambdaGenesys(StateMachineDevice): def _initialize_data(self): @@ -12,7 +13,6 @@ def _initialize_data(self): self._setpoint_current = 2.0 self._powerstate = "Off" - def _get_state_handlers(self): return {DefaultState.NAME: DefaultState()} @@ -64,9 +64,3 @@ def setpoint_current(self, c): @powerstate.setter def powerstate(self, p): self._powerstate = p - - - - - - From dbe77023d8851592129a21ec05eecf877f7a4493 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:31:29 +0100 Subject: [PATCH 0277/1466] Update device.py Implemented formatting changes --- lewis_emulators/TDKLambdaGenesys/device.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/TDKLambdaGenesys/device.py b/lewis_emulators/TDKLambdaGenesys/device.py index 77df748d..ac16c8ff 100644 --- a/lewis_emulators/TDKLambdaGenesys/device.py +++ b/lewis_emulators/TDKLambdaGenesys/device.py @@ -6,7 +6,6 @@ class SimulatedTDKLambdaGenesys(StateMachineDevice): def _initialize_data(self): - self._voltage = 10.0 self._current = 2.0 self._setpoint_voltage = 10.0 @@ -22,13 +21,12 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict() - @property - # return current actual voltage + @property def voltage(self): + # return current actual voltage return self._voltage - @property - # return the set voltage + @property def setpoint_voltage(self): return self._setpoint_voltage From a5ba5cdfba09033db5f8e80b4917078b2bdaa9ea Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:32:57 +0100 Subject: [PATCH 0278/1466] Update __init__.py Added blank line at end of file --- lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py b/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py index 384c8100..70924a98 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py @@ -1,4 +1,4 @@ from .stream_interface import TDKLambdaGenesysStreamInterface -__all__ = ['TDKLambdaGenesysStreamInterface'] \ No newline at end of file +__all__ = ['TDKLambdaGenesysStreamInterface'] From adbc4c6bb22dde32033b21450afd81f405167ea7 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:33:23 +0100 Subject: [PATCH 0279/1466] Update stream_interface.py Line formatting --- .../TDKLambdaGenesys/interfaces/stream_interface.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py index 20b0f0b1..56bef727 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py @@ -54,10 +54,3 @@ def read_powerstate(self): def write_powerstate(self, p): self._device.power_state = p return "POWER SET TO " + p - - - - - - - From 796526ef795d1f9271efec9dbd30f62f50c5ad54 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:33:46 +0100 Subject: [PATCH 0280/1466] Update __init__.py --- lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py b/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py index 70924a98..a575dcdc 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py @@ -1,4 +1,3 @@ from .stream_interface import TDKLambdaGenesysStreamInterface - __all__ = ['TDKLambdaGenesysStreamInterface'] From 781d3130b54aa8b022ee9405ea1b8de67eb3c335 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 2 Aug 2017 15:35:21 +0100 Subject: [PATCH 0281/1466] Update stream_interface.py Line formatting --- .../TDKLambdaGenesys/interfaces/stream_interface.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py index 56bef727..2fcf5761 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py @@ -1,24 +1,16 @@ from lewis.adapters.stream import StreamAdapter, Cmd from lewis.core.logging import has_log -from utils.command_builder import CmdBuilder +frin utils.command_builder import CmdBuilder class TDKLambdaGenesysStreamInterface(StreamAdapter): commands = { - CmdBuilder("write_voltage").escape("VOLT ").float().build(), CmdBuilder("read_setpoint_voltage").escape("PV?").build(), CmdBuilder("read_voltage").escape("MV?").build(), - CmdBuilder("write_current").escape("PC").float().build(), CmdBuilder("read_setpoint_current").escape("PC?").build(), CmdBuilder("read_current").escape("MC?").build(), - - #CmdBuilder("remote").escape("RMT 1").build(), - #CmdBuilder("reset").escape("RST").build(), - #CmdBuilder("write_powerstate").escape("OFF|ON").build(), - #CmdBuilder("write_powerstate").escape("OUT?"), - #CmdBuilder("write_address").escape("ADR").float().build(), } in_terminator = "\r" From 7e85dff1294b294eb24c22838cf57b2fcfe649d2 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 2 Aug 2017 15:48:30 +0100 Subject: [PATCH 0282/1466] Update emulator --- .../cybaman/interfaces/stream_interface.py | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index 2a6f17f3..409413c4 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -35,7 +35,7 @@ def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) def wait_before_response(self): - sleep(0.1) + sleep(0) def ignore(self): self.wait_before_response() @@ -62,23 +62,41 @@ def get_c(self): def set_all(self, a, b, c, tm): self.wait_before_response() + + self._verify_tm(a, b, c, tm) + self._device.a = float(a) self._device.b = float(b) self._device.c = float(c) return "" + def _verify_tm(self, a, b, c, tm): + tm = int(tm) + old_position = (self._device.a, self._device.b, self._device.c) + new_position = (float(a), float(b), float(c)) + + max_difference = max([abs(a-b) for a, b in zip(old_position, new_position)]) + expected_tm = max([int(round(max_difference * 1000)), 4000]) + + if (tm - expected_tm) > 1: # Allow a difference of 1 for rounding errors. + assert False, "Wrong TM value! Expected {} but got {}".format(expected_tm, tm) + def reset(self): + self.wait_before_response() self._device._initialize_data() return "" def home_a(self): + self.wait_before_response() self._device.home_axis_a() return "" def home_b(self): + self.wait_before_response() self._device.home_axis_b() return "" def home_c(self): + self.wait_before_response() self._device.home_axis_c() return "" From 2c3008b7682f5c17d65c2471f29184d646b17db8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 2 Aug 2017 15:50:51 +0100 Subject: [PATCH 0283/1466] Remove labview debug code --- .../cybaman/interfaces/stream_interface.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index 409413c4..f0fcc417 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -34,35 +34,25 @@ def handle_error(self, request, error): """ print "An error occurred at request " + repr(request) + ": " + repr(error) - def wait_before_response(self): - sleep(0) - def ignore(self): - self.wait_before_response() return "" def initialize(self): - self.wait_before_response() return "" def get_a(self): - self.wait_before_response() print "Returning input A as {}".format(self._device.a) return self._device.a*3577 def get_b(self): - self.wait_before_response() print "Returning input B as {}".format(self._device.b) return self._device.b*3663 def get_c(self): - self.wait_before_response() print "Returning input C as {}".format(self._device.c) return self._device.c*3663 def set_all(self, a, b, c, tm): - self.wait_before_response() - self._verify_tm(a, b, c, tm) self._device.a = float(a) @@ -82,21 +72,17 @@ def _verify_tm(self, a, b, c, tm): assert False, "Wrong TM value! Expected {} but got {}".format(expected_tm, tm) def reset(self): - self.wait_before_response() self._device._initialize_data() return "" def home_a(self): - self.wait_before_response() self._device.home_axis_a() return "" def home_b(self): - self.wait_before_response() self._device.home_axis_b() return "" def home_c(self): - self.wait_before_response() self._device.home_axis_c() return "" From 2c91f6e3866293619cd58f9aeb4c04520d799f60 Mon Sep 17 00:00:00 2001 From: Southren Date: Wed, 2 Aug 2017 17:08:10 +0100 Subject: [PATCH 0284/1466] Fixed bug in power_state, in CmdBuilder --- .../interfaces/stream_interface.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py index 2fcf5761..0e9710cf 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py @@ -1,16 +1,18 @@ from lewis.adapters.stream import StreamAdapter, Cmd from lewis.core.logging import has_log -frin utils.command_builder import CmdBuilder +from utils.command_builder import CmdBuilder class TDKLambdaGenesysStreamInterface(StreamAdapter): commands = { - CmdBuilder("write_voltage").escape("VOLT ").float().build(), + CmdBuilder("write_voltage").escape("PV ").float().build(), CmdBuilder("read_setpoint_voltage").escape("PV?").build(), CmdBuilder("read_voltage").escape("MV?").build(), CmdBuilder("write_current").escape("PC").float().build(), CmdBuilder("read_setpoint_current").escape("PC?").build(), CmdBuilder("read_current").escape("MC?").build(), + CmdBuilder("read_power").escape("OUT?").build(), + CmdBuilder("write_power").escape("OUT ").string().build(), } in_terminator = "\r" @@ -40,9 +42,11 @@ def write_current(self, c): self._device.setpoint_current = c return "VOLTAGE SET TO: " + c - def read_powerstate(self): - return self._device.power_state + def read_power(self): + return self._device.powerstate - def write_powerstate(self, p): - self._device.power_state = p + def write_power(self, p): + self._device.powerstate = p return "POWER SET TO " + p + + From b1696859f2661243eb1970a25fabbfc8bf63511d Mon Sep 17 00:00:00 2001 From: Southren Date: Thu, 3 Aug 2017 09:21:06 +0100 Subject: [PATCH 0285/1466] formatting errors fixed --- lewis_emulators/TDKLambdaGenesys/device.py | 2 -- .../TDKLambdaGenesys/interfaces/stream_interface.py | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/TDKLambdaGenesys/device.py b/lewis_emulators/TDKLambdaGenesys/device.py index ac16c8ff..321c4f6d 100644 --- a/lewis_emulators/TDKLambdaGenesys/device.py +++ b/lewis_emulators/TDKLambdaGenesys/device.py @@ -23,7 +23,6 @@ def _get_transition_handlers(self): @property def voltage(self): - # return current actual voltage return self._voltage @property @@ -42,7 +41,6 @@ def setpoint_current(self): def powerstate(self): return self._powerstate - # setters @voltage.setter def voltage(self, voltage): self._voltage = voltage diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py index 0e9710cf..66e23a59 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py +++ b/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py @@ -1,9 +1,9 @@ from lewis.adapters.stream import StreamAdapter, Cmd from lewis.core.logging import has_log -from utils.command_builder import CmdBuilder +from lewis_emulators.utils.command_builder import CmdBuilder -class TDKLambdaGenesysStreamInterface(StreamAdapter): +class TDKLambdaGenesysStreamInterface(StreamAdapter): commands = { CmdBuilder("write_voltage").escape("PV ").float().build(), CmdBuilder("read_setpoint_voltage").escape("PV?").build(), @@ -48,5 +48,3 @@ def read_power(self): def write_power(self, p): self._device.powerstate = p return "POWER SET TO " + p - - From d01c7bfc306291e3458d736eeb0a14d7a0f5ed11 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 3 Aug 2017 14:42:54 +0100 Subject: [PATCH 0286/1466] Add final eol to stream interface --- lewis_emulators/fermichopper/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 1f3d1ae9..c94cee91 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -136,4 +136,4 @@ def set_delay_highword(self, command, checksum): def set_gate_width(self, command, checksum): JulichChecksum.verify('#9', command, checksum) - self._device.set_gate_width(int(command, 16) / 50.4) \ No newline at end of file + self._device.set_gate_width(int(command, 16) / 50.4) From 0332bc1207a79dd57e6f05bc7af64776cf7a1e78 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 3 Aug 2017 15:41:12 +0100 Subject: [PATCH 0287/1466] Sorted out a few minor issues --- .../__init__.py | 2 +- .../device.py | 15 ++------- .../interfaces/__init__.py | 3 +- .../interfaces/stream_interface.py | 31 +++++++------------ .../states.py | 4 +-- 5 files changed, 19 insertions(+), 36 deletions(-) rename lewis_emulators/{TDKLambdaGenesys => tdk_lambda_genesys}/__init__.py (54%) rename lewis_emulators/{TDKLambdaGenesys => tdk_lambda_genesys}/device.py (94%) rename lewis_emulators/{TDKLambdaGenesys => tdk_lambda_genesys}/interfaces/__init__.py (57%) rename lewis_emulators/{TDKLambdaGenesys => tdk_lambda_genesys}/interfaces/stream_interface.py (70%) rename lewis_emulators/{TDKLambdaGenesys => tdk_lambda_genesys}/states.py (82%) diff --git a/lewis_emulators/TDKLambdaGenesys/__init__.py b/lewis_emulators/tdk_lambda_genesys/__init__.py similarity index 54% rename from lewis_emulators/TDKLambdaGenesys/__init__.py rename to lewis_emulators/tdk_lambda_genesys/__init__.py index 8f67f7e7..be04b16e 100644 --- a/lewis_emulators/TDKLambdaGenesys/__init__.py +++ b/lewis_emulators/tdk_lambda_genesys/__init__.py @@ -1,3 +1,3 @@ from .device import SimulatedTDKLambdaGenesys -__all__ = ['SimulatedTDKLambdaGenesys'] \ No newline at end of file +__all__ = ['SimulatedTDKLambdaGenesys'] diff --git a/lewis_emulators/TDKLambdaGenesys/device.py b/lewis_emulators/tdk_lambda_genesys/device.py similarity index 94% rename from lewis_emulators/TDKLambdaGenesys/device.py rename to lewis_emulators/tdk_lambda_genesys/device.py index f8711114..5b5e5565 100644 --- a/lewis_emulators/TDKLambdaGenesys/device.py +++ b/lewis_emulators/tdk_lambda_genesys/device.py @@ -2,17 +2,15 @@ from collections import OrderedDict from .states import DefaultState -class SimulatedTDKLambdaGenesys(StateMachineDevice): +class SimulatedTDKLambdaGenesys(StateMachineDevice): def _initialize_data(self): - self._voltage = 10.0 self._current = 2.0 self._setpoint_voltage = 10.0 self._setpoint_current = 2.0 self._powerstate = "Off" - def _get_state_handlers(self): return {DefaultState.NAME: DefaultState()} @@ -23,12 +21,12 @@ def _get_transition_handlers(self): return OrderedDict() @property - # return current actual voltage + # Return current actual voltage def voltage(self): return self._voltage @property - # return the set voltage + # Return the set voltage def setpoint_voltage(self): return self._setpoint_voltage @@ -44,7 +42,6 @@ def setpoint_current(self): def powerstate(self): return self._powerstate - # setters @voltage.setter def voltage(self, voltage): self._voltage = voltage @@ -64,9 +61,3 @@ def setpoint_current(self, c): @powerstate.setter def powerstate(self, p): self._powerstate = p - - - - - - diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py b/lewis_emulators/tdk_lambda_genesys/interfaces/__init__.py similarity index 57% rename from lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py rename to lewis_emulators/tdk_lambda_genesys/interfaces/__init__.py index 384c8100..a575dcdc 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/__init__.py +++ b/lewis_emulators/tdk_lambda_genesys/interfaces/__init__.py @@ -1,4 +1,3 @@ from .stream_interface import TDKLambdaGenesysStreamInterface - -__all__ = ['TDKLambdaGenesysStreamInterface'] \ No newline at end of file +__all__ = ['TDKLambdaGenesysStreamInterface'] diff --git a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py b/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py similarity index 70% rename from lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py rename to lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py index 20b0f0b1..b169a929 100644 --- a/lewis_emulators/TDKLambdaGenesys/interfaces/stream_interface.py +++ b/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py @@ -1,24 +1,20 @@ from lewis.adapters.stream import StreamAdapter, Cmd from lewis.core.logging import has_log -from utils.command_builder import CmdBuilder +from lewis_emulators.utils.command_builder import CmdBuilder + class TDKLambdaGenesysStreamInterface(StreamAdapter): commands = { - - CmdBuilder("write_voltage").escape("VOLT ").float().build(), + CmdBuilder("write_voltage").escape("PV ").float().build(), CmdBuilder("read_setpoint_voltage").escape("PV?").build(), CmdBuilder("read_voltage").escape("MV?").build(), - - CmdBuilder("write_current").escape("PC").float().build(), + CmdBuilder("write_current").escape("PC ").float().build(), CmdBuilder("read_setpoint_current").escape("PC?").build(), CmdBuilder("read_current").escape("MC?").build(), - - #CmdBuilder("remote").escape("RMT 1").build(), - #CmdBuilder("reset").escape("RST").build(), - #CmdBuilder("write_powerstate").escape("OFF|ON").build(), - #CmdBuilder("write_powerstate").escape("OUT?"), - #CmdBuilder("write_address").escape("ADR").float().build(), + CmdBuilder("remote").escape("RMT 1").build(), + CmdBuilder("write_powerstate").escape("OUT ").arg("[Off|On]").build(), + CmdBuilder("read_powerstate").escape("OUT?").build(), } in_terminator = "\r" @@ -49,15 +45,12 @@ def write_current(self, c): return "VOLTAGE SET TO: " + c def read_powerstate(self): - return self._device.power_state + return self._device.powerstate def write_powerstate(self, p): - self._device.power_state = p + self._device.powerstate = p return "POWER SET TO " + p - - - - - - + def remote(self): + # We can ignore this command + pass diff --git a/lewis_emulators/TDKLambdaGenesys/states.py b/lewis_emulators/tdk_lambda_genesys/states.py similarity index 82% rename from lewis_emulators/TDKLambdaGenesys/states.py rename to lewis_emulators/tdk_lambda_genesys/states.py index 10887c12..147c8f71 100644 --- a/lewis_emulators/TDKLambdaGenesys/states.py +++ b/lewis_emulators/tdk_lambda_genesys/states.py @@ -1,6 +1,6 @@ from lewis.core.statemachine import State -class DefaultState(State): - # default state +class DefaultState(State): + # Default state NAME = 'Default' From dea240ff2820f8d7728440fa1686fddf69a30fc4 Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Thu, 3 Aug 2017 15:43:30 +0100 Subject: [PATCH 0288/1466] Missed a comment or two --- lewis_emulators/tdk_lambda_genesys/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/tdk_lambda_genesys/device.py b/lewis_emulators/tdk_lambda_genesys/device.py index 5b5e5565..66926511 100644 --- a/lewis_emulators/tdk_lambda_genesys/device.py +++ b/lewis_emulators/tdk_lambda_genesys/device.py @@ -21,13 +21,13 @@ def _get_transition_handlers(self): return OrderedDict() @property - # Return current actual voltage def voltage(self): + # Return current actual voltage return self._voltage @property - # Return the set voltage def setpoint_voltage(self): + # Return the set voltage return self._setpoint_voltage @property From 2a84704aa29c1209c2d2418702b97593d91c4403 Mon Sep 17 00:00:00 2001 From: Kathryn Baker Date: Thu, 3 Aug 2017 17:35:08 +0100 Subject: [PATCH 0289/1466] Emulator for the RKIEN PSUs --- lewis_emulators/rknps/__init__.py | 3 + lewis_emulators/rknps/device.py | 224 ++++++++++++++++++ lewis_emulators/rknps/interfaces/__init__.py | 3 + .../rknps/interfaces/stream_interface.py | 154 ++++++++++++ lewis_emulators/rknps/states.py | 8 + 5 files changed, 392 insertions(+) create mode 100644 lewis_emulators/rknps/__init__.py create mode 100644 lewis_emulators/rknps/device.py create mode 100644 lewis_emulators/rknps/interfaces/__init__.py create mode 100644 lewis_emulators/rknps/interfaces/stream_interface.py create mode 100644 lewis_emulators/rknps/states.py diff --git a/lewis_emulators/rknps/__init__.py b/lewis_emulators/rknps/__init__.py new file mode 100644 index 00000000..d7f2221d --- /dev/null +++ b/lewis_emulators/rknps/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedRknps + +__all__ = ['SimulatedRknps'] diff --git a/lewis_emulators/rknps/device.py b/lewis_emulators/rknps/device.py new file mode 100644 index 00000000..c37338b2 --- /dev/null +++ b/lewis_emulators/rknps/device.py @@ -0,0 +1,224 @@ +from collections import OrderedDict + +from lewis.core.logging import has_log +from lewis.devices import StateMachineDevice +from .states import DefaultState + +@has_log +class SimulatedRknps(StateMachineDevice): + """ + Simulated Danfysik type controller used in multi-drop mode on RIKEN beamlines. + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self._active_adr = "001" + self._adr = {"001":"001","002":"002"} + self._ra = {"001":123456,"002":456789} + self._curr = {"001":123456,"002":456789} + self._volt = {"001":10,"002":20} + self._cmd = {"001":"REM","002":"LOC"} + self._pol = {"001":"+", "002":"-"} + self._status = { + "001":[".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".","."], + "002":["!",".",".",".",".",".",".",".",".","!",".",".",".",".",".",".",".",".",".",".",".",".",".","."]} + self._set_curr = {"001":123456,"002":456789} + + def _get_state_handlers(self): + """ + Returns: states and their names. + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state. + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions. + """ + return OrderedDict() + + @property + def adr(self): + """ + Gets the most recently assigned address. + + Returns: a string address. + """ + return self._adr[self._active_adr] + + @property + def ra(self): + """ + Gets the RA for the active address. + + Returns: unsigned absolute value of the current setpoint. + """ + return self._ra[self._active_adr] + + @property + def ad_curr(self): + """ + Gets an AD for the active address. + + Returns: value of the output current. + """ + return self._curr[self._active_adr] + + @property + def ad_volt(self): + """ + Gets an AD for the active address. + + Returns: value of the output voltage. + """ + return self._volt[self._active_adr] + + @property + def cmd(self): + """ + Gets the value of the local/remote status. + + Returns: the LOC/REM status of the device. + """ + return self._cmd[self._active_adr] + + @property + def pol(self): + """ + Gets the value of the polarity. + + Returns: the polarity of the device. + """ + return self._pol[self._active_adr] + + @property + def status(self): + """ + Gets the status. + + The status is a 24 character string, with . and ! relating to the value of the associated item. + In order to allow for easier alteration of the status, it is stored as a list and joined at this point. + + Returns: the status as a string. + """ + return ''.join(self._status[self._active_adr]) + + def set_power(self, power): + """ + Call the appropriate routine for on or off. + + Args: + power: turn the power oN or ofF. + """ + if power == "F": + self.off() + elif power == "N": + self.on() + + def set_interlock(self, ilk): + """ + Set whether the interlocks are active or not. + + This is a backdoor routine, and not accessed from the IOC. + + Args: + ilk: whether the interlocks should be active or inactive. + """ + if ilk == "active": + self._status[self._active_adr][9] = "!" + self.off() + elif ilk == "inactive": + self._status[self._active_adr][9] = "." + + def set_both_interlocks(self, ilk): + """ + Set whether the interlocks are active or not. + + This is a backdoor routine, and not accessed from the IOC. + This specific version is for use with the IOC test framework. + + Args: + ilk: whether the interlocks should be active or inactive. + """ + original_active_address = self._active_adr + self._active_adr = "001" + self.set_interlock(ilk) + self._active_adr = "002" + self.set_interlock(ilk) + self._active_adr = original_active_address + + def set_current(self, current): + """ + Update the values of the information relating to the current. + + The current is multiplied by a default factor of 1000, this is then divided out here as the readback default is + 1. + + Args: + current: The current to set + """ + current_to_use = current/1000 + self.set_current_values(current_to_use) + self._set_curr[self._active_adr] = current_to_use + + def reset(self): + """ + Reset the device to the standard off configuration. + """ + current_to_use = 0 + self.set_current_values(current_to_use) + self.off() + + def on(self): + """ + Turn the device on. + + Set the currents to the last set value. + Update the status to on. + """ + current_to_use = self._set_curr[self._active_adr] + self.set_current_values(current_to_use) + self._status[self._active_adr][0] = "." + + def set_current_values(self, current_to_use): + """ + Set all the appropriate current readback and polarity values to a given value. + + Args: + current_to_use: The current to be set. + """ + if current_to_use < 0: + self._pol[self._active_adr] = "-" + else: + self._pol[self._active_adr] = "+" + self._ra[self._active_adr] = abs(current_to_use) + self._curr[self._active_adr] = current_to_use + self._volt[self._active_adr] = current_to_use + + def off(self): + """ + Turn the device off. + """ + self.set_current_values(0) + self._status[self._active_adr][0] = "!" + + @has_log + def set_both_volt_values(self, volt): + """ + Update the voltage value of both devices. + + This is used only for testing via the backdoor. + + Args: + volt: the voltage value to set. + """ + for PSU in self._volt.keys(): + self.log.info("setting for %s" % PSU) + self._volt[PSU] = float(volt) diff --git a/lewis_emulators/rknps/interfaces/__init__.py b/lewis_emulators/rknps/interfaces/__init__.py new file mode 100644 index 00000000..21da3b6d --- /dev/null +++ b/lewis_emulators/rknps/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import RknpsStreamInterface + +__all__ = ['RknpsStreamInterface'] diff --git a/lewis_emulators/rknps/interfaces/stream_interface.py b/lewis_emulators/rknps/interfaces/stream_interface.py new file mode 100644 index 00000000..0188a4ab --- /dev/null +++ b/lewis_emulators/rknps/interfaces/stream_interface.py @@ -0,0 +1,154 @@ +from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.core.logging import has_log + + +@has_log +class RknpsStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + in_terminator = "\r" + out_terminator = "\n\r" + + commands = { + Cmd("set_adr", "^ADR (001|002)$"), + Cmd("get_adr", "^ADR$"), + Cmd("get_ra", "^RA$"), + Cmd("get_adc", "^AD (\d)"), + Cmd("get_cmd", "^CMD$"), + Cmd("set_cmd", "^(LOC|REM)$"), + Cmd("get_pol", "^PO$"), + Cmd("set_pol", "^(\+|-)$"), + Cmd("get_status", "^S1$"), + Cmd("set_power", "^(F|N)$"), + Cmd("set_da", "^DA (\d) (-?\d+)$"), + Cmd("reset", "^RS$") + } + + def handle_error(self, request, error): + """ + If command is not recognised print and error. + + Args: + request: requested string. + error: problem. + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def set_adr(self, address): + """ + Sets the active address. + + Args: + address: The address to use for the following commands. + """ + self._device._active_adr = address + + def get_adr(self): + """ + Gets the active address. + + Returns: a string address. + """ + return "{0}".format(self._device.adr) + + def get_ra(self): + """ + Gets the value for RA. + + Returns: a number in 10e-4 Amps. + """ + return "{0}".format(self._device.ra) + + def get_adc(self, channel): + """ + Get the value for the specified AD. + + Uses the default channels of 0 for current and 8 for voltage. + + Args: + channel: The AD to return. + + Returns: a number in either 10e-4 Amps, or in Volts. + """ + if channel == "2": + # Channel 2 is the AD for the voltage + return "{0}".format(self._device.ad_volt) + elif channel == "8": + # Channel 0 is the AD for the current + return "{0}".format(self._device.ad_curr) + # All other channels are not considered under the requirements of this emulator + + def get_cmd(self): + """ + Check whether the device is in Local/Remote mode. + + Returns: LOC for local mode, REM for remote mode. + """ + return "{0}".format(self._device.cmd) + + def set_cmd(self, cmd): + """ + Sets the active address to be in local or remote mode. + + Args: + cmd: The mode to set. + """ + self._device._cmd[self._device._active_adr] = cmd + + def get_pol(self): + """ + Get the polarity of the device. + + Returns: The polarity as +/-. + """ + return "{0}".format(self._device.pol) + + def set_pol(self, pol): + """ + Set the polarity of the device. + + Args: + pol: The polarity to set. + """ + self._device._pol[self._device._active_adr] = pol + + def get_status(self): + """ + Get the status of the device. + + Returns: A character string for the status. + """ + return "{0}".format(self._device.status) + + def set_power(self, power): + """ + Turn the output power of the PSU on or off. + + Args: + power: Whether to turn the PSU oN or ofF. + """ + self._device.set_power(power) + + def set_da(self, channel, value): + """ + Set a value for the appropriate DA. + + Considers only the channel for current. + + Args: + channel: The DA to apply the value to. + value: The value to apply. + """ + if channel == "0": + # Channel 0 is the DA for the current + self._device.set_current(int(value)) + # All other channels are not considered under the requirements of this emulator + + def reset(self): + """ + Reset the device, turn it off and set all values to 0. + """ + self._device.reset() + diff --git a/lewis_emulators/rknps/states.py b/lewis_emulators/rknps/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/rknps/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' From 7c1802d0df7d95c7de37db309162e0daff91afae Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Fri, 4 Aug 2017 08:15:33 +0100 Subject: [PATCH 0290/1466] Update device.py --- lewis_emulators/tdk_lambda_genesys/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/tdk_lambda_genesys/device.py b/lewis_emulators/tdk_lambda_genesys/device.py index 66926511..3fa18a25 100644 --- a/lewis_emulators/tdk_lambda_genesys/device.py +++ b/lewis_emulators/tdk_lambda_genesys/device.py @@ -9,7 +9,7 @@ def _initialize_data(self): self._current = 2.0 self._setpoint_voltage = 10.0 self._setpoint_current = 2.0 - self._powerstate = "Off" + self._powerstate = "OFF" def _get_state_handlers(self): return {DefaultState.NAME: DefaultState()} From 222355900c5c0786a741327102b9490722aa3d5b Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Fri, 4 Aug 2017 08:15:56 +0100 Subject: [PATCH 0291/1466] Update stream_interface.py --- .../tdk_lambda_genesys/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py b/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py index e78aea09..437e20ed 100644 --- a/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py +++ b/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py @@ -12,7 +12,7 @@ class TDKLambdaGenesysStreamInterface(StreamAdapter): CmdBuilder("read_setpoint_current").escape("PC?").build(), CmdBuilder("read_current").escape("MC?").build(), CmdBuilder("remote").escape("RMT 1").build(), - CmdBuilder("write_power").escape("OUT ").arg("[Off|On]").build(), + CmdBuilder("write_power").escape("OUT ").arg("[OFF|ON]").build(), CmdBuilder("read_power").escape("OUT?").build(), } From f842b673744a4e70a06fb865d133de372e0cc0cc Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 4 Aug 2017 09:45:08 +0100 Subject: [PATCH 0292/1466] Add states --- lewis_emulators/cybaman/device.py | 38 ++++++++++++++----- .../cybaman/interfaces/stream_interface.py | 38 +++++++++++++------ lewis_emulators/cybaman/states.py | 32 +++++++++++++--- 3 files changed, 82 insertions(+), 26 deletions(-) diff --git a/lewis_emulators/cybaman/device.py b/lewis_emulators/cybaman/device.py index b9a9d98b..65431dd8 100644 --- a/lewis_emulators/cybaman/device.py +++ b/lewis_emulators/cybaman/device.py @@ -1,7 +1,7 @@ from collections import OrderedDict from lewis.devices import StateMachineDevice -from .states import DefaultState +from .states import InitializedState, UninitializedState, MovingState class SimulatedCybaman(StateMachineDevice): @@ -13,40 +13,58 @@ def _initialize_data(self): """ Sets the initial state of the device. """ - self.a = 0 - self.b = 0 - self.c = 0 + self.a_setpoint = 0 + self.b_setpoint = 0 + self.c_setpoint = 0 + + self.a = self.a_setpoint + self.b = self.b_setpoint + self.c = self.c_setpoint self.home_position_axis_a = 66 self.home_position_axis_b = 77 self.home_position_axis_c = 88 + self.initialized = False + def _get_state_handlers(self): """ Returns: states and their names """ - return {DefaultState.NAME: DefaultState()} + return { + InitializedState.NAME: InitializedState(), + UninitializedState.NAME: UninitializedState(), + MovingState.NAME: MovingState(), + } def _get_initial_state(self): """ Returns: the name of the initial state """ - return DefaultState.NAME + return UninitializedState.NAME def _get_transition_handlers(self): """ Returns: the state transitions """ - return OrderedDict() + return OrderedDict([ + ((UninitializedState.NAME, InitializedState.NAME), lambda: self.initialized), + ((InitializedState.NAME, UninitializedState.NAME), lambda: not self.initialized), + ((MovingState.NAME, UninitializedState.NAME), lambda: not self.initialized), + ((InitializedState.NAME, MovingState.NAME), + lambda: self.a != self.a_setpoint or self.b != self.b_setpoint or self.c != self.c_setpoint), + ((MovingState.NAME, InitializedState.NAME), + lambda: self.a == self.a_setpoint and self.b == self.b_setpoint and self.c == self.c_setpoint), + ]) def home_axis_a(self): - self.a = self.home_position_axis_a + self.a_setpoint = self.home_position_axis_a def home_axis_b(self): - self.b = self.home_position_axis_b + self.b_setpoint = self.home_position_axis_b def home_axis_c(self): - self.c = self.home_position_axis_c + self.c_setpoint = self.home_position_axis_c diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index f0fcc417..687416b0 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -1,29 +1,37 @@ from lewis.adapters.stream import StreamAdapter, Cmd from time import sleep +from lewis.core.logging import has_log + class CybamanStreamInterface(StreamAdapter): """ Stream interface for the serial port """ + FLOAT = "([-+]?[0-9]*\.?[0-9]*)" + commands = { Cmd("initialize", "^A$"), Cmd("get_a", "^M101$"), Cmd("get_b", "^M201$"), Cmd("get_c", "^M301$"), - Cmd("set_all", "^OPEN PROG 10 CLEAR\nG1 A ([0-9]*\.?[0-9]*) B ([0-9]*\.?[0-9]*) C ([0-9]*\.?[0-9]*) TM([0-9]*)$"), + Cmd("set_all", "^OPEN PROG 10 CLEAR\nG1 A " + FLOAT + " B " + FLOAT + " C " + FLOAT + " TM([0-9]*)$"), Cmd("ignore", "^CLOSE$"), Cmd("ignore", "^B10R$"), Cmd("reset", "^\$\$\$$"), Cmd("home_a", "^B9001R$"), Cmd("home_b", "^B9002R$"), Cmd("home_c", "^B9003R$"), + Cmd("stop", "^{}$".format(chr(0x01))), } in_terminator = "\r" - out_terminator = chr(0x06) # ACK character + # ACK character + out_terminator = chr(0x06) + + @has_log def handle_error(self, request, error): """ If command is not recognised print and error. @@ -32,12 +40,23 @@ def handle_error(self, request, error): :param error: problem :return: """ - print "An error occurred at request " + repr(request) + ": " + repr(error) + error = "An error occurred at request " + repr(request) + ": " + repr(error) + print(error) + self.log.debug(error) + return error def ignore(self): return "" def initialize(self): + self._device.initialized = True + return "" + + def stop(self): + self._device.initialized = False + + def reset(self): + self._device._initialize_data() return "" def get_a(self): @@ -55,9 +74,9 @@ def get_c(self): def set_all(self, a, b, c, tm): self._verify_tm(a, b, c, tm) - self._device.a = float(a) - self._device.b = float(b) - self._device.c = float(c) + self._device.a_setpoint = float(a) + self._device.b_setpoint = float(b) + self._device.c_setpoint = float(c) return "" def _verify_tm(self, a, b, c, tm): @@ -68,13 +87,10 @@ def _verify_tm(self, a, b, c, tm): max_difference = max([abs(a-b) for a, b in zip(old_position, new_position)]) expected_tm = max([int(round(max_difference * 1000)), 4000]) - if (tm - expected_tm) > 1: # Allow a difference of 1 for rounding errors. + # Allow a difference of 1000 for rounding errors (error would get multiplied by 1000) + if abs(tm - expected_tm) > 1000: assert False, "Wrong TM value! Expected {} but got {}".format(expected_tm, tm) - def reset(self): - self._device._initialize_data() - return "" - def home_a(self): self._device.home_axis_a() return "" diff --git a/lewis_emulators/cybaman/states.py b/lewis_emulators/cybaman/states.py index c2beedfb..57641af1 100644 --- a/lewis_emulators/cybaman/states.py +++ b/lewis_emulators/cybaman/states.py @@ -1,8 +1,30 @@ +from lewis.core import approaches from lewis.core.statemachine import State -class DefaultState(State): - """ - Device is in default state. - """ - NAME = 'Default' +class UninitializedState(State): + NAME = "UninitializedState" + + def on_entry(self, dt): + print "Entering uninitialized state" + + +class InitializedState(State): + NAME = "InitializedState" + + def on_entry(self, dt): + print "Entering initialized state" + + +class MovingState(State): + NAME = "MovingState" + + def in_state(self, dt): + device = self._context + + device.a = approaches.linear(device.a, device.a_setpoint, 10, dt) + device.b = approaches.linear(device.b, device.b_setpoint, 10, dt) + device.c = approaches.linear(device.c, device.c_setpoint, 10, dt) + + def on_entry(self, dt): + print "Entering moving state" From 02b57800bb0db4bdd20fdb5297a94b1bdc943746 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 4 Aug 2017 12:10:33 +0100 Subject: [PATCH 0293/1466] Command builder missing string method --- lewis_emulators/utils/command_builder.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 0fa4468b..dc510a47 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -70,6 +70,14 @@ def int(self): """ return self.arg(r"\d+") + def string(self): + """ + Add a string argument. + + :return: builder + """ + return self.arg(r".*") + def build(self, *args, **kwargs): """ Builds the CMd object based on the target and regular expression. From ec02479b42e548b1fa37499adfd9d63d97f1e882 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 4 Aug 2017 13:50:38 +0100 Subject: [PATCH 0294/1466] Update stream interface --- lewis_emulators/cybaman/interfaces/stream_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index 687416b0..859a7c4e 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -85,9 +85,10 @@ def _verify_tm(self, a, b, c, tm): new_position = (float(a), float(b), float(c)) max_difference = max([abs(a-b) for a, b in zip(old_position, new_position)]) - expected_tm = max([int(round(max_difference * 1000)), 4000]) + expected_tm = max([int(round(max_difference/5.0)) * 1000, 4000]) - # Allow a difference of 1000 for rounding errors (error would get multiplied by 1000) + # Allow a difference of 1000 for rounding errors / differences between labview and epics + # (error would get multiplied by 1000) if abs(tm - expected_tm) > 1000: assert False, "Wrong TM value! Expected {} but got {}".format(expected_tm, tm) From 769fe22402c682fd9014f81b83a65ff5a5bcbae2 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 4 Aug 2017 17:19:24 +0100 Subject: [PATCH 0295/1466] code --- com2tcp/com2tcp.py | 2 +- lewis_emulators/cybaman/interfaces/stream_interface.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/com2tcp/com2tcp.py b/com2tcp/com2tcp.py index 7ce7cd7f..c9654e2e 100644 --- a/com2tcp/com2tcp.py +++ b/com2tcp/com2tcp.py @@ -49,7 +49,7 @@ def listen_to_tcp(tcp_conn, serial_conn): print "Data on serial: " + data tcp_conn.sendall(data) - time.sleep(0.1) + time.sleep(0.001) except (KeyboardInterrupt, SystemExit) as e: pass finally: diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index 859a7c4e..c963e36f 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -60,15 +60,12 @@ def reset(self): return "" def get_a(self): - print "Returning input A as {}".format(self._device.a) return self._device.a*3577 def get_b(self): - print "Returning input B as {}".format(self._device.b) return self._device.b*3663 def get_c(self): - print "Returning input C as {}".format(self._device.c) return self._device.c*3663 def set_all(self, a, b, c, tm): From 76c10d8ee38690608bc2ca00241fb38716b4d8c1 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 09:22:52 +0100 Subject: [PATCH 0296/1466] Add skeleton of IEG emulator --- lewis_emulators/ieg/__init__.py | 3 +++ lewis_emulators/ieg/device.py | 25 +++++++++++++++++++ lewis_emulators/ieg/interfaces/__init__.py | 3 +++ .../ieg/interfaces/stream_interface.py | 16 ++++++++++++ lewis_emulators/ieg/states.py | 5 ++++ 5 files changed, 52 insertions(+) create mode 100644 lewis_emulators/ieg/__init__.py create mode 100644 lewis_emulators/ieg/device.py create mode 100644 lewis_emulators/ieg/interfaces/__init__.py create mode 100644 lewis_emulators/ieg/interfaces/stream_interface.py create mode 100644 lewis_emulators/ieg/states.py diff --git a/lewis_emulators/ieg/__init__.py b/lewis_emulators/ieg/__init__.py new file mode 100644 index 00000000..e0ffc397 --- /dev/null +++ b/lewis_emulators/ieg/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedIeg + +__all__ = ['SimulatedIeg'] diff --git a/lewis_emulators/ieg/device.py b/lewis_emulators/ieg/device.py new file mode 100644 index 00000000..151d811a --- /dev/null +++ b/lewis_emulators/ieg/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedIeg(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + pass + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + # (('default', 'stopped'), lambda: False), + ]) diff --git a/lewis_emulators/ieg/interfaces/__init__.py b/lewis_emulators/ieg/interfaces/__init__.py new file mode 100644 index 00000000..3a78687d --- /dev/null +++ b/lewis_emulators/ieg/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import IegStreamInterface + +__all__ = ['IegStreamInterface'] diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py new file mode 100644 index 00000000..fe08cd05 --- /dev/null +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -0,0 +1,16 @@ +from lewis.adapters.stream import StreamAdapter, Cmd + + +class IegStreamInterface(StreamAdapter): + + # Commands that we expect via serial during normal operation + commands = { + # Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), + } + + in_terminator = "\n" + out_terminator = "\n" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + return str(error) diff --git a/lewis_emulators/ieg/states.py b/lewis_emulators/ieg/states.py new file mode 100644 index 00000000..e4ca48e8 --- /dev/null +++ b/lewis_emulators/ieg/states.py @@ -0,0 +1,5 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + pass From ab980033787eef09d3c773fc4cbe2474da43412b Mon Sep 17 00:00:00 2001 From: Matt Clarke Date: Tue, 8 Aug 2017 11:45:44 +0100 Subject: [PATCH 0297/1466] Added any command --- lewis_emulators/utils/command_builder.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 0fa4468b..52c4b367 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -70,6 +70,14 @@ def int(self): """ return self.arg(r"\d+") + def any(self): + """ + Add an argument that matches anything. + + :return: builder + """ + return self.arg(r".*") + def build(self, *args, **kwargs): """ Builds the CMd object based on the target and regular expression. From 22a573735ea0649d000cf1be95f619cf8f768e5b Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Tue, 8 Aug 2017 12:02:26 +0100 Subject: [PATCH 0298/1466] Command no longer needed --- lewis_emulators/utils/command_builder.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index dc510a47..0fa4468b 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -70,14 +70,6 @@ def int(self): """ return self.arg(r"\d+") - def string(self): - """ - Add a string argument. - - :return: builder - """ - return self.arg(r".*") - def build(self, *args, **kwargs): """ Builds the CMd object based on the target and regular expression. From ffb4ebe509f2e45badc4fa4590177de4a60d5fff Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 12:18:09 +0100 Subject: [PATCH 0299/1466] Add response builder to assist with complex responses. Add response from set operating mode command --- lewis_emulators/ieg/device.py | 30 +++++- .../ieg/interfaces/stream_interface.py | 95 ++++++++++++++++++- lewis_emulators/ieg/states.py | 18 +++- 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/lewis_emulators/ieg/device.py b/lewis_emulators/ieg/device.py index 151d811a..a9214b3d 100644 --- a/lewis_emulators/ieg/device.py +++ b/lewis_emulators/ieg/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultState +from states import DormantState, GasFlowState, PumpPurgeFillState, PumpState, SingleShotState from lewis.devices import StateMachineDevice @@ -9,17 +9,37 @@ def _initialize_data(self): """ Initialize all of the device's attributes. """ - pass + self.unique_id = 123 + + self.gas_valve_open = False + self.buffer_valve_open = False + self.pump_valve_open = False + + self.operatingmode = 0 def _get_state_handlers(self): return { - 'default': DefaultState(), + 'dormant': DormantState(), + 'gas_flow': GasFlowState(), + 'pump_purge_fill': PumpPurgeFillState(), + 'pump': PumpState(), + 'single_shot': SingleShotState(), } def _get_initial_state(self): - return 'default' + return 'dormant' def _get_transition_handlers(self): return OrderedDict([ - # (('default', 'stopped'), lambda: False), + (('dormant', 'pump_purge_fill'), lambda: self.operatingmode == 1), + (('pump_purge_fill', 'dormant'), lambda: self.operatingmode != 1), + + (('dormant', 'pump'), lambda: self.operatingmode == 2), + (('pump', 'dormant'), lambda: self.operatingmode != 2), + + (('dormant', 'gas_flow'), lambda: self.operatingmode == 3), + (('gas_flow', 'dormant'), lambda: self.operatingmode != 3), + + (('dormant', 'single_shot'), lambda: self.operatingmode == 4), + (('single_shot', 'dormant'), lambda: self.operatingmode != 4), ]) diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index fe08cd05..995cd5f0 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -1,16 +1,105 @@ from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.core.logging import has_log class IegStreamInterface(StreamAdapter): # Commands that we expect via serial during normal operation commands = { - # Cmd("get_all_data", "^#00000([0-9A-F]{2})\$$"), + Cmd("get_status", "^&STS0$"), + Cmd("change_operating_mode", "^&OPM([1-4])$"), + Cmd("abort", "^&KILL$"), } - in_terminator = "\n" - out_terminator = "\n" + in_terminator = "!" + out_terminator = "\r\n" + + def _build_valve_state(self): + val = 0 + val += 1 if self._device.pump_valve_open is True else 0 + val += 2 if self._device.buffer_valve_open is True else 0 + val += 4 if self._device.gas_valve_open is True else 0 + return val def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) + + def get_status(self): + pass + + @has_log + def change_operating_mode(self, mode): + self.log.info("Setting mode to {}".format(mode)) + self._device.operatingmode = int(mode) + return ResponseBuilder()\ + .ack()\ + .startdata()\ + .data("IEG").data(self._device.unique_id)\ + .separator()\ + .data("OPM").data(self._build_valve_state())\ + .enddata()\ + .build() + + def abort(self): + self._device.operatingmode = 0 + + +class ResponseBuilder(object): + """ + Response builder for the IEG + """ + + def __init__(self): + """ + Initialize a new response + """ + self.response = "" + + def ack(self): + """ + Add an ACK data packet to the response + :return: ResponseBuilder + """ + self.response += "&ACK!{term}".format(term=IegStreamInterface.out_terminator) + return self + + def startdata(self): + """ + Adds the character signifying the start of a data block + :return: ResponseBuilder + """ + self.response += "&" + return self + + def separator(self): + """ + Adds the character signifying the seperator between bits of data + :return: ResponseBuilder + """ + self.response += "," + return self + + def data(self, data): + """ + Adds data to the response being built + :param data: the data to add + :return: ResponseBuilder + """ + self.response += str(data) + return self + + def enddata(self): + """ + Adds the 'end data' character to the response + :return: ResponseBuilder + """ + self.response += "!" + return self + + def build(self): + """ + Extract the response from the builder + :return: (str) response + """ + return self.response \ No newline at end of file diff --git a/lewis_emulators/ieg/states.py b/lewis_emulators/ieg/states.py index e4ca48e8..f186a036 100644 --- a/lewis_emulators/ieg/states.py +++ b/lewis_emulators/ieg/states.py @@ -1,5 +1,21 @@ from lewis.core.statemachine import State -class DefaultState(State): +class DormantState(State): + pass + + +class PumpPurgeFillState(State): + pass + + +class PumpState(State): + pass + + +class GasFlowState(State): + pass + + +class SingleShotState(State): pass From 4e56117eb845479398b0cb67b5d18bcaaa384b69 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 15:30:29 +0100 Subject: [PATCH 0300/1466] Output correctly-formatted status data --- .../ieg/interfaces/stream_interface.py | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index 995cd5f0..94644d80 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -26,29 +26,48 @@ def handle_error(self, request, error): return str(error) def get_status(self): - pass + return ResponseBuilder() \ + .ack() \ + .startpacket() \ + .add_data_block("IEG", self._device.unique_id) \ + .add_data_block("OPM", self._device.operatingmode) \ + .add_data_block("VST", self._build_valve_state()) \ + .add_data_block("ERR", 0) \ + .add_data_block("BPH", 0) \ + .add_data_block("SPL", 0) \ + .add_data_block("SPH", 0) \ + .add_data_block("SPR", 0) \ + .endpacket() \ + .build() - @has_log def change_operating_mode(self, mode): - self.log.info("Setting mode to {}".format(mode)) self._device.operatingmode = int(mode) return ResponseBuilder()\ .ack()\ - .startdata()\ - .data("IEG").data(self._device.unique_id)\ - .separator()\ - .data("OPM").data(self._build_valve_state())\ - .enddata()\ + .startpacket()\ + .add_data_block("IEG", self._device.unique_id) \ + .add_data_block("OPM", self._device.operatingmode)\ + .endpacket()\ .build() def abort(self): self._device.operatingmode = 0 + return ResponseBuilder() \ + .ack() \ + .startpacket() \ + .add_data_block("IEG", self._device.unique_id) \ + .add_data_block("KILL") \ + .endpacket() \ + .build() class ResponseBuilder(object): """ Response builder for the IEG """ + packet_start = "&" + packet_end = "!" + data_block_sep = "," def __init__(self): """ @@ -58,43 +77,36 @@ def __init__(self): def ack(self): """ - Add an ACK data packet to the response + Add an ACK data packet, complete with terminator, to the response :return: ResponseBuilder """ - self.response += "&ACK!{term}".format(term=IegStreamInterface.out_terminator) + self.response += "{pack_start}ACK{pack_end}{term}".format(pack_start=self.packet_start, + pack_end=self.packet_end, + term=IegStreamInterface.out_terminator) return self - def startdata(self): + def startpacket(self): """ Adds the character signifying the start of a data block :return: ResponseBuilder """ - self.response += "&" + self.response += self.packet_start return self - def separator(self): + def endpacket(self): """ - Adds the character signifying the seperator between bits of data + Adds the 'end data' character to the response :return: ResponseBuilder """ - self.response += "," + self.response += self.packet_end return self - def data(self, data): - """ - Adds data to the response being built - :param data: the data to add - :return: ResponseBuilder - """ - self.response += str(data) - return self + def add_data_block(self, *data): + if not self.response[-1:] == self.data_block_sep and not self.response[-1:] == self.packet_start: + self.response += self.data_block_sep - def enddata(self): - """ - Adds the 'end data' character to the response - :return: ResponseBuilder - """ - self.response += "!" + for item in data: + self.response += "{}".format(item) return self def build(self): @@ -102,4 +114,4 @@ def build(self): Extract the response from the builder :return: (str) response """ - return self.response \ No newline at end of file + return self.response From 03da1c53aae6d5ebb7ada7969d147044b5d62de6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 16:12:18 +0100 Subject: [PATCH 0301/1466] Add error codes to emulator --- lewis_emulators/ieg/device.py | 3 +++ lewis_emulators/ieg/interfaces/stream_interface.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/ieg/device.py b/lewis_emulators/ieg/device.py index a9214b3d..cffd48ee 100644 --- a/lewis_emulators/ieg/device.py +++ b/lewis_emulators/ieg/device.py @@ -17,6 +17,8 @@ def _initialize_data(self): self.operatingmode = 0 + self.error = 0 + def _get_state_handlers(self): return { 'dormant': DormantState(), @@ -43,3 +45,4 @@ def _get_transition_handlers(self): (('dormant', 'single_shot'), lambda: self.operatingmode == 4), (('single_shot', 'dormant'), lambda: self.operatingmode != 4), ]) + diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index 94644d80..bbe9f4d9 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -32,7 +32,7 @@ def get_status(self): .add_data_block("IEG", self._device.unique_id) \ .add_data_block("OPM", self._device.operatingmode) \ .add_data_block("VST", self._build_valve_state()) \ - .add_data_block("ERR", 0) \ + .add_data_block("ERR", self._device.error) \ .add_data_block("BPH", 0) \ .add_data_block("SPL", 0) \ .add_data_block("SPH", 0) \ From c6c4497351a8536b1523666ed9f572c64e601220 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 16:47:40 +0100 Subject: [PATCH 0302/1466] Add sample pressure and limits --- lewis_emulators/ieg/device.py | 9 +++++++++ lewis_emulators/ieg/interfaces/stream_interface.py | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/ieg/device.py b/lewis_emulators/ieg/device.py index cffd48ee..62b93caf 100644 --- a/lewis_emulators/ieg/device.py +++ b/lewis_emulators/ieg/device.py @@ -17,6 +17,10 @@ def _initialize_data(self): self.operatingmode = 0 + self.sample_pressure_high_limit = 100 + self.sample_pressure_low_limit = 10 + self.sample_pressure = 0 + self.error = 0 def _get_state_handlers(self): @@ -46,3 +50,8 @@ def _get_transition_handlers(self): (('single_shot', 'dormant'), lambda: self.operatingmode != 4), ]) + def is_sample_pressure_high(self): + return self.sample_pressure > self.sample_pressure_high_limit + + def is_sample_pressure_low(self): + return self.sample_pressure < self.sample_pressure_low_limit diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index bbe9f4d9..af48cc8c 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -34,9 +34,9 @@ def get_status(self): .add_data_block("VST", self._build_valve_state()) \ .add_data_block("ERR", self._device.error) \ .add_data_block("BPH", 0) \ - .add_data_block("SPL", 0) \ - .add_data_block("SPH", 0) \ - .add_data_block("SPR", 0) \ + .add_data_block("SPL", 1 if self._device.is_sample_pressure_low() else 0) \ + .add_data_block("SPH", 1 if self._device.is_sample_pressure_high() else 0) \ + .add_data_block("SPR", int(self._device.sample_pressure)) \ .endpacket() \ .build() From 005c76fe86514997faae2672b5dd71ca83b378d8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 17:15:32 +0100 Subject: [PATCH 0303/1466] Change device to have cleaner external interface. Add support for final command. --- lewis_emulators/ieg/device.py | 26 +++++++++++++++++ .../ieg/interfaces/stream_interface.py | 29 ++++++++++++------- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/lewis_emulators/ieg/device.py b/lewis_emulators/ieg/device.py index 62b93caf..11b72428 100644 --- a/lewis_emulators/ieg/device.py +++ b/lewis_emulators/ieg/device.py @@ -23,6 +23,8 @@ def _initialize_data(self): self.error = 0 + self.buffer_pressure_high = True + def _get_state_handlers(self): return { 'dormant': DormantState(), @@ -55,3 +57,27 @@ def is_sample_pressure_high(self): def is_sample_pressure_low(self): return self.sample_pressure < self.sample_pressure_low_limit + + def get_id(self): + return self.unique_id + + def get_pressure(self): + return self.sample_pressure + + def get_error(self): + return self.error + + def is_pump_valve_open(self): + return self.pump_valve_open + + def is_buffer_valve_open(self): + return self.buffer_valve_open + + def is_gas_valve_open(self): + return self.gas_valve_open + + def get_operating_mode(self): + return self.operatingmode + + def is_buffer_pressure_high(self): + return self.buffer_pressure_high \ No newline at end of file diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index af48cc8c..08dd1aa4 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -16,9 +16,9 @@ class IegStreamInterface(StreamAdapter): def _build_valve_state(self): val = 0 - val += 1 if self._device.pump_valve_open is True else 0 - val += 2 if self._device.buffer_valve_open is True else 0 - val += 4 if self._device.gas_valve_open is True else 0 + val += 1 if self._device.is_pump_valve_open() else 0 + val += 2 if self._device.is_buffer_valve_open() else 0 + val += 4 if self._device.is_gas_valve_open() else 0 return val def handle_error(self, request, error): @@ -29,14 +29,14 @@ def get_status(self): return ResponseBuilder() \ .ack() \ .startpacket() \ - .add_data_block("IEG", self._device.unique_id) \ - .add_data_block("OPM", self._device.operatingmode) \ + .add_data_block("IEG", self._device.get_id()) \ + .add_data_block("OPM", self._device.get_operating_mode()) \ .add_data_block("VST", self._build_valve_state()) \ - .add_data_block("ERR", self._device.error) \ - .add_data_block("BPH", 0) \ + .add_data_block("ERR", self._device.get_error()) \ + .add_data_block("BPH", 0 if self._device.is_buffer_pressure_high() else 1) \ .add_data_block("SPL", 1 if self._device.is_sample_pressure_low() else 0) \ .add_data_block("SPH", 1 if self._device.is_sample_pressure_high() else 0) \ - .add_data_block("SPR", int(self._device.sample_pressure)) \ + .add_data_block("SPR", int(self._device.get_pressure())) \ .endpacket() \ .build() @@ -45,8 +45,8 @@ def change_operating_mode(self, mode): return ResponseBuilder()\ .ack()\ .startpacket()\ - .add_data_block("IEG", self._device.unique_id) \ - .add_data_block("OPM", self._device.operatingmode)\ + .add_data_block("IEG", self._device.get_id()) \ + .add_data_block("OPM", self._device.get_operating_mode())\ .endpacket()\ .build() @@ -55,7 +55,7 @@ def abort(self): return ResponseBuilder() \ .ack() \ .startpacket() \ - .add_data_block("IEG", self._device.unique_id) \ + .add_data_block("IEG", self._device.get_id()) \ .add_data_block("KILL") \ .endpacket() \ .build() @@ -102,6 +102,13 @@ def endpacket(self): return self def add_data_block(self, *data): + """ + Adds a data block. + The elements are converted to strings and added to the response in order. + If the preceding character is not already a separator nor the start of the data block a separator is added first + :param data: data to add to the response + :return: ResponseBuilder + """ if not self.response[-1:] == self.data_block_sep and not self.response[-1:] == self.packet_start: self.response += self.data_block_sep From 8ad78ef38ed8d148f696393bd0143f8f2c4ff0ea Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 8 Aug 2017 17:36:34 +0100 Subject: [PATCH 0304/1466] Tidy up ResponseBuilder --- .../ieg/interfaces/stream_interface.py | 55 ++++++------------- 1 file changed, 18 insertions(+), 37 deletions(-) diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index 08dd1aa4..65db7e9c 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -27,8 +27,6 @@ def handle_error(self, request, error): def get_status(self): return ResponseBuilder() \ - .ack() \ - .startpacket() \ .add_data_block("IEG", self._device.get_id()) \ .add_data_block("OPM", self._device.get_operating_mode()) \ .add_data_block("VST", self._build_valve_state()) \ @@ -37,33 +35,32 @@ def get_status(self): .add_data_block("SPL", 1 if self._device.is_sample_pressure_low() else 0) \ .add_data_block("SPH", 1 if self._device.is_sample_pressure_high() else 0) \ .add_data_block("SPR", int(self._device.get_pressure())) \ - .endpacket() \ .build() def change_operating_mode(self, mode): self._device.operatingmode = int(mode) return ResponseBuilder()\ - .ack()\ - .startpacket()\ .add_data_block("IEG", self._device.get_id()) \ .add_data_block("OPM", self._device.get_operating_mode())\ - .endpacket()\ .build() def abort(self): self._device.operatingmode = 0 return ResponseBuilder() \ - .ack() \ - .startpacket() \ .add_data_block("IEG", self._device.get_id()) \ .add_data_block("KILL") \ - .endpacket() \ .build() class ResponseBuilder(object): """ - Response builder for the IEG + Response builder for the IEG. + + Outputs: + - An ACK packet before the response, properly terminated. + - A "start of data block" character + - Any number of data blocks added by add_data_block() + - An "end of data block" character """ packet_start = "&" packet_end = "!" @@ -71,35 +68,14 @@ class ResponseBuilder(object): def __init__(self): """ - Initialize a new response - """ - self.response = "" - - def ack(self): - """ - Add an ACK data packet, complete with terminator, to the response - :return: ResponseBuilder - """ - self.response += "{pack_start}ACK{pack_end}{term}".format(pack_start=self.packet_start, - pack_end=self.packet_end, - term=IegStreamInterface.out_terminator) - return self - - def startpacket(self): - """ - Adds the character signifying the start of a data block - :return: ResponseBuilder + Initialize a new response. """ - self.response += self.packet_start - return self + self.response = "{pack_start}ACK{pack_end}{term}{pack_start}".format(pack_start=self.packet_start, + pack_end=self.packet_end, + term=IegStreamInterface.out_terminator) - def endpacket(self): - """ - Adds the 'end data' character to the response - :return: ResponseBuilder - """ - self.response += self.packet_end - return self + # Not yet in a valid state - set to true once at least one data block is added + self.valid = False def add_data_block(self, *data): """ @@ -114,6 +90,9 @@ def add_data_block(self, *data): for item in data: self.response += "{}".format(item) + + # At least one data block has now been added so this is a valid message + self.valid = True return self def build(self): @@ -121,4 +100,6 @@ def build(self): Extract the response from the builder :return: (str) response """ + assert self.valid, "At least one data block must be added before calling build" + self.response += self.packet_end return self.response From 5dc70a88ef6af5cc60a9704d38f1960990330815 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 9 Aug 2017 09:58:18 +0100 Subject: [PATCH 0305/1466] Tidy up implementation of ResponseBuilder --- lewis_emulators/ieg/interfaces/stream_interface.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index 65db7e9c..f5b80451 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -12,7 +12,9 @@ class IegStreamInterface(StreamAdapter): } in_terminator = "!" - out_terminator = "\r\n" + + # Out terminator is defined in ResponseBuilder instead as we need to add it to two messages. + out_terminator = "" def _build_valve_state(self): val = 0 @@ -63,16 +65,14 @@ class ResponseBuilder(object): - An "end of data block" character """ packet_start = "&" - packet_end = "!" + packet_end = "!\r\n" data_block_sep = "," def __init__(self): """ Initialize a new response. """ - self.response = "{pack_start}ACK{pack_end}{term}{pack_start}".format(pack_start=self.packet_start, - pack_end=self.packet_end, - term=IegStreamInterface.out_terminator) + self.response = "{pack_start}ACK{pack_end}{pack_start}".format(pack_start=self.packet_start, pack_end=self.packet_end) # Not yet in a valid state - set to true once at least one data block is added self.valid = False @@ -101,5 +101,4 @@ def build(self): :return: (str) response """ assert self.valid, "At least one data block must be added before calling build" - self.response += self.packet_end - return self.response + return "{response}{packet_end}".format(response=self.response, packet_end=self.packet_end) From c0d5f6d066a2d0b6706f9c65a36cb2548bf37b37 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 9 Aug 2017 10:45:07 +0100 Subject: [PATCH 0306/1466] Device doesn't need stateful behaviour. PEP8 fixes --- lewis_emulators/ieg/device.py | 29 +++++-------------- .../ieg/interfaces/stream_interface.py | 2 +- lewis_emulators/ieg/states.py | 18 +----------- 3 files changed, 10 insertions(+), 39 deletions(-) diff --git a/lewis_emulators/ieg/device.py b/lewis_emulators/ieg/device.py index 11b72428..d531769a 100644 --- a/lewis_emulators/ieg/device.py +++ b/lewis_emulators/ieg/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DormantState, GasFlowState, PumpPurgeFillState, PumpState, SingleShotState +from states import DefaultState from lewis.devices import StateMachineDevice @@ -27,30 +27,14 @@ def _initialize_data(self): def _get_state_handlers(self): return { - 'dormant': DormantState(), - 'gas_flow': GasFlowState(), - 'pump_purge_fill': PumpPurgeFillState(), - 'pump': PumpState(), - 'single_shot': SingleShotState(), + 'default': DefaultState(), } def _get_initial_state(self): - return 'dormant' + return 'default' def _get_transition_handlers(self): - return OrderedDict([ - (('dormant', 'pump_purge_fill'), lambda: self.operatingmode == 1), - (('pump_purge_fill', 'dormant'), lambda: self.operatingmode != 1), - - (('dormant', 'pump'), lambda: self.operatingmode == 2), - (('pump', 'dormant'), lambda: self.operatingmode != 2), - - (('dormant', 'gas_flow'), lambda: self.operatingmode == 3), - (('gas_flow', 'dormant'), lambda: self.operatingmode != 3), - - (('dormant', 'single_shot'), lambda: self.operatingmode == 4), - (('single_shot', 'dormant'), lambda: self.operatingmode != 4), - ]) + return OrderedDict([]) def is_sample_pressure_high(self): return self.sample_pressure > self.sample_pressure_high_limit @@ -80,4 +64,7 @@ def get_operating_mode(self): return self.operatingmode def is_buffer_pressure_high(self): - return self.buffer_pressure_high \ No newline at end of file + return self.buffer_pressure_high + + def set_operating_mode(self, mode): + self.operatingmode = mode diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index f5b80451..6f0db03f 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -40,7 +40,7 @@ def get_status(self): .build() def change_operating_mode(self, mode): - self._device.operatingmode = int(mode) + self._device.set_operating_mode(int(mode)) return ResponseBuilder()\ .add_data_block("IEG", self._device.get_id()) \ .add_data_block("OPM", self._device.get_operating_mode())\ diff --git a/lewis_emulators/ieg/states.py b/lewis_emulators/ieg/states.py index f186a036..e4ca48e8 100644 --- a/lewis_emulators/ieg/states.py +++ b/lewis_emulators/ieg/states.py @@ -1,21 +1,5 @@ from lewis.core.statemachine import State -class DormantState(State): - pass - - -class PumpPurgeFillState(State): - pass - - -class PumpState(State): - pass - - -class GasFlowState(State): - pass - - -class SingleShotState(State): +class DefaultState(State): pass From e5067272173c070c01fd23d51a650767f6c39dc7 Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Wed, 9 Aug 2017 18:30:42 +0100 Subject: [PATCH 0307/1466] Fixed a couple of bugs, improved readability --- .../inspectionProfiles/Project_Default.xml | 15 ++ .../inspectionProfiles/profiles_settings.xml | 7 + lewis_emulators/ag33220a/device.py | 175 ++++++++---------- .../ag33220a/interfaces/stream_interface.py | 38 ++-- 4 files changed, 122 insertions(+), 113 deletions(-) create mode 100644 lewis_emulators/.idea/inspectionProfiles/Project_Default.xml create mode 100644 lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml diff --git a/lewis_emulators/.idea/inspectionProfiles/Project_Default.xml b/lewis_emulators/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..a2ba4e76 --- /dev/null +++ b/lewis_emulators/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml b/lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..3b312839 --- /dev/null +++ b/lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index 5ea51b6e..13cb8530 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -5,36 +5,36 @@ class SimulatedAG33220A(Device): """ Simulated AG33220A """ - make = "Agilent Technologies,33220A" - firmware_rev_num = "MY44033103,2.02" - boot_kernel_revision_number = "2.02" - asic_rev_num = "22" - printed_circuit_board_rev_num = "2" - idn = make+"-"+firmware_rev_num+"-"+boot_kernel_revision_number+"-"+asic_rev_num+"-"+printed_circuit_board_rev_num + + # Constants + AMP_MIN = 0.01 + AMP_MAX = 10 + OFF_MAX = 4.995 + VOLT_MAX = 5 + VOLT_MIN = -5 + VOLT_LOW_MAX = 4.99 + VOLT_HIGH_MIN = -4.99 + VOLT_PRECISION = 0.01 + FREQ_MINS = {"SIN": 10 ** -6, "SQU": 10 ** -6, "RAMP": 10 ** -6, "PULS": 5 * 10 ** -4, + "NOIS": 10 ** -6, "USER": 10 ** -6} + FREQ_MAXS = {"SIN": 2 * 10 ** 7, "SQU": 2 * 10 ** 7, "RAMP": 2 * 10 ** 5, "PULS": 5 * 10 ** 6, + "NOIS": 2 * 10 ** 7, "USER": 6 * 10 ** 6} + + # Device variables + idn = "Agilent Technologies,33220A-MY44033103,2.02-2.02-22-2" amplitude = 0.1 frequency = 1000 offset = 0 units = "VPP" function = "SIN" - output = 0 + output = "ON" voltage_high = 0.05 voltage_low = -0.05 - amplitude_lower_bound = 0.01 - amplitude_upper_bound = 10 - offset_lower_bound = -4.995 - offset_upper_bound = 4.995 - voltage_lower_bound = -5 - voltage_upper_bound = 5 - voltage_low_lower_bound = -5 - voltage_low_upper_bound = 4.99 - voltage_high_lower_bound = -4.99 - voltage_high_upper_bound = 5 - voltage_precision = 0.01 + range_auto = "OFF" def limit(self, value, minimum, maximum): """ - Limits an input number between two given numbers - or sets the value to the maximum or minimum. + Limits an input number between two given numbers or sets the value to the maximum or minimum. :param value: the value to be limited :param minimum: the smallest that the value can be @@ -42,109 +42,99 @@ def limit(self, value, minimum, maximum): :return: the value after it has been limited """ - try: - value = float(value) - if value >= maximum: - return maximum - elif value < minimum: - return minimum - else: - return value - except: - return {"MIN": minimum, "MAX": maximum}[value] + if type(value) is str: + try: + value = float(value) + except ValueError: + return {"MIN": minimum, "MAX": maximum}[value] + + return max(min(value, maximum), minimum) def set_new_amplitude(self, new_amplitude): """ - Changing the amplitude to the new amplitude whilst also changing - voltage high, voltage low, and offset if voltage high or low is outside the boundary. + Changing the amplitude to the new amplitude whilst also changing the offset if voltage high or low is + outside the boundary. The volt high and low are then updated. - :param new_amplitude; the amplitude to set the devices amplitude to + :param new_amplitude: the amplitude to set the devices amplitude to """ - new_amplitude = self.limit(new_amplitude, self.amplitude_lower_bound, self.amplitude_upper_bound) - if 0.5 * new_amplitude + self.offset > self.voltage_upper_bound or self.offset - 0.5 * new_amplitude < self.voltage_lower_bound: - if 0.5 * new_amplitude + self.offset > self.voltage_upper_bound: - offset_difference = 0.5 * new_amplitude + self.offset - self.voltage_upper_bound - elif self.offset - 0.5 * new_amplitude < self.voltage_lower_bound: - offset_difference = -0.5 * new_amplitude + self.offset + self.voltage_upper_bound - self.offset -= offset_difference - self.voltage_high -= offset_difference - self.voltage_low -= offset_difference - else: - self.update_volt_high_and_low(new_amplitude, self.offset) + new_amplitude = self.limit(new_amplitude, self.AMP_MIN, self.AMP_MAX) + + peak_amp = 0.5 * new_amplitude + if self.offset + peak_amp > self.VOLT_MAX: + self.offset = self.VOLT_MAX - peak_amp + elif self.offset - peak_amp < self.VOLT_MIN: + self.offset = self.VOLT_MIN + peak_amp + self.amplitude = new_amplitude + self._update_volt_high_and_low(self.amplitude, self.offset) + def set_new_frequency(self, new_frequency): """ - Sets the frequency within limits between upper and lower bound - which are different for each function. + Sets the frequency within limits between upper and lower bound (depends on the function). - :param new_frequency: the frequency which wants to be set + :param new_frequency: the frequency to set to """ - self.frequency = self.limit(new_frequency, self.frequency_lower_bound(), self.frequency_upper_bound()) + self.frequency = self.limit(new_frequency, self.FREQ_MINS[self.function], self.FREQ_MAXS[self.function]) - def set_new_voltage_high(self,new_voltage_high): + def set_new_voltage_high(self, new_voltage_high): """ - Sets a new voltage high which then changes the voltage low if voltage high - is set to a value lower than the voltage low. + Sets a new voltage high which then changes the voltage low to keep it lower. The voltage offset and amplitude are then updated. - :param new_voltage_high: the value of voltage high which is to be set + :param new_voltage_high: the value of voltage high to set to """ - new_voltage_high = self.limit(new_voltage_high, self.voltage_high_lower_bound, self.voltage_high_upper_bound) + new_voltage_high = self.limit(new_voltage_high, self.VOLT_HIGH_MIN, self.VOLT_MAX) if new_voltage_high <= self.voltage_low: - self.voltage_low = self.limit(new_voltage_high - self.voltage_precision, self.voltage_low_lower_bound, new_voltage_high) - self.update_volt_and_offs(self.voltage_low, new_voltage_high) + self.voltage_low = self.limit(new_voltage_high - self.VOLT_PRECISION, self.VOLT_MIN, new_voltage_high) + self._update_volt_and_offs(self.voltage_low, new_voltage_high) - def set_new_voltage_low(self,new_voltage_low): + def set_new_voltage_low(self, new_voltage_low): """ - Sets a new voltage low which then changes the voltage high if voltage low - is set to a value higher than the voltage high. + Sets a new voltage high which then changes the voltage low to keep it higher. The voltage offset and amplitude are then updated. :param new_voltage_low: the value of voltage low which is to be set """ - new_voltage_low = self.limit(new_voltage_low, self.voltage_low_lower_bound, self.voltage_low_upper_bound) + new_voltage_low = self.limit(new_voltage_low, self.VOLT_MIN, self.VOLT_LOW_MAX) if new_voltage_low >= self.voltage_high: - self.voltage_high = self.limit(new_voltage_low + self.voltage_precision, new_voltage_low, self.voltage_high_upper_bound) - self.update_volt_and_offs(new_voltage_low, self.voltage_high) + self.voltage_high = self.limit(new_voltage_low + self.VOLT_PRECISION, new_voltage_low, self.VOLT_MAX) + self._update_volt_and_offs(new_voltage_low, self.voltage_high) - def update_volt_and_offs(self, new_low, new_high): + def _update_volt_and_offs(self, new_low, new_high): """ - Updates the value of amplitude and offset if there is a change in - voltage low or voltage high. + Updates the value of amplitude and offset if there is a change in voltage low or voltage high. :param new_low: the value of voltage low :param new_high: the value of voltage high """ self.voltage_high = new_high self.voltage_low = new_low - self.amplitude = self.voltage_high-self.voltage_low - self.offset = (self.voltage_high+self.voltage_low)/2 + self.amplitude = self.voltage_high - self.voltage_low + self.offset = (self.voltage_high + self.voltage_low)/2 def set_offs_and_update_voltage(self, new_offset): """ - Sets the value of offset and updates the amplitude, voltage low and - voltage high for a new value of the offset. + Sets the value of offset and updates the amplitude, voltage low and voltage high for a new value of the offset. :param new_offset: the new offset to be set """ - new_offset = self.limit(new_offset, self.offset_lower_bound, self.offset_upper_bound) - if new_offset+self.voltage_high > self.voltage_upper_bound: - self.amplitude = 2/(self.voltage_upper_bound-new_offset) - self.voltage_high = self.voltage_upper_bound - self.voltage_low = self.voltage_upper_bound - self.amplitude - elif new_offset+self.voltage_low < self.voltage_lower_bound: - self.amplitude = 2/(self.voltage_lower_bound-new_offset) - self.voltage_low = self.voltage_lower_bound - self.voltage_high = self.voltage_lower_bound + self.amplitude + new_offset = self.limit(new_offset, -self.OFF_MAX, self.OFF_MAX) + if new_offset + self.voltage_high > self.VOLT_MAX: + self.amplitude = 2*(self.VOLT_MAX-new_offset) + self.voltage_high = self.VOLT_MAX + self.voltage_low = self.VOLT_MAX - self.amplitude + elif new_offset + self.voltage_low < self.VOLT_MIN: + self.amplitude = 2*(self.VOLT_MIN-new_offset) + self.voltage_low = self.VOLT_MIN + self.voltage_high = self.VOLT_MIN + self.amplitude else: - self.update_volt_high_and_low(self.amplitude, new_offset) + self._update_volt_high_and_low(self.amplitude, new_offset) self.offset = new_offset - def update_volt_high_and_low(self, new_volt, new_offs): + def _update_volt_high_and_low(self, new_volt, new_offs): """ - Updates the value of voltage high and low for a given value of - amplitude and offset. + Updates the value of voltage high and low for a given value of amplitude and offset. :param new_volt: the value of the amplitude :param new_offs: the value of the offset @@ -154,20 +144,13 @@ def update_volt_high_and_low(self, new_volt, new_offs): self.voltage_high = new_offs + new_volt / 2 self.voltage_low = new_offs - new_volt / 2 - def frequency_lower_bound(self): - """ - Returning the appropriate lower bound for the frequency for a given function. - - :return: lower bound for the frequency for a given function - """ - return {"SIN": 10 ** -6, "SQU": 10 ** -6, "RAMP": 10 ** -6, "PULS": 5 * 10 ** -4, "NOIS": 10 ** -6, "USER": 10 ** -6}[self.function] + def get_output(self): + return ["OFF", "ON"].index(self.output) - def frequency_upper_bound(self): - """ - Returning the appropriate upper bound for the frequency for a given function. - - :return: upper bound for the frequency for a given function - """ - return {"SIN": 2 * 10 ** 7, "SQU": 2 * 10 ** 7, "RAMP": 2 * 10 ** 5, "PULS": 5 * 10 ** 6, "NOIS": 2 * 10 ** 7,"USER": 6 * 10 ** 6}[self.function] + def get_range_auto(self): + possible_ranges = ["OFF", "ON", "ONCE"] + return possible_ranges.index(self.range_auto) - pass + def set_function(self, new_function): + self.function = new_function + self.frequency = self.limit(self.frequency, self.FREQ_MINS[new_function], self.FREQ_MAXS[new_function]) \ No newline at end of file diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index b7d1b4d2..c3e20752 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -1,33 +1,36 @@ from lewis.adapters.stream import StreamAdapter, Cmd import traceback +NUM_MIN_MAX = "([\-0-9.]+|MAX|MIN)" + class AG33220AStreamInterface(StreamAdapter): commands = { Cmd("get_amplitude", "^VOLT\?$"), - Cmd("set_amplitude", "^VOLT " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("set_amplitude", "^VOLT " + NUM_MIN_MAX, argument_mappings=[str]), Cmd("get_frequency", "^FREQ\?$"), - Cmd("set_frequency", "^FREQ " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("set_frequency", "^FREQ " + NUM_MIN_MAX, argument_mappings=[str]), Cmd("get_offset", "^VOLT:OFFS\?$"), - Cmd("set_offset", "^VOLT:OFFS " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("set_offset", "^VOLT:OFFS " + NUM_MIN_MAX, argument_mappings=[str]), Cmd("get_units", "^VOLT:UNIT\?$"), - Cmd("set_units", "^VOLT:UNIT " + "(VPP|VRMS|DBM)$", argument_mappings=[str]), + Cmd("set_units", "^VOLT:UNIT (VPP|VRMS|DBM)$", argument_mappings=[str]), Cmd("get_function", "^FUNC\?$"), - Cmd("set_function", "^FUNC " + "(SIN|SQU|RAMP|PULS|NOIS|DC|USER)$", argument_mappings=[str]), + Cmd("set_function", "^FUNC (SIN|SQU|RAMP|PULS|NOIS|DC|USER)$", argument_mappings=[str]), Cmd("get_output", "^OUTP\?$"), - Cmd("set_output", "^OUTP " + "(0|1|ON|OFF)$", argument_mappings=[str]), + Cmd("set_output", "^OUTP (ON|OFF)$", argument_mappings=[str]), Cmd("get_idn", "^\*IDN\?$"), Cmd("get_voltage_high", "^VOLT:HIGH\?$"), - Cmd("set_voltage_high", "^VOLT:HIGH " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("set_voltage_high", "^VOLT:HIGH " + NUM_MIN_MAX, argument_mappings=[str]), Cmd("get_voltage_low", "^VOLT:LOW\?$"), - Cmd("set_voltage_low", "^VOLT:LOW " + "([\-0-9.]+|MAX|MIN)$", argument_mappings=[str]), + Cmd("set_voltage_low", "^VOLT:LOW " + NUM_MIN_MAX, argument_mappings=[str]), + Cmd("get_voltage_range_auto", "^VOLT:RANG:AUTO\?$"), + Cmd("set_voltage_range_auto", "^VOLT:RANG:AUTO (OFF|ON|ONCE)$", argument_mappings=[str]), } - in_terminator = "\n" # \r\n for putty + in_terminator = "\n" out_terminator = "\n" - # Takes in a value and returns a value in the form of x.xxx0000000000Eyy def float_output(self, value): value = float('%s' % float('%.4g' % float(value))) @@ -61,17 +64,12 @@ def get_function(self): return self._device.function def set_function(self, new_function): - self._device.function = new_function - self.set_frequency(self._device.frequency) + self._device.set_function(new_function) def get_output(self): - return self._device.output + return self._device.get_output() def set_output(self, new_output): - try: - new_output = int(new_output) - except: - new_output = ["OFF", "ON"].index(new_output) self._device.output = new_output def get_idn(self): @@ -89,5 +87,11 @@ def get_voltage_low(self): def set_voltage_low(self, new_voltage_low): self._device.set_new_voltage_low(new_voltage_low) + def get_voltage_range_auto(self): + return self._device.get_range_auto() + + def set_voltage_range_auto(self, range_auto): + self._device.range_auto = range_auto + def handle_error(self, request, error): print traceback.format_exc() From d90564c4accc0bebbaa76de9dc7382c975fb276a Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Wed, 9 Aug 2017 18:31:56 +0100 Subject: [PATCH 0308/1466] Removed accidentally added files --- .../.idea/inspectionProfiles/Project_Default.xml | 15 --------------- .../inspectionProfiles/profiles_settings.xml | 7 ------- lewis_emulators/.idea/vcs.xml | 7 ------- 3 files changed, 29 deletions(-) delete mode 100644 lewis_emulators/.idea/inspectionProfiles/Project_Default.xml delete mode 100644 lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml delete mode 100644 lewis_emulators/.idea/vcs.xml diff --git a/lewis_emulators/.idea/inspectionProfiles/Project_Default.xml b/lewis_emulators/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index a2ba4e76..00000000 --- a/lewis_emulators/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - \ No newline at end of file diff --git a/lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml b/lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b312839..00000000 --- a/lewis_emulators/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/lewis_emulators/.idea/vcs.xml b/lewis_emulators/.idea/vcs.xml deleted file mode 100644 index ff8c91d8..00000000 --- a/lewis_emulators/.idea/vcs.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file From d4dda86b542113343746eec0210562d92177575e Mon Sep 17 00:00:00 2001 From: John Holt Date: Thu, 10 Aug 2017 16:16:01 +0100 Subject: [PATCH 0309/1466] Add new line --- lewis_emulators/ag33220a/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/ag33220a/device.py b/lewis_emulators/ag33220a/device.py index 13cb8530..ef1bec1c 100644 --- a/lewis_emulators/ag33220a/device.py +++ b/lewis_emulators/ag33220a/device.py @@ -153,4 +153,4 @@ def get_range_auto(self): def set_function(self, new_function): self.function = new_function - self.frequency = self.limit(self.frequency, self.FREQ_MINS[new_function], self.FREQ_MAXS[new_function]) \ No newline at end of file + self.frequency = self.limit(self.frequency, self.FREQ_MINS[new_function], self.FREQ_MAXS[new_function]) From 6da330d698d6ee538a54489127d341acce49ee4a Mon Sep 17 00:00:00 2001 From: Attah Date: Fri, 11 Aug 2017 15:10:50 +0100 Subject: [PATCH 0310/1466] Lakeshore460 Gaussmeter Device Emulator --- lewis_emulators/lakeshore460/__init__.py | 3 + lewis_emulators/lakeshore460/device.py | 290 ++++++++++++++++++ .../lakeshore460/interfaces/__init__.py | 3 + .../interfaces/stream_interface.py | 169 ++++++++++ lewis_emulators/lakeshore460/states.py | 8 + 5 files changed, 473 insertions(+) create mode 100644 lewis_emulators/lakeshore460/__init__.py create mode 100644 lewis_emulators/lakeshore460/device.py create mode 100644 lewis_emulators/lakeshore460/interfaces/__init__.py create mode 100644 lewis_emulators/lakeshore460/interfaces/stream_interface.py create mode 100644 lewis_emulators/lakeshore460/states.py diff --git a/lewis_emulators/lakeshore460/__init__.py b/lewis_emulators/lakeshore460/__init__.py new file mode 100644 index 00000000..9904e3f5 --- /dev/null +++ b/lewis_emulators/lakeshore460/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedLakeshore460 + +__all__ = ['SimulatedLakeshore460'] diff --git a/lewis_emulators/lakeshore460/device.py b/lewis_emulators/lakeshore460/device.py new file mode 100644 index 00000000..e5349564 --- /dev/null +++ b/lewis_emulators/lakeshore460/device.py @@ -0,0 +1,290 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedLakeshore460(StateMachineDevice): + """ + Simulated AM Int2-L pressure transducer. + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self._idn = "000000000000000000000000000000000000000" + self._unit= "G" + self._status = 0 + self._mode = 0 + self._prms = 0 + self._filter = 0 + self._auto_range = 0 + self._manual_range = 2 + self._max_hold = 0 + self._rel_mode = 0 + self.rel_mode_reading = 4906 + self._rel_mode_reading_multiplier = "ON" + self._total_fields = 7.5 + self._source = 1 + self._channel = "Channel X" + self._display_filter = 5 + self._filter_points = 9 + self._reading_multiplier = "ON" + self._rel_set_point = 500 + self._max_reading = 100 + self._rel_multiplier = "ON" + self._magnetic_field_multiplier = "m" + self._magnetic_field_reading = 400 + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() + + @property + def idn(self): + """ + Returns: the IDN of device + """ + return self._idn + + @idn.setter + def idn(self, idn): + """ + :param: idn:sets the IDN of device + """ + self._idn = idn + + @property + def unit(self): + """ + :return: unit for the device + """ + return self._unit + + @unit.setter + def unit(self, unit): + """ + :param unit: set unit for the device + :return: + """ + self._unit = unit + + @property + def status(self): + """ + :return: status of device. If its on/off + """ + return self._status + + @status.setter + def status(self,status): + """ + :param status: sets the device status, if device is on/off + :return: + """ + self._status = status + + @property + def mode(self): + """ + :return: AC or DC unit of measurement + """ + return self._mode + + @mode.setter + def mode(self, mode): + self._mode = mode + + @property + def prms(self): + """ + :return: Peajk /RMS Field reading + """ + return self._prms + + @prms.setter + def prms(self, prms): + self._prms = prms + + @property + def filter(self): + """ + :return: Peajk /RMS Field reading + """ + return self._filter + + @filter.setter + def filter(self, state): + self._filter = state + + @property + def max_hold(self): + """ + :return: Peajk /RMS Field reading + """ + return self._max_hold + + @max_hold.setter + def max_hold(self, max_hold): + self._max_hold = max_hold + + @property + def rel_mode(self): + """ + :return: Peajk /RMS Field reading + """ + return self._rel_mode + + @rel_mode.setter + def rel_mode(self, rel_mode): + self._rel_mode = rel_mode + + @property + def auto_range(self): + """ + :return: Peajk /RMS Field reading + """ + return self._auto_range + + @auto_range.setter + def auto_range(self, range): + self._auto_range = range + + @property + def manual_range(self): + """ + :return: Peajk /RMS Field reading + """ + return self._manual_range + + @manual_range.setter + def manual_range(self, range): + self._manual_range = range + + @property + def total_fields(self): + return self._total_fields + + @total_fields.setter + def total_fields(self, total): + self._total_fields = total + + @property + def source(self): + return self._source + + @source.setter + def source(self, source): + self._source = source + + @property + def channel(self): + return self._channel + + @channel.setter + def channel(self, channel): + self._channel = channel + + @property + def display_filter(self): + return self._display_filter + + @display_filter.setter + def display_filter(self, percentage): + self._display_filter = percentage + + @property + def filter_points(self): + return self._filter_points + + @filter_points.setter + def filter_points(self, points): + self._filter_points = points + + @property + def rel_multiplier(self): + return self._rel_multiplier + + @rel_multiplier.setter + def rel_multiplier(self, multiplier): + self._rel_multiplier = multiplier + + @property + def reading_multiplier(self): + return self._reading_multiplier + + @reading_multiplier.setter + def reading_multiplier(self, multiplier): + self._reading_multiplier = multiplier + + @property + def rel_set_point(self): + return self._rel_set_point + + @rel_set_point.setter + def rel_set_point(self, setpoint): + self._rel_set_point = setpoint + + @property + def max_reading(self): + return self._max_reading + + @max_reading.setter + def max_reading(self, reading): + self._max_reading = reading + + @property + def rel_mode_reading(self): + """ + :return: Peajk /RMS Field reading + """ + return self._rel_mode_reading + + @rel_mode_reading.setter + def rel_mode_reading(self, rel_mode): + self._rel_mode_reading = rel_mode + + @property + def rel_mode_reading_multiplier(self): + """ + :return: Peajk /RMS Field reading + """ + return self._rel_mode_reading_multiplier + + @rel_mode_reading_multiplier.setter + def rel_mode_reading_multiplier(self, multiplier): + self._rel_mode_reading_multiplier = multiplier + + @property + def magnetic_field_multiplier(self): + return self._magnetic_field_multiplier + + @magnetic_field_multiplier.setter + def magnetic_field_multiplier(self, multiplier): + self._magnetic_field_multiplier = multiplier + + @property + def magnetic_field_reading(self): + return self._magnetic_field_reading + + @magnetic_field_reading.setter + def magnetic_field_reading(self, field): + self._magnetic_field_reading = field + + + diff --git a/lewis_emulators/lakeshore460/interfaces/__init__.py b/lewis_emulators/lakeshore460/interfaces/__init__.py new file mode 100644 index 00000000..2d3ac923 --- /dev/null +++ b/lewis_emulators/lakeshore460/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Lakeshore460StreamInterface + +__all__ = ['Lakeshore460StreamInterface'] diff --git a/lewis_emulators/lakeshore460/interfaces/stream_interface.py b/lewis_emulators/lakeshore460/interfaces/stream_interface.py new file mode 100644 index 00000000..fc58c853 --- /dev/null +++ b/lewis_emulators/lakeshore460/interfaces/stream_interface.py @@ -0,0 +1,169 @@ +from lewis.adapters.stream import StreamAdapter +from utils.command_builder import CmdBuilder +from lewis.core.logging import has_log + +@has_log +class Lakeshore460StreamInterface(StreamAdapter): + + in_terminator="\r\n" + out_terminator="\r\n" + + commands = { + CmdBuilder("get_IDN").escape("*IDN?").build(), + CmdBuilder("get_unit").escape("UNIT?").build(), + CmdBuilder("set_unit").escape("UNIT ").arg("G|T").build(), + CmdBuilder("get_on_off").escape("ONOFF?").build(), + CmdBuilder("set_on_off").escape("ONOFF ").arg("0|1").build(), + CmdBuilder("get_ac_dc_field_reading").escape("ACDC?").build(), + CmdBuilder("set_ac_dc_field_reading").escape("ACDC ").arg("0|1").build(), + CmdBuilder("get_prms_reading").escape("PRMS?").build(), + CmdBuilder("set_prms_reading").escape("PRMS ").arg("0|1").build(), + CmdBuilder("set_display_filter").escape("FILT ").arg("0|1").build(), + CmdBuilder("get_display_filter").escape("FILT?").build(), + CmdBuilder("set_max_hold").escape("MAX ").arg("0|1").build(), + CmdBuilder("get_max_hold").escape("MAX?").build(), + CmdBuilder("set_auto_range").escape("AUTO ").arg("0|1").build(), + CmdBuilder("get_auto_range").escape("AUTO?").build(), + CmdBuilder("set_relative_mode").escape("REL ").arg("0|1").build(), + CmdBuilder("get_relative_mode").escape("REL?").build(), + CmdBuilder("get_all_fields").escape("ALLF?").build(), + CmdBuilder("get_source").escape("VSRC?").build(), + CmdBuilder("set_source").escape("VSRC ").digit().build(), + CmdBuilder("get_channel").escape("CHNL?").build(), + CmdBuilder("set_channel").escape("CHNL ").digit().build(), + CmdBuilder("get_display_filter_window").escape("FWIN?").build(), + CmdBuilder("set_display_filter_window").escape("FWIN ").int().build(), + CmdBuilder("set_filter_points").escape("FNUM ").int().build(), + CmdBuilder("get_filter_points").escape("FNUM?").build(), + CmdBuilder("get_manual_range").escape("RANGE?").build(), + CmdBuilder("set_manual_range").escape("RANGE ").int().build(), + CmdBuilder("read_relative_mode_set_point_multiplier").escape("RELSM?").build(), + CmdBuilder("read_relative_mode_set_point").escape("RELS?").build(), + CmdBuilder("set_relative_mode_set_point").escape("RELS ").float().build(), + CmdBuilder("read_max_reading").escape("MAXR?").build(), + CmdBuilder("read_max_reading_multiplier").escape("MAXRM?").build(), + CmdBuilder("get_relative_mode_reading_multiplier").escape("RELRM?").build(), + CmdBuilder("get_relative_mode_reading").escape("RELR").build(), + CmdBuilder("get_magnetic_field_reading_multiplier").escape("FIELDM?").build(), + CmdBuilder("get_magnetic_field_reading").escape("FIELD?").build() + } + + def handle_error(self,request, error): + self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) + print("An error occurred at request" + repr(request) + ": " + repr(error)) + + def get_IDN(self): + return "{0}".format(self._device.idn) + + def get_unit(self): + + return "{0}".format(self._device.unit) + + def set_unit(self, unit): + self._device.unit = unit + + def get_on_off(self): + return self._device.status + + def set_on_off(self, status): + self._device.status = status + + def get_ac_dc_field_reading(self): + return "{0}".format(self._device.mode) + + def set_ac_dc_field_reading(self, mode): + self._device.mode = mode + + def get_prms_reading(self): + return "{0}".format(self._device.prms) + + def set_prms_reading(self, prms): + self._device.prms = prms + + def get_display_filter(self): + return "{0}".format(self._device.filter) + + def set_display_filter(self, filter): + self._device.filter = filter + + def set_max_hold(self, max_hold): + self._device.max_hold = max_hold + + def get_max_hold(self): + return "{0}".format(self._device.max_hold) + + def set_relative_mode(self, rel_mode): + self._device.rel_mode = rel_mode + + def get_relative_mode(self): + return "{0}".format(self._device.rel_mode) + + def set_auto_range(self, auto_range): + self._device.auto_range = auto_range + + def get_auto_range(self): + return "{0}".format(self._device.auto_range) + + def set_manual_range(self, range): + self._device.manual_range = range + + def get_manual_range(self): + return "{0}".format(self._device.manual_range) + + def get_all_fields(self): + return "{0}".format(self._device.total_fields) + + def set_source(self, source): + self._device.source = source + + def get_source(self): + return "{0}".format(self._device.source) + + def set_channel(self, channel): + self.log.error("My Channel is: {0}".format(self._device.channel)) + self._device.channel = channel + + def get_channel(self): + return "{0}".format(self._device.channel) + + def get_display_filter_window(self): + return "{0}".format(self._device.display_filter) + + def set_display_filter_window(self, percentage): + self._device.display_filter = percentage + + def set_filter_points(self, points): + self._device.filter_points = points + + def get_filter_points(self): + return "{0}".format(self._device.filter_points) + + def read_relative_mode_set_point_multiplier(self): + self.log.error("My Multiplier is: {0}".format(self._device.rel_multiplier)) + + return "{0}".format(self._device.rel_multiplier) + + def read_relative_mode_set_point(self): + + return "{0}".format(self._device.rel_set_point) + + def set_relative_mode_set_point(self, setpoint): + self._device.rel_set_point = setpoint + + def read_max_reading(self): + return "{0}".format( self._device.max_reading) + + def read_max_reading_multiplier(self): + return "{0}".format( self._device.reading_multiplier) + + def get_relative_mode_reading(self): + return "{0}".format(self._device.rel_mode_reading) + + def get_relative_mode_reading_multiplier(self): + return "{0}".format(self._device.rel_mode_reading_multiplier) + + def get_magnetic_field_reading(self): + return "{0}".format(self._device.magnetic_field_reading) + + def get_magnetic_field_reading_multiplier(self): + return "{0}".format(self._device.magnetic_field_reading_multiplier) diff --git a/lewis_emulators/lakeshore460/states.py b/lewis_emulators/lakeshore460/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/lakeshore460/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' From 1f18b9370880b66a4e9dd1a139310df6fa0e1055 Mon Sep 17 00:00:00 2001 From: Attah Date: Fri, 11 Aug 2017 15:36:37 +0100 Subject: [PATCH 0311/1466] added comments --- lewis_emulators/lakeshore460/device.py | 129 +++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 10 deletions(-) diff --git a/lewis_emulators/lakeshore460/device.py b/lewis_emulators/lakeshore460/device.py index e5349564..1d8430ed 100644 --- a/lewis_emulators/lakeshore460/device.py +++ b/lewis_emulators/lakeshore460/device.py @@ -6,7 +6,7 @@ class SimulatedLakeshore460(StateMachineDevice): """ - Simulated AM Int2-L pressure transducer. + Simulated Lakeshore 460 """ def _initialize_data(self): @@ -119,171 +119,280 @@ def prms(self): @prms.setter def prms(self, prms): + """ + :param prms: Sets the value to Peak or RMS + :return: + """ self._prms = prms @property def filter(self): """ - :return: Peajk /RMS Field reading + :return: returns filter """ return self._filter @filter.setter def filter(self, state): + """ + :param state: sets filter state (binary) + :return: + """ self._filter = state @property def max_hold(self): """ - :return: Peajk /RMS Field reading + :return: returns binary max hold """ return self._max_hold @max_hold.setter def max_hold(self, max_hold): + """ + :param max_hold: sets the max hold value + :return: + """ self._max_hold = max_hold @property def rel_mode(self): """ - :return: Peajk /RMS Field reading + :return: Return relative mode reading """ return self._rel_mode @rel_mode.setter def rel_mode(self, rel_mode): + """ + :param rel_mode: Sets the relative mode reading + :return: + """ self._rel_mode = rel_mode @property def auto_range(self): """ - :return: Peajk /RMS Field reading + :return: Returns the auto range """ return self._auto_range @auto_range.setter def auto_range(self, range): + """ + :param range: sets the auto range + :return: + """ self._auto_range = range @property def manual_range(self): """ - :return: Peajk /RMS Field reading + :return: the value that has been set for the manual range """ return self._manual_range @manual_range.setter def manual_range(self, range): + """ + :param range: sets the manual range + :return: + """ self._manual_range = range @property def total_fields(self): + """ + :return: the total field value X Y Z Field + """ return self._total_fields @total_fields.setter def total_fields(self, total): + """ + :param total: sets the total field value for X Y Z Field + :return: + """ self._total_fields = total @property def source(self): - return self._source + """ + :return: Returns magnetic field source XYZ, XYZ + """ + return self._source @source.setter def source(self, source): + """ + :param source: Sets magnetic field source XYZ, XYZ + :return: + """ self._source = source @property def channel(self): + """ + :return: returns the channel value i.e Channel X, Channel Y + """ return self._channel @channel.setter def channel(self, channel): + """ + :param channel: sets channel i.e Channel X, Channel Y + :return: + """ self._channel = channel @property def display_filter(self): + """ + :return: returns the percentage for display field opening + """ return self._display_filter @display_filter.setter def display_filter(self, percentage): + """ + :param percentage: sets the percentage for display filter opening + :return: + """ self._display_filter = percentage @property def filter_points(self): + """ + :return: returns filter points value should be between 1 to 10 + """ return self._filter_points @filter_points.setter def filter_points(self, points): + """ + :param points: sets filter points value should be between 1 to 10 + :return: + """ self._filter_points = points @property def rel_multiplier(self): + """ + :return: Returns relative multiplier for device + """ return self._rel_multiplier @rel_multiplier.setter def rel_multiplier(self, multiplier): + """ + :param multiplier: + :return: sets the relative multiplier for the device + """ self._rel_multiplier = multiplier @property def reading_multiplier(self): + """ + :return: return reading multiplier + """ return self._reading_multiplier @reading_multiplier.setter def reading_multiplier(self, multiplier): + """ + :param multiplier: sets the reading multiplier for the device + :return: + """ self._reading_multiplier = multiplier @property def rel_set_point(self): - return self._rel_set_point + """ + :return: return relative mode setpoint for Lakeshore 460 + """ + return self._rel_set_points @rel_set_point.setter def rel_set_point(self, setpoint): + """ + :param setpoint: sets the relative mode set point + :return: + """ self._rel_set_point = setpoint @property def max_reading(self): + """ + :return: returns max reading for Lakeshore 460 + """ return self._max_reading @max_reading.setter def max_reading(self, reading): + """ + :param reading: sets max_reading for device + :return: + """ self._max_reading = reading @property def rel_mode_reading(self): """ - :return: Peajk /RMS Field reading + :return: Return relative mode reading """ return self._rel_mode_reading @rel_mode_reading.setter def rel_mode_reading(self, rel_mode): + """ + :param rel_mode: sets relative mode reading + :return: + """ self._rel_mode_reading = rel_mode @property def rel_mode_reading_multiplier(self): """ - :return: Peajk /RMS Field reading + :return: Return relative mode multiplier """ return self._rel_mode_reading_multiplier @rel_mode_reading_multiplier.setter def rel_mode_reading_multiplier(self, multiplier): + """ + :param multiplier: sets relative mode multiplier + :return: + """ self._rel_mode_reading_multiplier = multiplier @property def magnetic_field_multiplier(self): + """ + :return: Returns Magnetic Field Multiplier + """ return self._magnetic_field_multiplier @magnetic_field_multiplier.setter def magnetic_field_multiplier(self, multiplier): + """ + :param multiplier: Sets Magnetic field multiplier i.e micro, kilo, etc. + :return: + """ self._magnetic_field_multiplier = multiplier @property def magnetic_field_reading(self): + """ + :return: Magnetic field Reading + """ return self._magnetic_field_reading @magnetic_field_reading.setter def magnetic_field_reading(self, field): + """ + :param field: sets Magnetic Field Reading + :return: + """ self._magnetic_field_reading = field From 3255ddfdb8033738c23baeb2348506af1d7b5b56 Mon Sep 17 00:00:00 2001 From: Kathryn Baker Date: Mon, 14 Aug 2017 15:35:59 +0100 Subject: [PATCH 0312/1466] Updates as per code review --- lewis_emulators/rknps/device.py | 10 ++++------ lewis_emulators/rknps/interfaces/stream_interface.py | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/rknps/device.py b/lewis_emulators/rknps/device.py index c37338b2..ed6148d6 100644 --- a/lewis_emulators/rknps/device.py +++ b/lewis_emulators/rknps/device.py @@ -22,8 +22,8 @@ def _initialize_data(self): self._cmd = {"001":"REM","002":"LOC"} self._pol = {"001":"+", "002":"-"} self._status = { - "001":[".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".",".","."], - "002":["!",".",".",".",".",".",".",".",".","!",".",".",".",".",".",".",".",".",".",".",".",".",".","."]} + "001":["."]*24, + "002":["."]*24} self._set_curr = {"001":123456,"002":456789} def _get_state_handlers(self): @@ -69,7 +69,7 @@ def ad_curr(self): Returns: value of the output current. """ - return self._curr[self._active_adr] + return int(self._curr[self._active_adr]) @property def ad_volt(self): @@ -78,7 +78,7 @@ def ad_volt(self): Returns: value of the output voltage. """ - return self._volt[self._active_adr] + return int(self._volt[self._active_adr]) @property def cmd(self): @@ -209,7 +209,6 @@ def off(self): self.set_current_values(0) self._status[self._active_adr][0] = "!" - @has_log def set_both_volt_values(self, volt): """ Update the voltage value of both devices. @@ -220,5 +219,4 @@ def set_both_volt_values(self, volt): volt: the voltage value to set. """ for PSU in self._volt.keys(): - self.log.info("setting for %s" % PSU) self._volt[PSU] = float(volt) diff --git a/lewis_emulators/rknps/interfaces/stream_interface.py b/lewis_emulators/rknps/interfaces/stream_interface.py index 0188a4ab..cb24b8b4 100644 --- a/lewis_emulators/rknps/interfaces/stream_interface.py +++ b/lewis_emulators/rknps/interfaces/stream_interface.py @@ -76,7 +76,7 @@ def get_adc(self, channel): # Channel 2 is the AD for the voltage return "{0}".format(self._device.ad_volt) elif channel == "8": - # Channel 0 is the AD for the current + # Channel 8 is the AD for the current return "{0}".format(self._device.ad_curr) # All other channels are not considered under the requirements of this emulator From 6ac3d181c8b9b4917eb426950c78e38644d54cf9 Mon Sep 17 00:00:00 2001 From: Attah Date: Tue, 15 Aug 2017 16:32:38 +0100 Subject: [PATCH 0313/1466] making rework changes --- lewis_emulators/lakeshore460/device.py | 34 +++++-------------- .../interfaces/stream_interface.py | 10 ++---- 2 files changed, 11 insertions(+), 33 deletions(-) diff --git a/lewis_emulators/lakeshore460/device.py b/lewis_emulators/lakeshore460/device.py index 1d8430ed..50f72f1d 100644 --- a/lewis_emulators/lakeshore460/device.py +++ b/lewis_emulators/lakeshore460/device.py @@ -23,7 +23,7 @@ def _initialize_data(self): self._manual_range = 2 self._max_hold = 0 self._rel_mode = 0 - self.rel_mode_reading = 4906 + self._rel_mode_reading = 4906 self._rel_mode_reading_multiplier = "ON" self._total_fields = 7.5 self._source = 1 @@ -80,7 +80,6 @@ def unit(self): def unit(self, unit): """ :param unit: set unit for the device - :return: """ self._unit = unit @@ -95,7 +94,6 @@ def status(self): def status(self,status): """ :param status: sets the device status, if device is on/off - :return: """ self._status = status @@ -108,12 +106,15 @@ def mode(self): @mode.setter def mode(self, mode): - self._mode = mode + """ + :param mode: sets the device mode, if device is set to AC or DC measurement + """ + self._mode = mode @property def prms(self): """ - :return: Peajk /RMS Field reading + :return: Peak /RMS Field reading """ return self._prms @@ -121,7 +122,6 @@ def prms(self): def prms(self, prms): """ :param prms: Sets the value to Peak or RMS - :return: """ self._prms = prms @@ -136,7 +136,6 @@ def filter(self): def filter(self, state): """ :param state: sets filter state (binary) - :return: """ self._filter = state @@ -151,7 +150,6 @@ def max_hold(self): def max_hold(self, max_hold): """ :param max_hold: sets the max hold value - :return: """ self._max_hold = max_hold @@ -166,7 +164,6 @@ def rel_mode(self): def rel_mode(self, rel_mode): """ :param rel_mode: Sets the relative mode reading - :return: """ self._rel_mode = rel_mode @@ -181,7 +178,6 @@ def auto_range(self): def auto_range(self, range): """ :param range: sets the auto range - :return: """ self._auto_range = range @@ -196,7 +192,6 @@ def manual_range(self): def manual_range(self, range): """ :param range: sets the manual range - :return: """ self._manual_range = range @@ -211,7 +206,6 @@ def total_fields(self): def total_fields(self, total): """ :param total: sets the total field value for X Y Z Field - :return: """ self._total_fields = total @@ -226,7 +220,6 @@ def source(self): def source(self, source): """ :param source: Sets magnetic field source XYZ, XYZ - :return: """ self._source = source @@ -241,7 +234,6 @@ def channel(self): def channel(self, channel): """ :param channel: sets channel i.e Channel X, Channel Y - :return: """ self._channel = channel @@ -256,7 +248,6 @@ def display_filter(self): def display_filter(self, percentage): """ :param percentage: sets the percentage for display filter opening - :return: """ self._display_filter = percentage @@ -271,7 +262,6 @@ def filter_points(self): def filter_points(self, points): """ :param points: sets filter points value should be between 1 to 10 - :return: """ self._filter_points = points @@ -285,8 +275,7 @@ def rel_multiplier(self): @rel_multiplier.setter def rel_multiplier(self, multiplier): """ - :param multiplier: - :return: sets the relative multiplier for the device + :param multiplier: sets the relative multiplier for the device """ self._rel_multiplier = multiplier @@ -301,7 +290,6 @@ def reading_multiplier(self): def reading_multiplier(self, multiplier): """ :param multiplier: sets the reading multiplier for the device - :return: """ self._reading_multiplier = multiplier @@ -310,13 +298,12 @@ def rel_set_point(self): """ :return: return relative mode setpoint for Lakeshore 460 """ - return self._rel_set_points + return self._rel_set_point @rel_set_point.setter def rel_set_point(self, setpoint): """ :param setpoint: sets the relative mode set point - :return: """ self._rel_set_point = setpoint @@ -331,7 +318,6 @@ def max_reading(self): def max_reading(self, reading): """ :param reading: sets max_reading for device - :return: """ self._max_reading = reading @@ -346,7 +332,6 @@ def rel_mode_reading(self): def rel_mode_reading(self, rel_mode): """ :param rel_mode: sets relative mode reading - :return: """ self._rel_mode_reading = rel_mode @@ -361,7 +346,6 @@ def rel_mode_reading_multiplier(self): def rel_mode_reading_multiplier(self, multiplier): """ :param multiplier: sets relative mode multiplier - :return: """ self._rel_mode_reading_multiplier = multiplier @@ -376,7 +360,6 @@ def magnetic_field_multiplier(self): def magnetic_field_multiplier(self, multiplier): """ :param multiplier: Sets Magnetic field multiplier i.e micro, kilo, etc. - :return: """ self._magnetic_field_multiplier = multiplier @@ -391,7 +374,6 @@ def magnetic_field_reading(self): def magnetic_field_reading(self, field): """ :param field: sets Magnetic Field Reading - :return: """ self._magnetic_field_reading = field diff --git a/lewis_emulators/lakeshore460/interfaces/stream_interface.py b/lewis_emulators/lakeshore460/interfaces/stream_interface.py index fc58c853..fd642baa 100644 --- a/lewis_emulators/lakeshore460/interfaces/stream_interface.py +++ b/lewis_emulators/lakeshore460/interfaces/stream_interface.py @@ -1,5 +1,5 @@ from lewis.adapters.stream import StreamAdapter -from utils.command_builder import CmdBuilder +from lewis_emulators.utils.command_builder import CmdBuilder from lewis.core.logging import has_log @has_log @@ -49,8 +49,8 @@ class Lakeshore460StreamInterface(StreamAdapter): } def handle_error(self,request, error): - self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) - print("An error occurred at request" + repr(request) + ": " + repr(error)) + self.log.error("An error occurred at request {0} : {1} ").format(request, error) + print("An error occurred at request {0} : {1} ").format(request, error) def get_IDN(self): return "{0}".format(self._device.idn) @@ -120,7 +120,6 @@ def get_source(self): return "{0}".format(self._device.source) def set_channel(self, channel): - self.log.error("My Channel is: {0}".format(self._device.channel)) self._device.channel = channel def get_channel(self): @@ -139,12 +138,9 @@ def get_filter_points(self): return "{0}".format(self._device.filter_points) def read_relative_mode_set_point_multiplier(self): - self.log.error("My Multiplier is: {0}".format(self._device.rel_multiplier)) - return "{0}".format(self._device.rel_multiplier) def read_relative_mode_set_point(self): - return "{0}".format(self._device.rel_set_point) def set_relative_mode_set_point(self, setpoint): From 33cb293636f5b41d51c3971c1671784445f7199e Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Thu, 17 Aug 2017 09:51:18 +0100 Subject: [PATCH 0314/1466] Add readback support for cycle setpoint --- lewis_emulators/instron_stress_rig/device.py | 3 +++ .../instron_stress_rig/interfaces/stream_interface.py | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 5bff426c..184ce5f3 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -236,6 +236,9 @@ def arm_quarter_counter(self): def get_quarter_counts(self): return self._waveform_generator.quart_counter.counts + def get_max_quarter_counts(self): + return self._waveform_generator.quart_counter.max_counts + def set_max_quarter_counts(self, val): self._waveform_generator.quart_counter.max_counts = val diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 136609d3..f28c87f6 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -49,7 +49,8 @@ class InstronStreamInterface(StreamAdapter): # Waveform (quarter counter event detector) commands Cmd("arm_quarter_counter", "^C212,2$"), Cmd("get_quarter_counts", "^Q210$"), - Cmd("set_max_quarter_counts", "^Q209,([0-9]+)$"), + Cmd("get_max_quarter_counts", "^Q209$"), + Cmd("set_max_quarter_counts", "^C209,([0-9]+)$"), Cmd("set_quarter_counter_off", "^C212,0$"), Cmd("get_quarter_counter_status", "^Q212$"), } @@ -179,6 +180,9 @@ def arm_quarter_counter(self): def get_quarter_counts(self): return self._device.get_quarter_counts() + def get_max_quarter_counts(self): + return self._device.get_max_quarter_counts() + def set_max_quarter_counts(self, val): self._device.set_max_quarter_counts(int(val)) From 8412591b76ef789a0d69c6c5ea2b7916e0cb3017 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Fri, 18 Aug 2017 13:58:42 +0100 Subject: [PATCH 0315/1466] Add new line to end of file --- lewis_emulators/instron_stress_rig/channel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/channel.py b/lewis_emulators/instron_stress_rig/channel.py index f7ef6d67..40ab1ba3 100644 --- a/lewis_emulators/instron_stress_rig/channel.py +++ b/lewis_emulators/instron_stress_rig/channel.py @@ -25,4 +25,4 @@ class StrainChannel(Channel): def __init__(self): super(StrainChannel, self).__init__() self.length = 1 - self.channel_type = 4 \ No newline at end of file + self.channel_type = 4 From 15ebb17bb6b8d70daecc00fe7ad350b6af2e1e45 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 23 Aug 2017 14:33:02 +0100 Subject: [PATCH 0316/1466] HFMAGPSU Emulator --- lewis_emulators/HFMAGPSU/__init__.py | 3 + lewis_emulators/HFMAGPSU/__init__.pyc | Bin 0 -> 270 bytes lewis_emulators/HFMAGPSU/device.py | 121 ++++++++++++++++++ lewis_emulators/HFMAGPSU/device.pyc | Bin 0 -> 6404 bytes .../HFMAGPSU/interfaces/__init__.py | 3 + .../HFMAGPSU/interfaces/__init__.pyc | Bin 0 -> 297 bytes .../HFMAGPSU/interfaces/stream_interface.py | 119 +++++++++++++++++ .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 0 -> 7790 bytes lewis_emulators/HFMAGPSU/states.py | 6 + lewis_emulators/HFMAGPSU/states.pyc | Bin 0 -> 527 bytes 10 files changed, 252 insertions(+) create mode 100644 lewis_emulators/HFMAGPSU/__init__.py create mode 100644 lewis_emulators/HFMAGPSU/__init__.pyc create mode 100644 lewis_emulators/HFMAGPSU/device.py create mode 100644 lewis_emulators/HFMAGPSU/device.pyc create mode 100644 lewis_emulators/HFMAGPSU/interfaces/__init__.py create mode 100644 lewis_emulators/HFMAGPSU/interfaces/__init__.pyc create mode 100644 lewis_emulators/HFMAGPSU/interfaces/stream_interface.py create mode 100644 lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc create mode 100644 lewis_emulators/HFMAGPSU/states.py create mode 100644 lewis_emulators/HFMAGPSU/states.pyc diff --git a/lewis_emulators/HFMAGPSU/__init__.py b/lewis_emulators/HFMAGPSU/__init__.py new file mode 100644 index 00000000..6d27d064 --- /dev/null +++ b/lewis_emulators/HFMAGPSU/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedHFMAGPSU + +__all__ = ['SimulatedHFMAGPSU'] diff --git a/lewis_emulators/HFMAGPSU/__init__.pyc b/lewis_emulators/HFMAGPSU/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9b2b9eaafa451a583d95a6e8c49eaa333c69b209 GIT binary patch literal 270 zcmYL^QAz_r42Cni(iTC`1N42LdH@lz)N1>n6qi0|17Yb5GBUF}Y_b)P;aR2A`T`U~?#o3+?bmQS^`-Md@P~ zDsgu8^fG_ml-oBI;kC&7hLOU$gWtPBm9f@DRR~0drf>%Dv|QZcx@Kv+=xV`vN{q|R zdQl>GDK)L&<1lMEY;0PL6^m;zxNpzojw<8n5+w4q7ou7Ek%`W~i;eAqyYG+Z{@d5t EKY}7Y)Bpeg literal 0 HcmV?d00001 diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py new file mode 100644 index 00000000..29ec5723 --- /dev/null +++ b/lewis_emulators/HFMAGPSU/device.py @@ -0,0 +1,121 @@ +from lewis.devices import StateMachineDevice +from collections import OrderedDict +from .states import DefaultState + +class SimulatedHFMAGPSU(StateMachineDevice): + + + def _initialize_data(self): + self._direction = '-' + self._outputMode = 'OFF' + self._rampTarget = 'ZERO' + self._heaterStatus = 'OFF' # off or on + self._heaterValue = 1.0 # V + self._maxTarget = 5.0 + self._midTarget = 2.5 + self._rampRate = 10.0 + self._pause = 'OFF' + self._limit = 10 + self._logMessage = "test" + #self._update = "update field" + + def _get_state_handlers(self): + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + return DefaultState.NAME + + def _get_transition_handlers(self): + return OrderedDict() + + @property + def direction(self): + return self._direction + + @property + def outputMode(self): + return self._outputMode + + @property + def rampTarget(self): + return self._rampTarget + + @property + def heaterStatus(self): + return self._heaterStatus + + @property + def heaterValue(self): + return self._heaterValue + + @property + def maxTarget(self): + return self._maxTarget + + @property + def midTarget(self): + return self._midTarget + + @property + def rampRate(self): + return self._rampRate + + @property + def pause(self): + return self._pause + + @property + def limit(self): + return self._limit + + #@property + #def update(self): + # return self._update + + @property + def logMessage(self): + return self._logMessage + + @direction.setter + def direction(self, d): + self._direction = d + + @outputMode.setter + def outputMode(self, om): + self._outputMode = om + + @rampTarget.setter + def rampTarget(self, rt): + self._rampTarget = rt + + @heaterStatus.setter + def heaterStatus(self, hs): + self._heaterStatus = hs + + @heaterValue.setter + def heaterValue(self, hv): + self._heaterValue = hv + + @maxTarget.setter + def maxTarget(self, mt): + self._maxTarget = mt + + @midTarget.setter + def midTarget(self, mt): + self._midTarget = mt + + @rampRate.setter + def rampRate(self, rate): + self._rampRate = rate + + @pause.setter + def pause(self, p): + self._pause = p + + @limit.setter + def limit(self, lim): + self._limit = lim + + @logMessage.setter + def logMessage(self, lm): + self._logMessage = lm diff --git a/lewis_emulators/HFMAGPSU/device.pyc b/lewis_emulators/HFMAGPSU/device.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da5b45d1bcfb4bf6725505b206c6b2e2381a8565 GIT binary patch literal 6404 zcmc(jTW{k;6vxN8G?!kIbh|gM3yVY$VtD|81c+_v1s2qHHQ}O$NR|~(TNgXFJd<4( zEl=e;@WgjS;)y3X|Km)u-K|t)3A?E$bIv5=-^rXg=Xk1rt+xOE^^c>mnEh4heUmQz zH8no{J0cV!a>URP4&;gmD-7TZ=_WpUYxYb>rZRKS&k%Z#*&3x$)E_Q?noYDw9($ zO1|~d6J^XQ#!nS_aSF$tY6DlFX}!%K3owZF>)@*1fMvl2IRX=PoF zAm^TX6F*cb+o-`NoVihOJT|I9oy?XOOp(`VtOPR9$;z-Os^{lBXnQE+4r>;nL;5XC0IeB`_mr zEb82Ep82uCZ!b0o0pscXIpeZqkB@Wh$WI7_k#$H{R%lhm78~zZqjSDrpraQ@ zg|XwAZjhUuQ~pKv)D*`4GI9Tqo1L@%MfOu&7&|K3A9J&F+JBDyQDN+_5;r~T=k#|@ z{1@4y;(X>c;vVN_=j1B8XO1bA+Et_l|L=!M0x-=*6EZUa`-zHT@h&8fTIEA^W=R`K7bTyX{+u*DK0%!28OUZm6T*R>|_-m!)NscoRz8= zk}s3_C;XG-pXZbE@Ug4mI5OSmy5v&P=rrH~f`MlPrpe$%qYRoHs^UZ|oKX}b(uLyw z_F-|iskT+>*@m)B-0yA8Pb$G6dQ&bdlp)5hPq^Y&SMxdy-lnd{U`6fG^P3amV^;8|Mf`OaByGhdqO16x&eUoa Px@PcvGrmj4ADL!fG|EZj literal 0 HcmV?d00001 diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py new file mode 100644 index 00000000..16b135f1 --- /dev/null +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -0,0 +1,119 @@ +from lewis.adapters.stream import StreamAdapter +from lewis_emulators.utils.command_builder import CmdBuilder + +class HFMAGPSUStreamInterface(StreamAdapter): + + # terminators set to ascii ETX + in_terminator = "\r\n" + out_terminator = "\r\n" + + + + commands = { + CmdBuilder("read_direction").escape("GET SIGN").build(), + CmdBuilder("read_output_mode").escape("GET O").build(), + CmdBuilder("read_ramp_target").escape("R S").build(), + CmdBuilder("read_heater_status").escape("HEATER").build(), + CmdBuilder("read_max_target").escape("GET MAX").build(), + CmdBuilder("read_mid_target").escape("GET MID").build(), + CmdBuilder("read_ramp_rate").escape("RAMP").build(), + CmdBuilder("read_limit").escape("H V").build(), + # CmdBuilder("read_update").escape("U").build(), + CmdBuilder("read_pause").escape("P").build(), + CmdBuilder("read_heater_value").escape("GET H").build(), + + CmdBuilder("write_direction").escape("D ").arg("-|0|\+").build(), # '+' is special character + CmdBuilder("write_output_mode").escape("T ").arg("AMPS|TESLA").build(), + CmdBuilder("write_ramp_target").escape("RAMP ").arg("ZERO|MID|MAX").build(), + CmdBuilder("write_heater_status").escape("H ").arg("OFF|ON").build(), + CmdBuilder("write_heater_value").escape("S H ").float().build(), + CmdBuilder("write_max_target").escape("SET MAX ").float().build(), + CmdBuilder("write_mid_target").escape("SET MID ").float().build(), + CmdBuilder("write_ramp_rate").escape("SET RAMP ").float().build(), + CmdBuilder("write_limit").escape("S L ").float().build(), + CmdBuilder("write_pause").escape("P ").arg("OFF|ON").build(), + } + + def handle_error(self, request, error): + self.log.error("Beep boop. Error occurred at " + repr(request) + ": " + repr(error)) + print("Beep boop. Error occurred at " + repr(request) + ": " + repr(error)) + + def read_direction(self): + return self._device.direction + + def read_output_mode(self): + return self._device.outputMode + + def read_ramp_target(self): + return self._device.rampTarget + + def read_ramp_rate(self): + return self._device.rampRate + + def read_heater_status(self): + return self._device.heaterStatus + + def read_heater_value(self): + return self._device.heaterValue + + def read_max_target(self): + return self._device.maxTarget + + def read_mid_target(self): + return self._device.midTarget + + def read_limit(self): + return self._device.limit + + def read_pause(self): + return self._device.pause + + def write_max_target(self, mt): + self._device.maxTarget = mt + self._device.logMessage = "HH:MM:SS MAX SETTING: [" + str(mt) + "]" + return self._device.logMessage + + def write_mid_target(self, mt): + self._device.midTarget = mt + self._device.logMessage = "HH:MM:SS MID SETTING: [" + str(mt) + "]" + return self._device.logMessage + + def write_ramp_rate(self, rr): + self._device.rampRate = rr + self._device.logMessage = "HH:MM:SS RAMP RATE: [" + str(rr) + "]" + return self._device.logMessage + + def write_direction(self, d): + self._device.direction = d + self._device.logMessage = "HH:MM:SS DIRECTION: [" + str(d) + "]" + return self._device.logMessage + + def write_ramp_target(self, rt): + self._device.rampTarget = rt + self._device.logMessage = "HH:MM:SS RAMP TARGET: [" + str(rt) + "]" + return self._device.logMessage + + def write_heater_status(self, hs): + self._device.heaterStatus = hs + self._device.logMessage = "HH:MM:SS HEATER STATUS: [" + str(hs) + "]" + return self._device.logMessage + + def write_heater_value(self, hv): + self._device.heaterValue = hv + self._device.logMessage = "HH:MM:SS HEATER OUTPUT: [" + str(hv) + "]" + return self._device.logMessage + + def write_output_mode(self, om): + self._device.outputMode = om + self._device.logMessage = "HH:MM:SS UNITS: [" + str(om) + "]" + return self._device.logMessage + + def write_limit(self, l): + self._device.limit = l + self._device.logMessage = "HH:MM:SS VOLTAGE LIMIT: [" + str(l) + "]" + return self._device.logMessage + + def write_pause(self, p): + self._device.pause = p + self._device.logMessage = "HH:MM:SS PAUSE STATUS: [" + str(p) + "]" + return self._device.logMessage diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7eed44639c53b6eb68ee7c2807aaf7cde1cafa21 GIT binary patch literal 7790 zcmd5>dvnu95I@;TAR&POfrPhso0p-qv@}qPNlfgS*hx=LrWkir;kyuZEZfzcq3!rT z`Zf9~`vKbBmCv!L!c4~#>^LW{_Ex&x-|g+~t0%RPcf7jnD|iZIUO-bRP*pP2 z3)HHn=nDG4_!j$Mv0o-pS0tuTG32KWeA`?%DSYqNS5oehAj)=2NJR{;96WUG0T$ zrB<-Y7`GjL!aTbxJy?^j(w^=pjqALS&sFkVA2Skx_nUe85b$vLQtroreqa2jzqq=m zJq;hiD0%`O=eg3R#y~-qHT=Mjlq%TUd+fH?QBq^5+0@64Y){j7UZb;TW`Xp0ANpDj zz?6b#R&9mW-0ME**6z?-BB?>JP_AMtR0q^@x|O`Mo*R%IL=J&PNx36gnCS35BI)Aan&9G zq>sFs?!AHx&`01d8YRL%8h+g#=%5)O12*kCIt@0ITY;vEaOpxsfPaYB7Sk!#r7*n+ zL3$DIC{`~*{Ix#Oy;cxlSq5KSRu|yw;E?fq9{36DBEGwkE$x!*wN^+q5KtGM)Pw(XP`wF)dXr~xmIi{9d-iQ)U{l@ zt!nwr;)+vKopu}ET3eOx{JP8sorb4dVJ+|!ESFkCzW0?Ydt6mB+g!~zJDjS)TVaEA z`or9~x}^1WxL2qvvocm49o{s;86C5avqPI~IfGtBX1RiN(>S^P5J5s5 z!5pcj94F`&G@3Q`e{AD6JVvn~TB;=oYvW=SPSxEZt-@7Vp=dlVR^cMLKZI2ne$Xlo zL4I6p!lmy~UyPSgi1f1|jKW|;X~(#@93ns-AWRsr;g(Ek#RDK9UyCve zHz9-T^`uSmk?v<{bfzyXmrBcygU0|H64OetytZt=rhNUU3~rIMh4Y(0QKAN!5bfuX zu$81z-djmY3}q>hEW*mwqfJ(@3b6)_P7KVNwa!}ggF_9H^gY!09T;~2Gz5ZUiox$; z2Wmk$@hsj#8(H$oPs8Br&<+YEoP~n%Ejp=iIEjL1eT>mH5KO{|d?^e^ixRGoA3hgK zt3{q)sT4QL2`F9O4<$Teh|z+CrHkzY1SuA#$dxKN4wZ`pn6B;z6E3}Q#DNLRG_Ma3 zr!(;G{*sFdKekiJRkoZ2r0}r1@3PxfqxC4P!Tb#Epn_8pQX5;9&8;M#y75V*-i;Qi z1yD{NAW~Sc>-*GJxmZc^r7NF8s1=MBA+!K$Tc5|5wl~%*xwX8#UMv-pdho?1?JGfG;W|DRgSYA?Kf1mH!UtroeXJON$=6aM{e^p Q^2%rEZd9+Hx8_s-0uJsA9smFU literal 0 HcmV?d00001 diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py new file mode 100644 index 00000000..e04bc7a3 --- /dev/null +++ b/lewis_emulators/HFMAGPSU/states.py @@ -0,0 +1,6 @@ +from lewis.core.statemachine import State + +class DefaultState(State): + + # default state + NAME = 'Default' diff --git a/lewis_emulators/HFMAGPSU/states.pyc b/lewis_emulators/HFMAGPSU/states.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1c0607bca8614ff31c39dda59b7047ff74bf37f4 GIT binary patch literal 527 zcmcgo%}&EG40hV_qX`f3Kn&^1HOTR;V11Z)L}JOC6>2*^YD0C)%3Yy-$A zkVZ$GUjFck1bf!e)M|_NjJ+imy05Gc{B?494}9o?0l_ukeq1j}hZ-m2%gD*HrwmHf z+MrT)>WOLczH)TJbUlBJBepOCX{L)CxvI%_y}{bbdE1hVpH_=hQs1^sXXO%K%M4@F zSK2n6G@2}Svck8L6ds}^cV$tktlKw6zo?daN$!I*Yxjx!i<3wJ!*RGC^jd6&WZQJFN0qon1`YO literal 0 HcmV?d00001 From f3acfaf7b88ca658d5e3d24cdd8f6f42762d21ef Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 23 Aug 2017 18:25:34 +0100 Subject: [PATCH 0317/1466] Fix bug --- .../instron_stress_rig/quarter_cycle_event_detector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py index 600c5d5a..ec0e00c6 100644 --- a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py +++ b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py @@ -19,7 +19,7 @@ def count(self): self.counts += 1 # This is intentionally == not >=. The counter won't trip if it already exceeds max_counts if self.counts == self.max_counts: - self.state == QCEDStates.TRIPPED + self.state = QCEDStates.TRIPPED def off(self): self.state = QCEDStates.OFF From 24d90ef5a37ab4e3c8066da66d30e330c3e83dfb Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Wed, 30 Aug 2017 11:37:17 +0100 Subject: [PATCH 0318/1466] Add skeleton emulator --- lewis_emulators/eurotherm/__init__.py | 3 ++ lewis_emulators/eurotherm/device.py | 36 +++++++++++++++++++ .../eurotherm/interfaces/__init__.py | 3 ++ .../eurotherm/interfaces/stream_interface.py | 26 ++++++++++++++ lewis_emulators/eurotherm/states.py | 8 +++++ 5 files changed, 76 insertions(+) create mode 100644 lewis_emulators/eurotherm/__init__.py create mode 100644 lewis_emulators/eurotherm/device.py create mode 100644 lewis_emulators/eurotherm/interfaces/__init__.py create mode 100644 lewis_emulators/eurotherm/interfaces/stream_interface.py create mode 100644 lewis_emulators/eurotherm/states.py diff --git a/lewis_emulators/eurotherm/__init__.py b/lewis_emulators/eurotherm/__init__.py new file mode 100644 index 00000000..87d46bf2 --- /dev/null +++ b/lewis_emulators/eurotherm/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedAmint2l + +__all__ = ['SimulatedAmint2l'] diff --git a/lewis_emulators/eurotherm/device.py b/lewis_emulators/eurotherm/device.py new file mode 100644 index 00000000..8021e0d8 --- /dev/null +++ b/lewis_emulators/eurotherm/device.py @@ -0,0 +1,36 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedEurotherm(StateMachineDevice): + """ + Simulated Eurotherm temperature sensor. + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self._pressure = 2.0 + self.address = "AB" + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() + diff --git a/lewis_emulators/eurotherm/interfaces/__init__.py b/lewis_emulators/eurotherm/interfaces/__init__.py new file mode 100644 index 00000000..caaff951 --- /dev/null +++ b/lewis_emulators/eurotherm/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import EurothermStreamInterface + +__all__ = ['EurothermStreamInterface'] diff --git a/lewis_emulators/eurotherm/interfaces/stream_interface.py b/lewis_emulators/eurotherm/interfaces/stream_interface.py new file mode 100644 index 00000000..62a3ec1b --- /dev/null +++ b/lewis_emulators/eurotherm/interfaces/stream_interface.py @@ -0,0 +1,26 @@ +from lewis.adapters.stream import StreamAdapter +from lewis_emulators.utils.command_builder import CmdBuilder + + +class EurothermStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + } + + in_terminator = chr(3) + out_terminator = chr(3) + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + diff --git a/lewis_emulators/eurotherm/states.py b/lewis_emulators/eurotherm/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/eurotherm/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' From 770dec02e3a49a16958cd8219fe09a988ee0504a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 31 Aug 2017 08:56:25 +0100 Subject: [PATCH 0319/1466] Modify protocol to be in line with real device --- lewis_emulators/instron_stress_rig/device.py | 6 +----- .../instron_stress_rig/interfaces/stream_interface.py | 11 +++-------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lewis_emulators/instron_stress_rig/device.py b/lewis_emulators/instron_stress_rig/device.py index 184ce5f3..8d30a999 100644 --- a/lewis_emulators/instron_stress_rig/device.py +++ b/lewis_emulators/instron_stress_rig/device.py @@ -77,11 +77,7 @@ def get_control_channel(self): def set_control_channel(self, channel): self.control_channel = channel - def get_watchdog_status(self): - return self._watchdog_status - - def set_watchdog_status(self, enabled, status): - self._watchdog_status = (enabled, status) + def disable_watchdog(self): self.watchdog_refresh_time = self.current_time def get_control_mode(self): diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index f28c87f6..5c3096b7 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -7,8 +7,7 @@ class InstronStreamInterface(StreamAdapter): commands = { Cmd("get_control_channel", "^Q300$"), Cmd("set_control_channel", "^C300,([1-3])$"), - Cmd("get_watchdog_status", "^Q904$"), - Cmd("set_watchdog_status", "^C904,([0-2]),([0-3])$"), + Cmd("disable_watchdog", "^C904,0$"), Cmd("get_control_mode", "^Q909$"), Cmd("set_control_mode", "^P909,([0-1])$"), Cmd("get_status", "^Q22$"), @@ -68,12 +67,8 @@ def get_control_channel(self): def set_control_channel(self, channel): self._device.set_control_channel(int(channel)) - def get_watchdog_status(self): - enabled, status = self._device.get_watchdog_status() - return "{},{}".format(enabled, status) - - def set_watchdog_status(self, cv1, cv2): - self._device.set_watchdog_status(int(cv1), int(cv2)) + def disable_watchdog(self): + self._device.disable_watchdog() def get_control_mode(self): return self._device.get_control_mode() From 9a9ad395da0e5b7e37a77c07aa86aabc0cc74f2d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 31 Aug 2017 13:17:16 +0100 Subject: [PATCH 0320/1466] Make emulator act like real device --- .../instron_stress_rig/quarter_cycle_event_detector.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py index ec0e00c6..8805db3a 100644 --- a/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py +++ b/lewis_emulators/instron_stress_rig/quarter_cycle_event_detector.py @@ -13,6 +13,7 @@ def reset(self): def arm(self): self.state = QCEDStates.ARMED + self.counts = 0 def count(self): if self.state == QCEDStates.ARMED: @@ -23,7 +24,8 @@ def count(self): def off(self): self.state = QCEDStates.OFF - self.counts = 0 + # When the QCED is turned off it does not automatically reset it's count + # So don't do self.counts = 0 here. def cycles(self, fractional=True): if fractional: From 31fa6c9bd1487df6bae0e4baabba3e2d07fc0ef6 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Thu, 31 Aug 2017 16:13:20 +0100 Subject: [PATCH 0321/1466] Add HLG emulator --- .../amint2l/interfaces/stream_interface.py | 10 +- lewis_emulators/hlg/__init__.py | 3 + lewis_emulators/hlg/device.py | 36 ++++++ lewis_emulators/hlg/interfaces/__init__.py | 3 + .../hlg/interfaces/stream_interface.py | 106 ++++++++++++++++++ lewis_emulators/hlg/states.py | 8 ++ lewis_emulators/utils/command_builder.py | 24 ++++ 7 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 lewis_emulators/hlg/__init__.py create mode 100644 lewis_emulators/hlg/device.py create mode 100644 lewis_emulators/hlg/interfaces/__init__.py create mode 100644 lewis_emulators/hlg/interfaces/stream_interface.py create mode 100644 lewis_emulators/hlg/states.py diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py index 4235f880..180f27c2 100644 --- a/lewis_emulators/amint2l/interfaces/stream_interface.py +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -1,7 +1,9 @@ from lewis.adapters.stream import StreamAdapter -from lewis_emulators.utils.command_builder import CmdBuilder +from lewis.core.logging import has_log +from lewis_emulators.utils.command_builder import CmdBuilder +@has_log class Amint2lStreamInterface(StreamAdapter): """ Stream interface for the serial port @@ -23,7 +25,7 @@ def handle_error(self, request, error): error: problem """ - print "An error occurred at request " + repr(request) + ": " + repr(error) + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) def get_pressure(self, address): @@ -35,9 +37,9 @@ def get_pressure(self, address): """ if address.upper() != self._device.address.upper(): - print "unknown address {0}".format(address) + self.log.error("unknown address {0}".format(address)) return None - print str(self._device.pressure) + self.log.info("Pressure: {0}".format(self._device.pressure)) if self._device.pressure is None: return None else: diff --git a/lewis_emulators/hlg/__init__.py b/lewis_emulators/hlg/__init__.py new file mode 100644 index 00000000..841f916d --- /dev/null +++ b/lewis_emulators/hlg/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedHgl + +__all__ = ['SimulatedHgl'] diff --git a/lewis_emulators/hlg/device.py b/lewis_emulators/hlg/device.py new file mode 100644 index 00000000..c7e8e29c --- /dev/null +++ b/lewis_emulators/hlg/device.py @@ -0,0 +1,36 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedHgl(StateMachineDevice): + """ + Simulated AM Int2-L pressure transducer. + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self.level = 2.0 + self.verbosity = 0 + self.prefix = 1 + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() diff --git a/lewis_emulators/hlg/interfaces/__init__.py b/lewis_emulators/hlg/interfaces/__init__.py new file mode 100644 index 00000000..8204c019 --- /dev/null +++ b/lewis_emulators/hlg/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import HlgStreamInterface + +__all__ = ['HlgStreamInterface'] diff --git a/lewis_emulators/hlg/interfaces/stream_interface.py b/lewis_emulators/hlg/interfaces/stream_interface.py new file mode 100644 index 00000000..387575dc --- /dev/null +++ b/lewis_emulators/hlg/interfaces/stream_interface.py @@ -0,0 +1,106 @@ +from lewis.adapters.stream import StreamAdapter +from lewis.core.logging import has_log + +from lewis_emulators.utils.command_builder import CmdBuilder + +PREFIXES = [ + "", # 0 + "\r\n", # 1 + "\r\n ", # 2 + "\r\n01:23:45 ", # 3 + "\r\n -------> ", # 4 + ", ", # 5 +] + + +@has_log +class HlgStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + CmdBuilder("get_level").escape("PM").build(), + CmdBuilder("set_verbosity").escape("CV").int().build(), + CmdBuilder("set_prefix").escape("CP").int().build() + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def set_verbosity(self, verbosity): + """ + Set the verbosity of the output from the device + + Args: + verbosity: 0 normal, 1 labview style (more verbose) + + Returns: confirmation message + + """ + verbosity_as_int = int(verbosity) + if verbosity_as_int != 0 and verbosity_as_int != 1: + raise AssertionError("Verbosity must be 0 or 1 was '{0}'".format(verbosity)) + self._device.verbosity = verbosity_as_int + if verbosity_as_int == 0: + out_verbose = "Normal" + else: + out_verbose = "labVIEW" + return self._format_output("CV{0}".format(verbosity_as_int), "Verbose=", out_verbose) + + def set_prefix(self, prefix): + """ + Set the prefix the device returns + Args: + prefix: prefix id 0-5 see PREFIXES for details + + Returns: confirmation message + + """ + prefix_as_int = int(prefix) + if prefix_as_int < 0 or prefix_as_int >= len(PREFIXES): + raise AssertionError("Prefix must be between 0 and 5 '{0}'".format(prefix)) + self._device.prefix = prefix_as_int + return self._format_output("CP{0}".format(prefix_as_int), "Verbose=", prefix) + + def get_level(self): + + """ + Gets the current level + + Returns: level in correct units + + """ + if self._device.level is None: + return None + else: + return self._format_output("PM", "Probe value=", "{level:.3f} mm".format(level=self._device.level)) + + def _format_output(self, echo, verbose_prefix, data): + """ + Format the output of a command depending on verbosity and prefix settings of device + Args: + echo: string to echo back to user + verbose_prefix: prefix for value in normal verbose mode + data: data to output + + Returns: formatted output from command + + """ + output_string = echo + output_string += PREFIXES[self._device.prefix] + if self._device.verbosity == 0: + output_string += verbose_prefix + output_string += data + return output_string diff --git a/lewis_emulators/hlg/states.py b/lewis_emulators/hlg/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/hlg/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 52c4b367..29bdc15f 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -6,6 +6,22 @@ class CmdBuilder(object): """ Build a command for the stream adapter. + + Do this by creating this object, adding the values and then building it (this uses a fluent interface). + + For example to read a pressure the ioc might send "pres?" and when that happens this should call get_pres + command would be: + >>> CmdBuilder("get_pres").escape("pres?").build() + This will generate the regex needed by Lewis. The escape is just making sure none of the characters are special + reg ex characters. + If you wanted to set a pressure the ioc might send "pres " where is a floating point number, + the interface should call set_pres with that number. Now use: + >>> CmdBuilder("set_pres").escape("pres ").float().build() + this add float as a regularly expression capture group for your argument. It is equivalent to: + >>> Cmd("set_pres", r"pres ([+-]?\d+\.?\d*)") + There are various arguments like int and digit. Finally some special characters are included so if your protocol uses + enquirey character ascii 5 you can match is using + >>> CmdBuilder("set_pres").escape("pres?").enq().build() """ def __init__(self, target_method, arg_sep=",", ignore=""): @@ -62,6 +78,14 @@ def digit(self): """ return self.arg(r"\d") + def char(self): + """ + Add a single character argument. + + :return: builder + """ + return self.arg(r".") + def int(self): """ Add an integer argument. From bbb78d1df5f26dda32a1b933e9e3799a4e51fd6f Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Fri, 1 Sep 2017 16:58:37 +0100 Subject: [PATCH 0322/1466] Revert "Add HLG emulator" This reverts commit 31fa6c9bd1487df6bae0e4baabba3e2d07fc0ef6. --- .../amint2l/interfaces/stream_interface.py | 10 +- lewis_emulators/hlg/__init__.py | 3 - lewis_emulators/hlg/device.py | 36 ------ lewis_emulators/hlg/interfaces/__init__.py | 3 - .../hlg/interfaces/stream_interface.py | 106 ------------------ lewis_emulators/hlg/states.py | 8 -- lewis_emulators/utils/command_builder.py | 24 ---- 7 files changed, 4 insertions(+), 186 deletions(-) delete mode 100644 lewis_emulators/hlg/__init__.py delete mode 100644 lewis_emulators/hlg/device.py delete mode 100644 lewis_emulators/hlg/interfaces/__init__.py delete mode 100644 lewis_emulators/hlg/interfaces/stream_interface.py delete mode 100644 lewis_emulators/hlg/states.py diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py index 180f27c2..4235f880 100644 --- a/lewis_emulators/amint2l/interfaces/stream_interface.py +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -1,9 +1,7 @@ from lewis.adapters.stream import StreamAdapter -from lewis.core.logging import has_log - from lewis_emulators.utils.command_builder import CmdBuilder -@has_log + class Amint2lStreamInterface(StreamAdapter): """ Stream interface for the serial port @@ -25,7 +23,7 @@ def handle_error(self, request, error): error: problem """ - self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + print "An error occurred at request " + repr(request) + ": " + repr(error) def get_pressure(self, address): @@ -37,9 +35,9 @@ def get_pressure(self, address): """ if address.upper() != self._device.address.upper(): - self.log.error("unknown address {0}".format(address)) + print "unknown address {0}".format(address) return None - self.log.info("Pressure: {0}".format(self._device.pressure)) + print str(self._device.pressure) if self._device.pressure is None: return None else: diff --git a/lewis_emulators/hlg/__init__.py b/lewis_emulators/hlg/__init__.py deleted file mode 100644 index 841f916d..00000000 --- a/lewis_emulators/hlg/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .device import SimulatedHgl - -__all__ = ['SimulatedHgl'] diff --git a/lewis_emulators/hlg/device.py b/lewis_emulators/hlg/device.py deleted file mode 100644 index c7e8e29c..00000000 --- a/lewis_emulators/hlg/device.py +++ /dev/null @@ -1,36 +0,0 @@ -from collections import OrderedDict - -from lewis.devices import StateMachineDevice -from .states import DefaultState - - -class SimulatedHgl(StateMachineDevice): - """ - Simulated AM Int2-L pressure transducer. - """ - - def _initialize_data(self): - """ - Sets the initial state of the device. - """ - self.level = 2.0 - self.verbosity = 0 - self.prefix = 1 - - def _get_state_handlers(self): - """ - Returns: states and their names - """ - return {DefaultState.NAME: DefaultState()} - - def _get_initial_state(self): - """ - Returns: the name of the initial state - """ - return DefaultState.NAME - - def _get_transition_handlers(self): - """ - Returns: the state transitions - """ - return OrderedDict() diff --git a/lewis_emulators/hlg/interfaces/__init__.py b/lewis_emulators/hlg/interfaces/__init__.py deleted file mode 100644 index 8204c019..00000000 --- a/lewis_emulators/hlg/interfaces/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .stream_interface import HlgStreamInterface - -__all__ = ['HlgStreamInterface'] diff --git a/lewis_emulators/hlg/interfaces/stream_interface.py b/lewis_emulators/hlg/interfaces/stream_interface.py deleted file mode 100644 index 387575dc..00000000 --- a/lewis_emulators/hlg/interfaces/stream_interface.py +++ /dev/null @@ -1,106 +0,0 @@ -from lewis.adapters.stream import StreamAdapter -from lewis.core.logging import has_log - -from lewis_emulators.utils.command_builder import CmdBuilder - -PREFIXES = [ - "", # 0 - "\r\n", # 1 - "\r\n ", # 2 - "\r\n01:23:45 ", # 3 - "\r\n -------> ", # 4 - ", ", # 5 -] - - -@has_log -class HlgStreamInterface(StreamAdapter): - """ - Stream interface for the serial port - """ - - commands = { - CmdBuilder("get_level").escape("PM").build(), - CmdBuilder("set_verbosity").escape("CV").int().build(), - CmdBuilder("set_prefix").escape("CP").int().build() - } - - in_terminator = "\r\n" - out_terminator = "\r\n" - - def handle_error(self, request, error): - """ - If command is not recognised print and error - - Args: - request: requested string - error: problem - - """ - self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) - - def set_verbosity(self, verbosity): - """ - Set the verbosity of the output from the device - - Args: - verbosity: 0 normal, 1 labview style (more verbose) - - Returns: confirmation message - - """ - verbosity_as_int = int(verbosity) - if verbosity_as_int != 0 and verbosity_as_int != 1: - raise AssertionError("Verbosity must be 0 or 1 was '{0}'".format(verbosity)) - self._device.verbosity = verbosity_as_int - if verbosity_as_int == 0: - out_verbose = "Normal" - else: - out_verbose = "labVIEW" - return self._format_output("CV{0}".format(verbosity_as_int), "Verbose=", out_verbose) - - def set_prefix(self, prefix): - """ - Set the prefix the device returns - Args: - prefix: prefix id 0-5 see PREFIXES for details - - Returns: confirmation message - - """ - prefix_as_int = int(prefix) - if prefix_as_int < 0 or prefix_as_int >= len(PREFIXES): - raise AssertionError("Prefix must be between 0 and 5 '{0}'".format(prefix)) - self._device.prefix = prefix_as_int - return self._format_output("CP{0}".format(prefix_as_int), "Verbose=", prefix) - - def get_level(self): - - """ - Gets the current level - - Returns: level in correct units - - """ - if self._device.level is None: - return None - else: - return self._format_output("PM", "Probe value=", "{level:.3f} mm".format(level=self._device.level)) - - def _format_output(self, echo, verbose_prefix, data): - """ - Format the output of a command depending on verbosity and prefix settings of device - Args: - echo: string to echo back to user - verbose_prefix: prefix for value in normal verbose mode - data: data to output - - Returns: formatted output from command - - """ - output_string = echo - output_string += PREFIXES[self._device.prefix] - if self._device.verbosity == 0: - output_string += verbose_prefix - output_string += data - return output_string diff --git a/lewis_emulators/hlg/states.py b/lewis_emulators/hlg/states.py deleted file mode 100644 index c2beedfb..00000000 --- a/lewis_emulators/hlg/states.py +++ /dev/null @@ -1,8 +0,0 @@ -from lewis.core.statemachine import State - - -class DefaultState(State): - """ - Device is in default state. - """ - NAME = 'Default' diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 29bdc15f..52c4b367 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -6,22 +6,6 @@ class CmdBuilder(object): """ Build a command for the stream adapter. - - Do this by creating this object, adding the values and then building it (this uses a fluent interface). - - For example to read a pressure the ioc might send "pres?" and when that happens this should call get_pres - command would be: - >>> CmdBuilder("get_pres").escape("pres?").build() - This will generate the regex needed by Lewis. The escape is just making sure none of the characters are special - reg ex characters. - If you wanted to set a pressure the ioc might send "pres " where is a floating point number, - the interface should call set_pres with that number. Now use: - >>> CmdBuilder("set_pres").escape("pres ").float().build() - this add float as a regularly expression capture group for your argument. It is equivalent to: - >>> Cmd("set_pres", r"pres ([+-]?\d+\.?\d*)") - There are various arguments like int and digit. Finally some special characters are included so if your protocol uses - enquirey character ascii 5 you can match is using - >>> CmdBuilder("set_pres").escape("pres?").enq().build() """ def __init__(self, target_method, arg_sep=",", ignore=""): @@ -78,14 +62,6 @@ def digit(self): """ return self.arg(r"\d") - def char(self): - """ - Add a single character argument. - - :return: builder - """ - return self.arg(r".") - def int(self): """ Add an integer argument. From 087e0837f0c3cb3700d601533d53af0e6ac4738f Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Thu, 31 Aug 2017 16:13:20 +0100 Subject: [PATCH 0323/1466] Add HLG emulator --- .../amint2l/interfaces/stream_interface.py | 10 +- lewis_emulators/hlg/__init__.py | 3 + lewis_emulators/hlg/device.py | 36 ++++++ lewis_emulators/hlg/interfaces/__init__.py | 3 + .../hlg/interfaces/stream_interface.py | 106 ++++++++++++++++++ lewis_emulators/hlg/states.py | 8 ++ lewis_emulators/utils/command_builder.py | 24 ++++ 7 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 lewis_emulators/hlg/__init__.py create mode 100644 lewis_emulators/hlg/device.py create mode 100644 lewis_emulators/hlg/interfaces/__init__.py create mode 100644 lewis_emulators/hlg/interfaces/stream_interface.py create mode 100644 lewis_emulators/hlg/states.py diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py index 4235f880..180f27c2 100644 --- a/lewis_emulators/amint2l/interfaces/stream_interface.py +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -1,7 +1,9 @@ from lewis.adapters.stream import StreamAdapter -from lewis_emulators.utils.command_builder import CmdBuilder +from lewis.core.logging import has_log +from lewis_emulators.utils.command_builder import CmdBuilder +@has_log class Amint2lStreamInterface(StreamAdapter): """ Stream interface for the serial port @@ -23,7 +25,7 @@ def handle_error(self, request, error): error: problem """ - print "An error occurred at request " + repr(request) + ": " + repr(error) + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) def get_pressure(self, address): @@ -35,9 +37,9 @@ def get_pressure(self, address): """ if address.upper() != self._device.address.upper(): - print "unknown address {0}".format(address) + self.log.error("unknown address {0}".format(address)) return None - print str(self._device.pressure) + self.log.info("Pressure: {0}".format(self._device.pressure)) if self._device.pressure is None: return None else: diff --git a/lewis_emulators/hlg/__init__.py b/lewis_emulators/hlg/__init__.py new file mode 100644 index 00000000..841f916d --- /dev/null +++ b/lewis_emulators/hlg/__init__.py @@ -0,0 +1,3 @@ +from .device import SimulatedHgl + +__all__ = ['SimulatedHgl'] diff --git a/lewis_emulators/hlg/device.py b/lewis_emulators/hlg/device.py new file mode 100644 index 00000000..c7e8e29c --- /dev/null +++ b/lewis_emulators/hlg/device.py @@ -0,0 +1,36 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedHgl(StateMachineDevice): + """ + Simulated AM Int2-L pressure transducer. + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self.level = 2.0 + self.verbosity = 0 + self.prefix = 1 + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() diff --git a/lewis_emulators/hlg/interfaces/__init__.py b/lewis_emulators/hlg/interfaces/__init__.py new file mode 100644 index 00000000..8204c019 --- /dev/null +++ b/lewis_emulators/hlg/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import HlgStreamInterface + +__all__ = ['HlgStreamInterface'] diff --git a/lewis_emulators/hlg/interfaces/stream_interface.py b/lewis_emulators/hlg/interfaces/stream_interface.py new file mode 100644 index 00000000..387575dc --- /dev/null +++ b/lewis_emulators/hlg/interfaces/stream_interface.py @@ -0,0 +1,106 @@ +from lewis.adapters.stream import StreamAdapter +from lewis.core.logging import has_log + +from lewis_emulators.utils.command_builder import CmdBuilder + +PREFIXES = [ + "", # 0 + "\r\n", # 1 + "\r\n ", # 2 + "\r\n01:23:45 ", # 3 + "\r\n -------> ", # 4 + ", ", # 5 +] + + +@has_log +class HlgStreamInterface(StreamAdapter): + """ + Stream interface for the serial port + """ + + commands = { + CmdBuilder("get_level").escape("PM").build(), + CmdBuilder("set_verbosity").escape("CV").int().build(), + CmdBuilder("set_prefix").escape("CP").int().build() + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def set_verbosity(self, verbosity): + """ + Set the verbosity of the output from the device + + Args: + verbosity: 0 normal, 1 labview style (more verbose) + + Returns: confirmation message + + """ + verbosity_as_int = int(verbosity) + if verbosity_as_int != 0 and verbosity_as_int != 1: + raise AssertionError("Verbosity must be 0 or 1 was '{0}'".format(verbosity)) + self._device.verbosity = verbosity_as_int + if verbosity_as_int == 0: + out_verbose = "Normal" + else: + out_verbose = "labVIEW" + return self._format_output("CV{0}".format(verbosity_as_int), "Verbose=", out_verbose) + + def set_prefix(self, prefix): + """ + Set the prefix the device returns + Args: + prefix: prefix id 0-5 see PREFIXES for details + + Returns: confirmation message + + """ + prefix_as_int = int(prefix) + if prefix_as_int < 0 or prefix_as_int >= len(PREFIXES): + raise AssertionError("Prefix must be between 0 and 5 '{0}'".format(prefix)) + self._device.prefix = prefix_as_int + return self._format_output("CP{0}".format(prefix_as_int), "Verbose=", prefix) + + def get_level(self): + + """ + Gets the current level + + Returns: level in correct units + + """ + if self._device.level is None: + return None + else: + return self._format_output("PM", "Probe value=", "{level:.3f} mm".format(level=self._device.level)) + + def _format_output(self, echo, verbose_prefix, data): + """ + Format the output of a command depending on verbosity and prefix settings of device + Args: + echo: string to echo back to user + verbose_prefix: prefix for value in normal verbose mode + data: data to output + + Returns: formatted output from command + + """ + output_string = echo + output_string += PREFIXES[self._device.prefix] + if self._device.verbosity == 0: + output_string += verbose_prefix + output_string += data + return output_string diff --git a/lewis_emulators/hlg/states.py b/lewis_emulators/hlg/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/hlg/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 52c4b367..29bdc15f 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -6,6 +6,22 @@ class CmdBuilder(object): """ Build a command for the stream adapter. + + Do this by creating this object, adding the values and then building it (this uses a fluent interface). + + For example to read a pressure the ioc might send "pres?" and when that happens this should call get_pres + command would be: + >>> CmdBuilder("get_pres").escape("pres?").build() + This will generate the regex needed by Lewis. The escape is just making sure none of the characters are special + reg ex characters. + If you wanted to set a pressure the ioc might send "pres " where is a floating point number, + the interface should call set_pres with that number. Now use: + >>> CmdBuilder("set_pres").escape("pres ").float().build() + this add float as a regularly expression capture group for your argument. It is equivalent to: + >>> Cmd("set_pres", r"pres ([+-]?\d+\.?\d*)") + There are various arguments like int and digit. Finally some special characters are included so if your protocol uses + enquirey character ascii 5 you can match is using + >>> CmdBuilder("set_pres").escape("pres?").enq().build() """ def __init__(self, target_method, arg_sep=",", ignore=""): @@ -62,6 +78,14 @@ def digit(self): """ return self.arg(r"\d") + def char(self): + """ + Add a single character argument. + + :return: builder + """ + return self.arg(r".") + def int(self): """ Add an integer argument. From 767d35c49a0cc100856341f31b747a4154f61428 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Fri, 1 Sep 2017 17:07:19 +0100 Subject: [PATCH 0324/1466] Add eurotherm tests --- lewis_emulators/eurotherm/__init__.py | 4 +- lewis_emulators/eurotherm/device.py | 52 +++++++++++++++++-- .../eurotherm/interfaces/stream_interface.py | 26 ++++++++-- lewis_emulators/eurotherm/states.py | 2 + lewis_emulators/utils/command_builder.py | 8 +++ 5 files changed, 83 insertions(+), 9 deletions(-) diff --git a/lewis_emulators/eurotherm/__init__.py b/lewis_emulators/eurotherm/__init__.py index 87d46bf2..49281812 100644 --- a/lewis_emulators/eurotherm/__init__.py +++ b/lewis_emulators/eurotherm/__init__.py @@ -1,3 +1,3 @@ -from .device import SimulatedAmint2l +from .device import SimulatedEurotherm -__all__ = ['SimulatedAmint2l'] +__all__ = ['SimulatedEurotherm'] diff --git a/lewis_emulators/eurotherm/device.py b/lewis_emulators/eurotherm/device.py index 8021e0d8..4a747930 100644 --- a/lewis_emulators/eurotherm/device.py +++ b/lewis_emulators/eurotherm/device.py @@ -13,14 +13,20 @@ def _initialize_data(self): """ Sets the initial state of the device. """ - self._pressure = 2.0 - self.address = "AB" + self._current_temperature = 0.0 + self._setpoint_temperature = 0.0 + self._ramp_setpoint_temperature = 0.0 + self._ramping_on = False + self._ramp_rate = 1.0 + self._address = "A1" def _get_state_handlers(self): """ Returns: states and their names """ - return {DefaultState.NAME: DefaultState()} + return { + DefaultState.NAME: DefaultState() + } def _get_initial_state(self): """ @@ -34,3 +40,43 @@ def _get_transition_handlers(self): """ return OrderedDict() + @property + def address(self): + self._address + + @address.setter + def address(self, addr): + self._address = addr + + @property + def current_temperature(self): + return self._current_temperature + + @current_temperature.setter + def current_temperature(self, temp): + self._current_temperature = temp + + @property + def ramping_on(self): + return self._ramping_on + + @ramping_on.setter + def ramping_on(self, toggle): + self._ramping_on = toggle + + @property + def ramp_rate(self): + return self._ramp_rate + + @ramp_rate.setter + def ramp_rate(self, ramp_rate): + self._ramp_rate = ramp_rate + + @property + def ramp_setpoint_temperature(self): + return self._ramp_setpoint_temperature + + @ramp_setpoint_temperature.setter + def ramp_setpoint_temperature(self, temp): + self._ramp_setpoint_temperature = temp + diff --git a/lewis_emulators/eurotherm/interfaces/stream_interface.py b/lewis_emulators/eurotherm/interfaces/stream_interface.py index 62a3ec1b..c02fc8ff 100644 --- a/lewis_emulators/eurotherm/interfaces/stream_interface.py +++ b/lewis_emulators/eurotherm/interfaces/stream_interface.py @@ -1,17 +1,24 @@ from lewis.adapters.stream import StreamAdapter from lewis_emulators.utils.command_builder import CmdBuilder +from lewis.core.logging import has_log - +@has_log class EurothermStreamInterface(StreamAdapter): """ Stream interface for the serial port """ commands = { + CmdBuilder("get_current_temperature").eot().escape("0011PV").enq().build(), + CmdBuilder("get_ramp_setpoint").eot().escape("0011SP").enq().build(), + CmdBuilder("set_ramp_setpoint", arg_sep="").eot().escape("0011").stx().escape("SL").float().etx().any().build(), } - in_terminator = chr(3) - out_terminator = chr(3) + # The real Eurotherm uses timeouts instead of terminators to assess when a command is finished. To make this work + # with the emulator we manually added terminators via asyn commands to the device. Lewis will be able to handle this + # natively in future versions. See: https://github.com/DMSC-Instrument-Data/lewis/pull/262 + in_terminator = "\r\n" + out_terminator = "\r\n" def handle_error(self, request, error): """ @@ -22,5 +29,16 @@ def handle_error(self, request, error): error: problem """ - print "An error occurred at request " + repr(request) + ": " + repr(error) + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def get_current_temperature(self): + return "\x02PV{}".format(self._device.current_temperature) + + def set_temperature_setpoint(self, temperature): + self._device.setpoint_temperature = temperature + + def get_ramp_setpoint(self): + return "\x02SP{}".format(self._device.ramp_setpoint_temperature) + def set_ramp_setpoint(self, temperature, _): + self._device.ramp_setpoint_temperature = temperature diff --git a/lewis_emulators/eurotherm/states.py b/lewis_emulators/eurotherm/states.py index c2beedfb..72490bd8 100644 --- a/lewis_emulators/eurotherm/states.py +++ b/lewis_emulators/eurotherm/states.py @@ -1,4 +1,5 @@ from lewis.core.statemachine import State +from lewis.core import approaches class DefaultState(State): @@ -6,3 +7,4 @@ class DefaultState(State): Device is in default state. """ NAME = 'Default' + diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 52c4b367..6c417f44 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -115,6 +115,14 @@ def etx(self): """ return self.add_ascii_character(3) + def eot(self): + """ + Add the EOT character (0x4) to the string. + + :return: builder + """ + return self.add_ascii_character(4) + def enq(self): """ Add the ENQ character (0x5) to the string. From 06ab4feadeeb993db92f0409744bf5672c3e88b7 Mon Sep 17 00:00:00 2001 From: Samuel Jackson Date: Fri, 1 Sep 2017 17:19:01 +0100 Subject: [PATCH 0325/1466] Add docstrings --- lewis_emulators/eurotherm/device.py | 60 +++++++++++++++++++ .../eurotherm/interfaces/stream_interface.py | 24 ++++++-- 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/eurotherm/device.py b/lewis_emulators/eurotherm/device.py index 4a747930..fe8d5d93 100644 --- a/lewis_emulators/eurotherm/device.py +++ b/lewis_emulators/eurotherm/device.py @@ -42,41 +42,101 @@ def _get_transition_handlers(self): @property def address(self): + """ + Get the address of the device. + + Returns: the address of the device e.g. "A01" + """ self._address @address.setter def address(self, addr): + """ + Sets the address of the device. + + Args: + addr (str): the address of this device e.g. "A01". + + """ self._address = addr @property def current_temperature(self): + """ + Get current temperature of the device. + + Returns: the current temperature in K. + """ return self._current_temperature @current_temperature.setter def current_temperature(self, temp): + """ + Set the current temperature of the device. + + Args: + temp: the current temperature of the device in K. + + """ self._current_temperature = temp @property def ramping_on(self): + """ + Gets whether the device is currently ramping. + + Returns: bool indicating if the device is ramping. + """ return self._ramping_on @ramping_on.setter def ramping_on(self, toggle): + """ + Sets whether the device is currently ramping. + + Args: + toggle (bool): turn ramping on or off. + + """ self._ramping_on = toggle @property def ramp_rate(self): + """ + Get the current ramp rate. + + Returns: the current ramp rate in K/min + """ return self._ramp_rate @ramp_rate.setter def ramp_rate(self, ramp_rate): + """ + Set the ramp rate. + + Args: + ramp_rate (float): set the current ramp rate in K/min. + + """ self._ramp_rate = ramp_rate @property def ramp_setpoint_temperature(self): + """ + Get the set point temperature. + + Returns: the current value of the setpoint temperature in K. + """ return self._ramp_setpoint_temperature @ramp_setpoint_temperature.setter def ramp_setpoint_temperature(self, temp): + """ + Set the set point temperature. + + Args: + temp (float): the current value of the set point temperature in K. + + """ self._ramp_setpoint_temperature = temp diff --git a/lewis_emulators/eurotherm/interfaces/stream_interface.py b/lewis_emulators/eurotherm/interfaces/stream_interface.py index c02fc8ff..60fdd8a5 100644 --- a/lewis_emulators/eurotherm/interfaces/stream_interface.py +++ b/lewis_emulators/eurotherm/interfaces/stream_interface.py @@ -1,8 +1,7 @@ from lewis.adapters.stream import StreamAdapter from lewis_emulators.utils.command_builder import CmdBuilder -from lewis.core.logging import has_log -@has_log + class EurothermStreamInterface(StreamAdapter): """ Stream interface for the serial port @@ -32,13 +31,28 @@ def handle_error(self, request, error): self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) def get_current_temperature(self): - return "\x02PV{}".format(self._device.current_temperature) + """ + Get the current temperature of the device. - def set_temperature_setpoint(self, temperature): - self._device.setpoint_temperature = temperature + Returns: the current temperature formatted like the Eurotherm protocol. + """ + return "\x02PV{}".format(self._device.current_temperature) def get_ramp_setpoint(self): + """ + Get the set point temperature. + + Returns: the current set point temperature formatted like the Eurotherm protocol. + """ return "\x02SP{}".format(self._device.ramp_setpoint_temperature) def set_ramp_setpoint(self, temperature, _): + """ + Set the set point temperature. + + Args: + temperature: the temperature to set the setpoint to. + _: unused argument captured by the command. + + """ self._device.ramp_setpoint_temperature = temperature From 9b5a3acfb49ea63aa22f13a000ec455830cf1087 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 4 Sep 2017 15:43:52 +0100 Subject: [PATCH 0326/1466] Make a bit more pythonic and improve docstring --- lewis_emulators/hlg/interfaces/stream_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/hlg/interfaces/stream_interface.py b/lewis_emulators/hlg/interfaces/stream_interface.py index 387575dc..7b9d470e 100644 --- a/lewis_emulators/hlg/interfaces/stream_interface.py +++ b/lewis_emulators/hlg/interfaces/stream_interface.py @@ -50,7 +50,7 @@ def set_verbosity(self, verbosity): """ verbosity_as_int = int(verbosity) - if verbosity_as_int != 0 and verbosity_as_int != 1: + if verbosity_as_int not in [0, 1]: raise AssertionError("Verbosity must be 0 or 1 was '{0}'".format(verbosity)) self._device.verbosity = verbosity_as_int if verbosity_as_int == 0: @@ -69,8 +69,8 @@ def set_prefix(self, prefix): """ prefix_as_int = int(prefix) - if prefix_as_int < 0 or prefix_as_int >= len(PREFIXES): - raise AssertionError("Prefix must be between 0 and 5 '{0}'".format(prefix)) + if not 0 <= prefix_as_int < len(PREFIXES): + raise AssertionError("Prefix must be between 0 and {1} '{0}'".format(prefix, len(PREFIXES))) self._device.prefix = prefix_as_int return self._format_output("CP{0}".format(prefix_as_int), "Verbose=", prefix) @@ -79,7 +79,7 @@ def get_level(self): """ Gets the current level - Returns: level in correct units + Returns: level in correct units or None if no level is set """ if self._device.level is None: From f11b4a37ed342ea658e83f6960474bc417389bfb Mon Sep 17 00:00:00 2001 From: esouthren Date: Mon, 4 Sep 2017 16:39:49 +0100 Subject: [PATCH 0327/1466] changed command_builder.py to fix Pause command bug, tidied emulator files --- lewis_emulators/HFMAGPSU/device.py | 53 +++++---- .../HFMAGPSU/interfaces/stream_interface.py | 109 ++++++++++-------- lewis_emulators/utils/command_builder.py | 10 +- 3 files changed, 93 insertions(+), 79 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 29ec5723..bc7469f4 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -2,22 +2,25 @@ from collections import OrderedDict from .states import DefaultState + + + + class SimulatedHFMAGPSU(StateMachineDevice): def _initialize_data(self): - self._direction = '-' - self._outputMode = 'OFF' - self._rampTarget = 'ZERO' - self._heaterStatus = 'OFF' # off or on - self._heaterValue = 1.0 # V + self._isOutputModeTesla = False + self._isHeaterOn = False + self._isPaused = False + self._direction = 0 + self._rampTarget = 0 + self._heaterValue = 1.0 self._maxTarget = 5.0 self._midTarget = 2.5 self._rampRate = 10.0 - self._pause = 'OFF' self._limit = 10 - self._logMessage = "test" - #self._update = "update field" + self._logMessage = "this is the initial log message" def _get_state_handlers(self): return {DefaultState.NAME: DefaultState()} @@ -33,16 +36,16 @@ def direction(self): return self._direction @property - def outputMode(self): - return self._outputMode + def isOutputModeTesla(self): + return self._isOutputModeTesla @property def rampTarget(self): return self._rampTarget @property - def heaterStatus(self): - return self._heaterStatus + def isHeaterOn(self): + return self._isHeaterOn @property def heaterValue(self): @@ -61,17 +64,13 @@ def rampRate(self): return self._rampRate @property - def pause(self): - return self._pause + def isPaused(self): + return self._isPaused @property def limit(self): return self._limit - #@property - #def update(self): - # return self._update - @property def logMessage(self): return self._logMessage @@ -80,17 +79,17 @@ def logMessage(self): def direction(self, d): self._direction = d - @outputMode.setter - def outputMode(self, om): - self._outputMode = om + @isOutputModeTesla.setter + def isOutputModeTesla(self, om): + self._isOutputModeTesla = om @rampTarget.setter def rampTarget(self, rt): self._rampTarget = rt - @heaterStatus.setter - def heaterStatus(self, hs): - self._heaterStatus = hs + @isHeaterOn.setter + def isHeaterOn(self, hs): + self._isHeaterOn = hs @heaterValue.setter def heaterValue(self, hv): @@ -108,9 +107,9 @@ def midTarget(self, mt): def rampRate(self, rate): self._rampRate = rate - @pause.setter - def pause(self, p): - self._pause = p + @isPaused.setter + def isPaused(self, p): + self._isPaused = p @limit.setter def limit(self, lim): diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 16b135f1..07f6e12b 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -3,12 +3,11 @@ class HFMAGPSUStreamInterface(StreamAdapter): + # terminators set to ascii ETX in_terminator = "\r\n" out_terminator = "\r\n" - - commands = { CmdBuilder("read_direction").escape("GET SIGN").build(), CmdBuilder("read_output_mode").escape("GET O").build(), @@ -18,20 +17,19 @@ class HFMAGPSUStreamInterface(StreamAdapter): CmdBuilder("read_mid_target").escape("GET MID").build(), CmdBuilder("read_ramp_rate").escape("RAMP").build(), CmdBuilder("read_limit").escape("H V").build(), - # CmdBuilder("read_update").escape("U").build(), CmdBuilder("read_pause").escape("P").build(), CmdBuilder("read_heater_value").escape("GET H").build(), - CmdBuilder("write_direction").escape("D ").arg("-|0|\+").build(), # '+' is special character + CmdBuilder("write_direction").escape("D ").arg("-|0|\+").build(), CmdBuilder("write_output_mode").escape("T ").arg("AMPS|TESLA").build(), CmdBuilder("write_ramp_target").escape("RAMP ").arg("ZERO|MID|MAX").build(), CmdBuilder("write_heater_status").escape("H ").arg("OFF|ON").build(), + CmdBuilder("write_pause").escape("P ").arg("OFF|ON").build(), CmdBuilder("write_heater_value").escape("S H ").float().build(), CmdBuilder("write_max_target").escape("SET MAX ").float().build(), CmdBuilder("write_mid_target").escape("SET MID ").float().build(), CmdBuilder("write_ramp_rate").escape("SET RAMP ").float().build(), - CmdBuilder("write_limit").escape("S L ").float().build(), - CmdBuilder("write_pause").escape("P ").arg("OFF|ON").build(), + CmdBuilder("write_limit").escape("S L ").float().build() } def handle_error(self, request, error): @@ -41,79 +39,88 @@ def handle_error(self, request, error): def read_direction(self): return self._device.direction + def write_direction(self, d): + self._device.direction = d + self._device.logMessage = "HH:MM:SS DIRECTION: [" + str(d) + "]" + return self._device.logMessage + def read_output_mode(self): - return self._device.outputMode + return "TESLA" if self._device.isOutputModeTesla else "AMPS" + + def write_output_mode(self, om): + if om == "TESLA": + self._device.isOutputModeTesla = True + else: + self._device.isOutputModeTesla = False + self._device.logMessage = "HH:MM:SS UNITS: [" + str(om) + "]" + return self._device.logMessage def read_ramp_target(self): return self._device.rampTarget + def write_ramp_target(self, rt): + self._device.rampTarget = rt + self._device.logMessage = "HH:MM:SS RAMP TARGET: [" + str(rt) + "]" + return self._device.logMessage + def read_ramp_rate(self): return self._device.rampRate + def write_ramp_rate(self, rr): + self._device.rampRate = rr + self._device.logMessage = "HH:MM:SS RAMP RATE: [" + str(rr) + "]" + return self._device.logMessage + def read_heater_status(self): - return self._device.heaterStatus + return "ON" if self._device.isHeaterOn else "OFF" + + def write_heater_status(self, hs): + if hs == "ON": + self._device.isHeaterOn = True + else: + self._device.isHeaterOn = False + self._device.logMessage = "HH:MM:SS HEATER STATUS: [" + str(hs) + "]" + return self._device.logMessage + + def read_pause(self): + return "ON" if self._device.isPaused else "OFF" + + def write_pause(self, paused): + if paused == "ON": + self._device.isPaused = True + else: + self._device.isPaused = False + self._device.logMessage = "HH:MM:SS PAUSE STATUS: [" + str(paused) + "]" + return self._device.logMessage def read_heater_value(self): return self._device.heaterValue + def write_heater_value(self, hv): + self._device.heaterValue = hv + self._device.logMessage = "HH:MM:SS HEATER OUTPUT: [" + str(hv) + "]" + return self._device.logMessage + def read_max_target(self): return self._device.maxTarget - def read_mid_target(self): - return self._device.midTarget - - def read_limit(self): - return self._device.limit - - def read_pause(self): - return self._device.pause - def write_max_target(self, mt): self._device.maxTarget = mt self._device.logMessage = "HH:MM:SS MAX SETTING: [" + str(mt) + "]" return self._device.logMessage + def read_mid_target(self): + return self._device.midTarget + def write_mid_target(self, mt): self._device.midTarget = mt self._device.logMessage = "HH:MM:SS MID SETTING: [" + str(mt) + "]" return self._device.logMessage - def write_ramp_rate(self, rr): - self._device.rampRate = rr - self._device.logMessage = "HH:MM:SS RAMP RATE: [" + str(rr) + "]" - return self._device.logMessage - - def write_direction(self, d): - self._device.direction = d - self._device.logMessage = "HH:MM:SS DIRECTION: [" + str(d) + "]" - return self._device.logMessage - - def write_ramp_target(self, rt): - self._device.rampTarget = rt - self._device.logMessage = "HH:MM:SS RAMP TARGET: [" + str(rt) + "]" - return self._device.logMessage - - def write_heater_status(self, hs): - self._device.heaterStatus = hs - self._device.logMessage = "HH:MM:SS HEATER STATUS: [" + str(hs) + "]" - return self._device.logMessage - - def write_heater_value(self, hv): - self._device.heaterValue = hv - self._device.logMessage = "HH:MM:SS HEATER OUTPUT: [" + str(hv) + "]" - return self._device.logMessage - - def write_output_mode(self, om): - self._device.outputMode = om - self._device.logMessage = "HH:MM:SS UNITS: [" + str(om) + "]" - return self._device.logMessage + def read_limit(self): + return self._device.limit def write_limit(self, l): self._device.limit = l self._device.logMessage = "HH:MM:SS VOLTAGE LIMIT: [" + str(l) + "]" return self._device.logMessage - - def write_pause(self, p): - self._device.pause = p - self._device.logMessage = "HH:MM:SS PAUSE STATUS: [" + str(p) + "]" - return self._device.logMessage diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 52c4b367..70796d26 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -8,17 +8,20 @@ class CmdBuilder(object): Build a command for the stream adapter. """ - def __init__(self, target_method, arg_sep=",", ignore=""): + def __init__(self, target_method, arg_sep=",", ignore="", default_regex_chars=True): """ Create a builder. Use build to create the final object :param target_method: name of the method target to call when the reg ex matches :param arg_sep: separators between the arguments :param ignore: set of characters to ignore between text and arguments + :param default_regex_chars: forces CmdBuilder to match entire argument (e.g if read/write commands + have the same starting argument but one has additional args) """ self._target_method = target_method self._arg_sep = arg_sep self._current_sep = "" + self._default_regex_chars = default_regex_chars if ignore is None or ignore == "": self._ignore = "" else: @@ -86,6 +89,11 @@ def build(self, *args, **kwargs): :param **kwargs: key word arguments to pass to Cmd constructor :return: Cmd object """ + startRegex = '^' + endRegex = '$' + if self._default_regex_chars: + self._reg_ex = startRegex + self._reg_ex + endRegex + return Cmd(self._target_method, self._reg_ex, *args, **kwargs) def add_ascii_character(self, char_number): From c1a66874ee04ce0807a9b92e775f1c4af6a7cd8f Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 6 Sep 2017 16:29:57 +0100 Subject: [PATCH 0328/1466] changed log message output --- lewis_emulators/HFMAGPSU/device.py | 111 ++++++++------- .../HFMAGPSU/interfaces/__init__.py | 2 +- .../HFMAGPSU/interfaces/stream_interface.py | 128 ++++++++++-------- lewis_emulators/utils/command_builder.py | 14 +- 4 files changed, 134 insertions(+), 121 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index bc7469f4..14da24b2 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -3,24 +3,21 @@ from .states import DefaultState - - - class SimulatedHFMAGPSU(StateMachineDevice): def _initialize_data(self): - self._isOutputModeTesla = False - self._isHeaterOn = False - self._isPaused = False + self._is_output_mode_tesla = False + self._is_heater_on = False + self._is_paused = False self._direction = 0 - self._rampTarget = 0 - self._heaterValue = 1.0 - self._maxTarget = 5.0 - self._midTarget = 2.5 - self._rampRate = 10.0 + self._ramp_target = 0 + self._heater_value = 1.0 + self._max_target = 5.0 + self._mid_target = 2.5 + self._ramp_rate = 10.0 self._limit = 10 - self._logMessage = "this is the initial log message" + self._log_message = "this is the initial log message" def _get_state_handlers(self): return {DefaultState.NAME: DefaultState()} @@ -36,85 +33,85 @@ def direction(self): return self._direction @property - def isOutputModeTesla(self): - return self._isOutputModeTesla + def is_output_mode_tesla(self): + return self._is_output_mode_tesla @property - def rampTarget(self): - return self._rampTarget + def ramp_target(self): + return self._ramp_target @property - def isHeaterOn(self): - return self._isHeaterOn + def is_heater_on(self): + return self._is_heater_on @property - def heaterValue(self): - return self._heaterValue + def heater_value(self): + return self._heater_value @property - def maxTarget(self): - return self._maxTarget + def max_target(self): + return self._max_target @property - def midTarget(self): - return self._midTarget + def mid_target(self): + return self._mid_target @property - def rampRate(self): - return self._rampRate + def ramp_rate(self): + return self._ramp_rate @property - def isPaused(self): - return self._isPaused + def is_paused(self): + return self._is_paused @property def limit(self): return self._limit @property - def logMessage(self): - return self._logMessage + def log_message(self): + return self._log_message @direction.setter def direction(self, d): self._direction = d - @isOutputModeTesla.setter - def isOutputModeTesla(self, om): - self._isOutputModeTesla = om + @is_output_mode_tesla.setter + def is_output_mode_tesla(self, om): + self._is_output_mode_tesla = om - @rampTarget.setter - def rampTarget(self, rt): - self._rampTarget = rt + @ramp_target.setter + def ramp_target(self, rt): + self._ramp_target = rt - @isHeaterOn.setter - def isHeaterOn(self, hs): - self._isHeaterOn = hs + @is_heater_on.setter + def is_heater_on(self, hs): + self._is_heater_on = hs - @heaterValue.setter - def heaterValue(self, hv): - self._heaterValue = hv + @heater_value.setter + def heater_value(self, hv): + self._heater_value = hv - @maxTarget.setter - def maxTarget(self, mt): - self._maxTarget = mt + @max_target.setter + def max_target(self, mt): + self._max_target = mt - @midTarget.setter - def midTarget(self, mt): - self._midTarget = mt + @mid_target.setter + def mid_target(self, mt): + self._mid_target = mt - @rampRate.setter - def rampRate(self, rate): - self._rampRate = rate + @ramp_rate.setter + def ramp_rate(self, rate): + self._ramp_rate = rate - @isPaused.setter - def isPaused(self, p): - self._isPaused = p + @is_paused.setter + def is_paused(self, p): + self._is_paused = p @limit.setter def limit(self, lim): self._limit = lim - @logMessage.setter - def logMessage(self, lm): - self._logMessage = lm + @log_message.setter + def log_message(self, lm): + self._log_message = lm diff --git a/lewis_emulators/HFMAGPSU/interfaces/__init__.py b/lewis_emulators/HFMAGPSU/interfaces/__init__.py index a671b148..a1a75831 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/__init__.py +++ b/lewis_emulators/HFMAGPSU/interfaces/__init__.py @@ -1,3 +1,3 @@ from .stream_interface import HFMAGPSUStreamInterface -__all__ = ['HFMAGPSUStreamInterface'] \ No newline at end of file +__all__ = ['HFMAGPSUStreamInterface'] diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 07f6e12b..5111a142 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -1,5 +1,7 @@ from lewis.adapters.stream import StreamAdapter from lewis_emulators.utils.command_builder import CmdBuilder +from datetime import datetime + class HFMAGPSUStreamInterface(StreamAdapter): @@ -32,95 +34,109 @@ class HFMAGPSUStreamInterface(StreamAdapter): CmdBuilder("write_limit").escape("S L ").float().build() } + def _create_log_message(self, pv, value): + current_time = datetime.now().strftime('%H:%M:%S') + self._device.log_message = "{} {}: [{}]".format(current_time, pv, value) + def handle_error(self, request, error): - self.log.error("Beep boop. Error occurred at " + repr(request) + ": " + repr(error)) - print("Beep boop. Error occurred at " + repr(request) + ": " + repr(error)) + self.log.error("Error occurred at " + repr(request) + ": " + repr(error)) + print("Error occurred at " + repr(request) + ": " + repr(error)) def read_direction(self): return self._device.direction - def write_direction(self, d): - self._device.direction = d - self._device.logMessage = "HH:MM:SS DIRECTION: [" + str(d) + "]" - return self._device.logMessage + def write_direction(self, direction): + self._device.direction = direction + self._create_log_message("DIRECTION", str(direction)) + return self._device.log_message def read_output_mode(self): - return "TESLA" if self._device.isOutputModeTesla else "AMPS" - - def write_output_mode(self, om): - if om == "TESLA": - self._device.isOutputModeTesla = True + return "TESLA" if self._device.is_output_mode_tesla else "AMPS" + + def write_output_mode(self, output_mode): + if output_mode == "TESLA": + self._device.is_output_mode_tesla = True + self._create_log_message("OUTPUT MODE", str(output_mode)) + elif output_mode == "AMPS": + self._device.is_output_mode_tesla = False + self._create_log_message("OUTPUT MODE", str(output_mode)) else: - self._device.isOutputModeTesla = False - self._device.logMessage = "HH:MM:SS UNITS: [" + str(om) + "]" - return self._device.logMessage + raise AssertionError("Invalid arguments sent") + return self._device.log_message def read_ramp_target(self): - return self._device.rampTarget + return self._device.ramp_target - def write_ramp_target(self, rt): - self._device.rampTarget = rt - self._device.logMessage = "HH:MM:SS RAMP TARGET: [" + str(rt) + "]" - return self._device.logMessage + def write_ramp_target(self, ramp_target): + self._device.ramp_target = ramp_target + self._create_log_message("RAMP TARGET", str(ramp_target)) + return self._device.log_message def read_ramp_rate(self): - return self._device.rampRate + return self._device.ramp_rate - def write_ramp_rate(self, rr): - self._device.rampRate = rr - self._device.logMessage = "HH:MM:SS RAMP RATE: [" + str(rr) + "]" - return self._device.logMessage + def write_ramp_rate(self, ramp_rate): + self._device.ramp_rate = ramp_rate + self._create_log_message("RAMP RATE", str(ramp_rate)) + return self._device.log_message def read_heater_status(self): - return "ON" if self._device.isHeaterOn else "OFF" - - def write_heater_status(self, hs): - if hs == "ON": - self._device.isHeaterOn = True + return "ON" if self._device.is_heater_on else "OFF" + + def write_heater_status(self, heater_status): + if heater_status == "ON": + self._device.is_heater_on = True + self._create_log_message("HEATER STATUS", str(heater_status)) + elif heater_status == "OFF": + self._device.is_heater_on = False + self._create_log_message("HEATER STATUS", str(heater_status)) else: - self._device.isHeaterOn = False - self._device.logMessage = "HH:MM:SS HEATER STATUS: [" + str(hs) + "]" - return self._device.logMessage + raise AssertionError("Invalid arguments sent") + return self._device.log_message def read_pause(self): - return "ON" if self._device.isPaused else "OFF" + return "ON" if self._device.is_paused else "OFF" def write_pause(self, paused): if paused == "ON": - self._device.isPaused = True + self._device.is_paused = True + self._create_log_message("PAUSE STATUS", str(paused)) + elif paused == "OFF": + self._device.is_paused = False + self._create_log_message("PAUSE STATUS", str(paused)) else: - self._device.isPaused = False - self._device.logMessage = "HH:MM:SS PAUSE STATUS: [" + str(paused) + "]" - return self._device.logMessage + raise AssertionError("Invalid arguments sent") + + return self._device.log_message def read_heater_value(self): - return self._device.heaterValue + return self._device.heater_value - def write_heater_value(self, hv): - self._device.heaterValue = hv - self._device.logMessage = "HH:MM:SS HEATER OUTPUT: [" + str(hv) + "]" - return self._device.logMessage + def write_heater_value(self, heater_value): + self._device.heater_value = heater_value + self._create_log_message("HEATER OUTPUT", str(heater_value)) + return self._device.log_message def read_max_target(self): - return self._device.maxTarget + return self._device.max_target - def write_max_target(self, mt): - self._device.maxTarget = mt - self._device.logMessage = "HH:MM:SS MAX SETTING: [" + str(mt) + "]" - return self._device.logMessage + def write_max_target(self, max_target): + self._device.max_target = max_target + self._create_log_message("MAX SETTING", str(max_target)) + return self._device.log_message def read_mid_target(self): - return self._device.midTarget + return self._device.mid_target - def write_mid_target(self, mt): - self._device.midTarget = mt - self._device.logMessage = "HH:MM:SS MID SETTING: [" + str(mt) + "]" - return self._device.logMessage + def write_mid_target(self, mid_target): + self._device.mid_target = mid_target + self._create_log_message("MID SETTING", str(mid_target)) + return self._device.log_message def read_limit(self): return self._device.limit - def write_limit(self, l): - self._device.limit = l - self._device.logMessage = "HH:MM:SS VOLTAGE LIMIT: [" + str(l) + "]" - return self._device.logMessage + def write_limit(self, limit): + self._device.limit = limit + self._create_log_message("VOLTAGE LIMIT", limit) + return self._device.log_message diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 70796d26..98c4aa2e 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -8,20 +8,20 @@ class CmdBuilder(object): Build a command for the stream adapter. """ - def __init__(self, target_method, arg_sep=",", ignore="", default_regex_chars=True): + def __init__(self, target_method, arg_sep=",", ignore="", match_entire_string=True): """ Create a builder. Use build to create the final object :param target_method: name of the method target to call when the reg ex matches :param arg_sep: separators between the arguments :param ignore: set of characters to ignore between text and arguments - :param default_regex_chars: forces CmdBuilder to match entire argument (e.g if read/write commands + :param match_entire_string: forces CmdBuilder to match entire argument (e.g if read/write commands have the same starting argument but one has additional args) """ self._target_method = target_method self._arg_sep = arg_sep self._current_sep = "" - self._default_regex_chars = default_regex_chars + self._match_entire_string = match_entire_string if ignore is None or ignore == "": self._ignore = "" else: @@ -43,8 +43,8 @@ def arg(self, arg_regex): Add an argument to the command. :param arg_regex: regex for the argument (capture group will be added) - :return: builder - """ + :return: builder """ + self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore self._current_sep = self._arg_sep return self @@ -91,8 +91,8 @@ def build(self, *args, **kwargs): """ startRegex = '^' endRegex = '$' - if self._default_regex_chars: - self._reg_ex = startRegex + self._reg_ex + endRegex + if self._match_entire_string: + self._reg_ex = "{}{}{}".format(startRegex, self._reg_ex, endRegex) return Cmd(self._target_method, self._reg_ex, *args, **kwargs) From 996f3ec010738062cbfa62a9cfabaea6240267b3 Mon Sep 17 00:00:00 2001 From: Attah Date: Thu, 14 Sep 2017 12:03:49 +0100 Subject: [PATCH 0329/1466] Device Emulator review changes --- lewis_emulators/lakeshore460/interfaces/stream_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lewis_emulators/lakeshore460/interfaces/stream_interface.py b/lewis_emulators/lakeshore460/interfaces/stream_interface.py index fd642baa..46bec520 100644 --- a/lewis_emulators/lakeshore460/interfaces/stream_interface.py +++ b/lewis_emulators/lakeshore460/interfaces/stream_interface.py @@ -56,7 +56,6 @@ def get_IDN(self): return "{0}".format(self._device.idn) def get_unit(self): - return "{0}".format(self._device.unit) def set_unit(self, unit): From a6981e34abf693adfb2c4bf530fe9c8fbd3e74f8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 15 Sep 2017 17:08:34 +0100 Subject: [PATCH 0330/1466] Make emulator act like real thing --- lewis_emulators/fermichopper/interfaces/stream_interface.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index c94cee91..88941084 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -116,7 +116,8 @@ def get_all_data(self, checksum): + JulichChecksum.append("#E{:04X}".format(int(round((self._device.autozero_2_lower + 22.86647) / 0.04486)))) \ + JulichChecksum.append("#F{:04X}".format(int(round(self._device.get_voltage() / 0.4274)))) \ + JulichChecksum.append("#G{:04X}".format(int(round((self._device.get_electronics_temp() + 25.0) / 0.14663)))) \ - + JulichChecksum.append("#H{:04X}".format(int(round((self._device.get_motor_temp() + 12.124) / 0.1263)))) + + JulichChecksum.append("#H{:04X}".format(int(round((self._device.get_motor_temp() + 12.124) / 0.1263))))\ + + "$" def execute_command(self, command, checksum): JulichChecksum.verify('#1', command, checksum) From a363d6953d648cef2ace72903dc4e3ea78cb995a Mon Sep 17 00:00:00 2001 From: esouthren Date: Mon, 18 Sep 2017 11:46:37 +0100 Subject: [PATCH 0331/1466] commit before starting to work on States and everything probably went a bit pete tong --- lewis_emulators/HFMAGPSU/device.py | 112 ++---------------- .../HFMAGPSU/interfaces/stream_interface.py | 98 +++++++++++---- 2 files changed, 85 insertions(+), 125 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 14da24b2..ee7ff96a 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -5,19 +5,19 @@ class SimulatedHFMAGPSU(StateMachineDevice): - def _initialize_data(self): - self._is_output_mode_tesla = False - self._is_heater_on = False - self._is_paused = False - self._direction = 0 - self._ramp_target = 0 - self._heater_value = 1.0 - self._max_target = 5.0 - self._mid_target = 2.5 - self._ramp_rate = 10.0 - self._limit = 10 - self._log_message = "this is the initial log message" + self.is_output_mode_tesla = False + self.is_heater_on = True + self.is_paused = True + self.output = 0 + self.direction = '-' + self.ramp_target = 'ZERO' + self.heater_value = 1.0 + self.max_target = 20.0 + self.mid_target = 10.0 + self.ramp_rate = 0.5 + self.limit = 10 + self.log_message = "this is the initial log message" def _get_state_handlers(self): return {DefaultState.NAME: DefaultState()} @@ -27,91 +27,3 @@ def _get_initial_state(self): def _get_transition_handlers(self): return OrderedDict() - - @property - def direction(self): - return self._direction - - @property - def is_output_mode_tesla(self): - return self._is_output_mode_tesla - - @property - def ramp_target(self): - return self._ramp_target - - @property - def is_heater_on(self): - return self._is_heater_on - - @property - def heater_value(self): - return self._heater_value - - @property - def max_target(self): - return self._max_target - - @property - def mid_target(self): - return self._mid_target - - @property - def ramp_rate(self): - return self._ramp_rate - - @property - def is_paused(self): - return self._is_paused - - @property - def limit(self): - return self._limit - - @property - def log_message(self): - return self._log_message - - @direction.setter - def direction(self, d): - self._direction = d - - @is_output_mode_tesla.setter - def is_output_mode_tesla(self, om): - self._is_output_mode_tesla = om - - @ramp_target.setter - def ramp_target(self, rt): - self._ramp_target = rt - - @is_heater_on.setter - def is_heater_on(self, hs): - self._is_heater_on = hs - - @heater_value.setter - def heater_value(self, hv): - self._heater_value = hv - - @max_target.setter - def max_target(self, mt): - self._max_target = mt - - @mid_target.setter - def mid_target(self, mt): - self._mid_target = mt - - @ramp_rate.setter - def ramp_rate(self, rate): - self._ramp_rate = rate - - @is_paused.setter - def is_paused(self, p): - self._is_paused = p - - @limit.setter - def limit(self, lim): - self._limit = lim - - @log_message.setter - def log_message(self, lm): - self._log_message = lm diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 5111a142..8837ca48 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -12,12 +12,13 @@ class HFMAGPSUStreamInterface(StreamAdapter): commands = { CmdBuilder("read_direction").escape("GET SIGN").build(), - CmdBuilder("read_output_mode").escape("GET O").build(), - CmdBuilder("read_ramp_target").escape("R S").build(), + CmdBuilder("read_output_mode").escape("T").build(), + CmdBuilder("read_output").escape("GET O").build(), + CmdBuilder("read_ramp_target").escape("RAMP").build(), CmdBuilder("read_heater_status").escape("HEATER").build(), CmdBuilder("read_max_target").escape("GET MAX").build(), CmdBuilder("read_mid_target").escape("GET MID").build(), - CmdBuilder("read_ramp_rate").escape("RAMP").build(), + CmdBuilder("read_ramp_rate").escape("GET RATE").build(), CmdBuilder("read_limit").escape("H V").build(), CmdBuilder("read_pause").escape("P").build(), CmdBuilder("read_heater_value").escape("GET H").build(), @@ -36,14 +37,30 @@ class HFMAGPSUStreamInterface(StreamAdapter): def _create_log_message(self, pv, value): current_time = datetime.now().strftime('%H:%M:%S') - self._device.log_message = "{} {}: [{}]".format(current_time, pv, value) + self._device.log_message = "{} {}: {}".format(current_time, pv, value) + def handle_error(self, request, error): self.log.error("Error occurred at " + repr(request) + ": " + repr(error)) print("Error occurred at " + repr(request) + ": " + repr(error)) + def _get_output_mode_string(self): + if self._device.is_output_mode_tesla: + return "TESLA" + else: + return "AMPS" + + def _get_ramp_target_value(self): + target = 0 + if self._device.ramp_target == "MID": + target = self._device.mid_target + elif self._device.ramp_target == "MAX": + target = self._device.max_target + + return target + def read_direction(self): - return self._device.direction + return "HH.MM.SS CURRENT DIRECTION: {}".format(self._device.direction) def write_direction(self, direction): self._device.direction = direction @@ -51,90 +68,121 @@ def write_direction(self, direction): return self._device.log_message def read_output_mode(self): - return "TESLA" if self._device.is_output_mode_tesla else "AMPS" + mode = "AMPS" + if self._device.is_output_mode_tesla: + mode = "TESLA" + return "HH.MM.SS UNITS: {}".format(mode) + + def read_output(self): + # If target is + or - the current output, we are ramping up or down + # new output = current output +/- ramp rate + if not self._device.is_paused: + target = self._get_ramp_target_value() + if target >= self._device.output: + self._device.output += self._device.ramp_rate + elif target < : + self._device.output -= self._device.ramp_rate + mode = self._get_output_mode_string() + + return "HH.MM.SS OUTPUT: {} {} AT {} VOLTS".format(self._device.output, mode, self._device.heater_value) def write_output_mode(self, output_mode): if output_mode == "TESLA": self._device.is_output_mode_tesla = True - self._create_log_message("OUTPUT MODE", str(output_mode)) + self._create_log_message("OUTPUT MODE", output_mode) elif output_mode == "AMPS": self._device.is_output_mode_tesla = False - self._create_log_message("OUTPUT MODE", str(output_mode)) + self._create_log_message("OUTPUT MODE", output_mode) else: raise AssertionError("Invalid arguments sent") return self._device.log_message def read_ramp_target(self): - return self._device.ramp_target + return "HH:MM:SS RAMP TARGET: {}".format(self._device.ramp_target) def write_ramp_target(self, ramp_target): self._device.ramp_target = ramp_target - self._create_log_message("RAMP TARGET", str(ramp_target)) + self._create_log_message("RAMP TARGET", ramp_target) return self._device.log_message def read_ramp_rate(self): - return self._device.ramp_rate + return "HH:MM:SS RAMP RATE {} A/SEC".format(self._device.ramp_rate) def write_ramp_rate(self, ramp_rate): self._device.ramp_rate = ramp_rate - self._create_log_message("RAMP RATE", str(ramp_rate)) + self._create_log_message("RAMP RATE", ramp_rate) return self._device.log_message def read_heater_status(self): - return "ON" if self._device.is_heater_on else "OFF" + heater_value = "OFF" + if self._device.heater_value: + heater_value = "ON" + return "HH:MM:SS HEATER STATUS: {}".format(heater_value) def write_heater_status(self, heater_status): if heater_status == "ON": self._device.is_heater_on = True - self._create_log_message("HEATER STATUS", str(heater_status)) + self._create_log_message("HEATER STATUS", heater_status) elif heater_status == "OFF": self._device.is_heater_on = False - self._create_log_message("HEATER STATUS", str(heater_status)) + self._create_log_message("HEATER STATUS", heater_status) else: raise AssertionError("Invalid arguments sent") return self._device.log_message def read_pause(self): - return "ON" if self._device.is_paused else "OFF" + paused = "OFF" + if self._device.is_paused: + paused = "ON" + return "HH:MM:SS PAUSE STATUS: {}".format(paused) def write_pause(self, paused): + mode = self._get_output_mode_string() + target = self._get_ramp_target_value() if paused == "ON": self._device.is_paused = True - self._create_log_message("PAUSE STATUS", str(paused)) + output = "HOLDING ON PAUSE AT {} {}".format(self._device.output, mode) + self._create_log_message("RAMP STATUS", output) elif paused == "OFF": self._device.is_paused = False - self._create_log_message("PAUSE STATUS", str(paused)) + output = "RAMPING FROM {} TO {} {} AT A/SEC".format(self._device.output, + target, mode, + self._device.ramp_rate) + self._create_log_message("RAMP STATUS", output) else: raise AssertionError("Invalid arguments sent") return self._device.log_message def read_heater_value(self): - return self._device.heater_value + return "HH:MM:SS HEATER OUTPUT: {} VOLTS".format(self._device.heater_value) def write_heater_value(self, heater_value): self._device.heater_value = heater_value - self._create_log_message("HEATER OUTPUT", str(heater_value)) + self._create_log_message("HEATER OUTPUT", heater_value) return self._device.log_message def read_max_target(self): - return self._device.max_target + mode = self._get_output_mode_string() + return "HH:MM:SS MAX SETTING: {} {}".format(self._device.max_target, mode) + def write_max_target(self, max_target): self._device.max_target = max_target - self._create_log_message("MAX SETTING", str(max_target)) + self._create_log_message("MAX SETTING", max_target) return self._device.log_message def read_mid_target(self): - return self._device.mid_target + mode = self._get_output_mode_string() + return "HH:MM:SS MID SETTING: {} {}".format(self._device.mid_target, mode) def write_mid_target(self, mid_target): self._device.mid_target = mid_target - self._create_log_message("MID SETTING", str(mid_target)) + self._create_log_message("MID SETTING", mid_target) return self._device.log_message def read_limit(self): - return self._device.limit + return "HH:MM:SS VOLTAGE LIMIT: {} VOLTS".format(self._device.limit) def write_limit(self, limit): self._device.limit = limit From d239b59ef5dbb36559d0c8a9fb028b5b6b834afc Mon Sep 17 00:00:00 2001 From: Dominic Oram Date: Mon, 18 Sep 2017 18:23:36 +0100 Subject: [PATCH 0332/1466] Added dummy commands so that lewis would not throw errors --- .../eurotherm/interfaces/stream_interface.py | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/eurotherm/interfaces/stream_interface.py b/lewis_emulators/eurotherm/interfaces/stream_interface.py index 60fdd8a5..81fa259d 100644 --- a/lewis_emulators/eurotherm/interfaces/stream_interface.py +++ b/lewis_emulators/eurotherm/interfaces/stream_interface.py @@ -10,6 +10,15 @@ class EurothermStreamInterface(StreamAdapter): commands = { CmdBuilder("get_current_temperature").eot().escape("0011PV").enq().build(), CmdBuilder("get_ramp_setpoint").eot().escape("0011SP").enq().build(), + CmdBuilder("get_output").eot().escape("0011OP").enq().build(), + CmdBuilder("get_max_output").eot().escape("0011HO").enq().build(), + CmdBuilder("get_autotune").eot().escape("0011AT").enq().build(), + CmdBuilder("get_proportional").eot().escape("0011XP").enq().build(), + CmdBuilder("get_derivative").eot().escape("0011TD").enq().build(), + CmdBuilder("get_integral").eot().escape("0011TI").enq().build(), + CmdBuilder("get_highlim").eot().escape("0011HS").enq().build(), + CmdBuilder("get_lowlim").eot().escape("0011LS").enq().build(), + CmdBuilder("set_ramp_setpoint", arg_sep="").eot().escape("0011").stx().escape("SL").float().etx().any().build(), } @@ -17,7 +26,7 @@ class EurothermStreamInterface(StreamAdapter): # with the emulator we manually added terminators via asyn commands to the device. Lewis will be able to handle this # natively in future versions. See: https://github.com/DMSC-Instrument-Data/lewis/pull/262 in_terminator = "\r\n" - out_terminator = "\r\n" + out_terminator = chr(3) def handle_error(self, request, error): """ @@ -30,6 +39,54 @@ def handle_error(self, request, error): """ self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + def get_proportional(self): + """ + TODO: Get the proportional of the device's PID values + """ + return "\x02XP0" + + def get_integral(self): + """ + TODO: Get the integral of the device's PID values + """ + return "\x02TI0" + + def get_derivative(self): + """ + TODO: Get the derivative of the device's PID values + """ + return "\x02TD0" + + def get_output(self): + """ + TODO: Get the output of the device + """ + return "\x02OP0" + + def get_highlim(self): + """ + TODO: Get the high limit of the device + """ + return "\x02HS0" + + def get_lowlim(self): + """ + TODO: Get the low limit of the device + """ + return "\x02LS0" + + def get_max_output(self): + """ + TODO: Get the max output of the device + """ + return "\x02HO0" + + def get_autotune(self): + """ + TODO: Get the max output of the device + """ + return "\x02AT0" + def get_current_temperature(self): """ Get the current temperature of the device. From fba5c7d78664de3604f1831eff6d40645e8079f7 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 21 Sep 2017 12:07:41 +0100 Subject: [PATCH 0333/1466] Fix emulator --- lewis_emulators/fermichopper/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/fermichopper/device.py b/lewis_emulators/fermichopper/device.py index e07bad5b..355b19c9 100644 --- a/lewis_emulators/fermichopper/device.py +++ b/lewis_emulators/fermichopper/device.py @@ -110,7 +110,7 @@ def set_delay_lowword(self, value): self.update_delay() def update_delay(self): - self.delay = (self.delay_highword * 65536 + self.delay_lowword)/50400.0 + self.delay = (self.delay_highword * 65536 + self.delay_lowword)/50.4 def set_gate_width(self, value): self.gatewidth = value From bd34966c26fc32005a7b8da8e93a9c46924a7343 Mon Sep 17 00:00:00 2001 From: esouthren Date: Thu, 21 Sep 2017 14:16:29 +0100 Subject: [PATCH 0334/1466] About to start working on SNL implementation for states --- lewis_emulators/HFMAGPSU/device.py | 148 ++++++++++++++++-- .../HFMAGPSU/interfaces/stream_interface.py | 22 +-- lewis_emulators/HFMAGPSU/states.py | 43 ++++- 3 files changed, 183 insertions(+), 30 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index ee7ff96a..655c863b 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -1,29 +1,145 @@ from lewis.devices import StateMachineDevice from collections import OrderedDict -from .states import DefaultState +from states import DefaultInitState, DefaultStartedState, DefaultStoppedState class SimulatedHFMAGPSU(StateMachineDevice): def _initialize_data(self): - self.is_output_mode_tesla = False - self.is_heater_on = True - self.is_paused = True - self.output = 0 - self.direction = '-' - self.ramp_target = 'ZERO' - self.heater_value = 1.0 - self.max_target = 20.0 - self.mid_target = 10.0 - self.ramp_rate = 0.5 - self.limit = 10 - self.log_message = "this is the initial log message" + self._is_output_mode_tesla = False + self._is_heater_on = True + self._is_paused = True + self._output = 0 + self._direction = '0' + self._ramp_target = 'MID' + self._heater_value = 0 + self._max_target = 34.92 + self._mid_target = 10.0 + self._ramp_rate = 0.5 + self._limit = 5 + self._log_message = "this is the initial log message" + self._constant = 0.00029 + + self.ready = True def _get_state_handlers(self): - return {DefaultState.NAME: DefaultState()} + return { + 'init': DefaultInitState(), + 'not_ramping': DefaultStoppedState(), + 'ramping': DefaultStartedState(), + } def _get_initial_state(self): - return DefaultState.NAME + return 'init' def _get_transition_handlers(self): - return OrderedDict() + + return OrderedDict([ + (('init', 'not_ramping'), lambda: self.ready), + (('not_ramping', 'ramping'), lambda: not self._is_paused), + (('ramping', 'not_ramping'), lambda: self._is_paused), + ]) + + @property + def direction(self): + return self._direction + + @property + def is_output_mode_tesla(self): + return self._is_output_mode_tesla + + @property + def output(self): + return self._output + + @property + def ramp_target(self): + return self._ramp_target + + @property + def is_heater_on(self): + return self._is_heater_on + + @property + def heater_value(self): + return self._heater_value + + @property + def max_target(self): + return self._max_target + + @property + def mid_target(self): + return self._mid_target + + @property + def ramp_rate(self): + return self._ramp_rate + + @property + def is_paused(self): + return self._is_paused + + @property + def limit(self): + return self._limit + + @property + def log_message(self): + return self._log_message + + @property + def constant(self): + return self._constant + + @direction.setter + def direction(self, d): + self._direction = d + + @is_output_mode_tesla.setter + def is_output_mode_tesla(self, om): + self._is_output_mode_tesla = om + + @ramp_target.setter + def ramp_target(self, rt): + self._ramp_target = rt + + @is_heater_on.setter + def is_heater_on(self, hs): + self._is_heater_on = hs + + @heater_value.setter + def heater_value(self, hv): + self._heater_value = hv + + @max_target.setter + def max_target(self, mt): + self._max_target = mt + + @mid_target.setter + def mid_target(self, mt): + self._mid_target = mt + + @ramp_rate.setter + def ramp_rate(self, rate): + self._ramp_rate = rate + + @is_paused.setter + def is_paused(self, p): + self._is_paused = p + + @limit.setter + def limit(self, lim): + self._limit = lim + + @log_message.setter + def log_message(self, lm): + self._log_message = lm + + @output.setter + def output(self, op): + self._output = op + + @constant.setter + def constant(self, cons): + self._constant = cons diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 8837ca48..273b33fe 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -5,7 +5,6 @@ class HFMAGPSUStreamInterface(StreamAdapter): - # terminators set to ascii ETX in_terminator = "\r\n" out_terminator = "\r\n" @@ -22,6 +21,7 @@ class HFMAGPSUStreamInterface(StreamAdapter): CmdBuilder("read_limit").escape("H V").build(), CmdBuilder("read_pause").escape("P").build(), CmdBuilder("read_heater_value").escape("GET H").build(), + CmdBuilder("read_constant").escape("GET TPA").build(), CmdBuilder("write_direction").escape("D ").arg("-|0|\+").build(), CmdBuilder("write_output_mode").escape("T ").arg("AMPS|TESLA").build(), @@ -32,14 +32,14 @@ class HFMAGPSUStreamInterface(StreamAdapter): CmdBuilder("write_max_target").escape("SET MAX ").float().build(), CmdBuilder("write_mid_target").escape("SET MID ").float().build(), CmdBuilder("write_ramp_rate").escape("SET RAMP ").float().build(), - CmdBuilder("write_limit").escape("S L ").float().build() + CmdBuilder("write_limit").escape("S L ").float().build(), + CmdBuilder("write_constant").escape("SET TPA ").float().build() } def _create_log_message(self, pv, value): current_time = datetime.now().strftime('%H:%M:%S') self._device.log_message = "{} {}: {}".format(current_time, pv, value) - def handle_error(self, request, error): self.log.error("Error occurred at " + repr(request) + ": " + repr(error)) print("Error occurred at " + repr(request) + ": " + repr(error)) @@ -76,14 +76,7 @@ def read_output_mode(self): def read_output(self): # If target is + or - the current output, we are ramping up or down # new output = current output +/- ramp rate - if not self._device.is_paused: - target = self._get_ramp_target_value() - if target >= self._device.output: - self._device.output += self._device.ramp_rate - elif target < : - self._device.output -= self._device.ramp_rate mode = self._get_output_mode_string() - return "HH.MM.SS OUTPUT: {} {} AT {} VOLTS".format(self._device.output, mode, self._device.heater_value) def write_output_mode(self, output_mode): @@ -166,7 +159,6 @@ def read_max_target(self): mode = self._get_output_mode_string() return "HH:MM:SS MAX SETTING: {} {}".format(self._device.max_target, mode) - def write_max_target(self, max_target): self._device.max_target = max_target self._create_log_message("MAX SETTING", max_target) @@ -188,3 +180,11 @@ def write_limit(self, limit): self._device.limit = limit self._create_log_message("VOLTAGE LIMIT", limit) return self._device.log_message + + def read_constant(self): + return "HH:MM:SS FIELD CONSTANT: {} T/A".format(self._device.constant) + + def write_constant(self, constant): + self._device.constant = constant + self._create_log_message("FIELD CONSTANT", constant) + return self._device.log_message diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py index e04bc7a3..7a6d36d7 100644 --- a/lewis_emulators/HFMAGPSU/states.py +++ b/lewis_emulators/HFMAGPSU/states.py @@ -1,6 +1,43 @@ from lewis.core.statemachine import State +from lewis.core import approaches + + +def get_target_value(target, mid_val, max_val): + + if target == "MID": + target_value = mid_val + elif target == "MAX": + target_value = max_val + else: + target_value = 0 + + return target_value + + +class DefaultInitState(State): + pass + + +class DefaultStoppedState(State): + def in_state(self, dt): + pass + + +class DefaultStartedState(State): + def in_state(self, dt): + device = self._context + SMALL = 0.00001 + target = get_target_value(device._ramp_target, + device._mid_target, + device._max_target) + rate = device._ramp_rate + + # Starting ramping towards target value + device._output = approaches.linear(float(device._output), float(target), float(rate), dt) + # If the output equals the target, trigger not_ramping state with _is_paused variable + ramp_complete = abs(float(device._output) - float(target)) < SMALL + if ramp_complete: + device._is_paused = True + -class DefaultState(State): - # default state - NAME = 'Default' From d9335e11b497f13364f015aee617c62d47921fce Mon Sep 17 00:00:00 2001 From: Attah Date: Fri, 22 Sep 2017 13:17:38 +0100 Subject: [PATCH 0335/1466] Final commit for Lakeshore460 before revie ;) --- lewis_emulators/lakeshore460/device.py | 140 +++++++++--------- .../interfaces/stream_interface.py | 8 +- 2 files changed, 70 insertions(+), 78 deletions(-) diff --git a/lewis_emulators/lakeshore460/device.py b/lewis_emulators/lakeshore460/device.py index 50f72f1d..5cf01f97 100644 --- a/lewis_emulators/lakeshore460/device.py +++ b/lewis_emulators/lakeshore460/device.py @@ -4,6 +4,26 @@ from .states import DefaultState +class Channel(object): + def __init__(self): + self.display_filter_window = 0 + self.filter_points = 0 + self.manual_range = 0 + self.max_multiplier = "u" + self.rel_multiplier = "u" + self.magnetic_field_multiplier = "u" + self.rel_mode_multiplier = "u" + self.unit = "G" + self.status = 0 + self.mode = 0 + self.prms = 0 + self.filter = 0 + self.max_hold = 0 + self.rel_mode = 0 + self.auto_range = 0 + self.rel_setpoint = 0 + + class SimulatedLakeshore460(StateMachineDevice): """ Simulated Lakeshore 460 @@ -14,28 +34,13 @@ def _initialize_data(self): Sets the initial state of the device. """ self._idn = "000000000000000000000000000000000000000" - self._unit= "G" - self._status = 0 - self._mode = 0 - self._prms = 0 - self._filter = 0 - self._auto_range = 0 - self._manual_range = 2 - self._max_hold = 0 - self._rel_mode = 0 self._rel_mode_reading = 4906 - self._rel_mode_reading_multiplier = "ON" - self._total_fields = 7.5 self._source = 1 - self._channel = "Channel X" - self._display_filter = 5 - self._filter_points = 9 - self._reading_multiplier = "ON" + self._channel = "X" self._rel_set_point = 500 self._max_reading = 100 - self._rel_multiplier = "ON" - self._magnetic_field_multiplier = "m" self._magnetic_field_reading = 400 + self.channels = {"X": Channel(), "Y":Channel(), "Z": Channel(), "V": Channel()} def _get_state_handlers(self): """ @@ -55,6 +60,7 @@ def _get_transition_handlers(self): """ return OrderedDict() + @property def idn(self): """ @@ -74,140 +80,126 @@ def unit(self): """ :return: unit for the device """ - return self._unit + return self.channels[self._channel].unit @unit.setter def unit(self, unit): """ :param unit: set unit for the device """ - self._unit = unit + self.channels[self._channel].unit = unit @property def status(self): """ :return: status of device. If its on/off """ - return self._status + return self.channels[self._channel].status @status.setter - def status(self,status): + def status(self, status): """ :param status: sets the device status, if device is on/off """ - self._status = status + self.channels[self._channel].status = status @property def mode(self): """ :return: AC or DC unit of measurement """ - return self._mode + return self.channels[self._channel].mode @mode.setter def mode(self, mode): - """ + """ :param mode: sets the device mode, if device is set to AC or DC measurement """ - self._mode = mode + self.channels[self._channel].mode = mode @property def prms(self): """ :return: Peak /RMS Field reading """ - return self._prms + return self.channels[self._channel].prms @prms.setter def prms(self, prms): """ :param prms: Sets the value to Peak or RMS """ - self._prms = prms + self.channels[self._channel].prms = prms @property def filter(self): """ :return: returns filter """ - return self._filter + return self.channels[self._channel].filter @filter.setter def filter(self, state): """ :param state: sets filter state (binary) """ - self._filter = state + self.channels[self._channel].filter = state @property def max_hold(self): """ :return: returns binary max hold """ - return self._max_hold + return self.channels[self._channel].max_hold @max_hold.setter def max_hold(self, max_hold): """ :param max_hold: sets the max hold value """ - self._max_hold = max_hold + self.channels[self._channel].max_hold = max_hold @property def rel_mode(self): """ :return: Return relative mode reading """ - return self._rel_mode + return self.channels[self._channel].rel_mode @rel_mode.setter def rel_mode(self, rel_mode): """ :param rel_mode: Sets the relative mode reading """ - self._rel_mode = rel_mode + self.channels[self._channel].rel_mode = rel_mode @property def auto_range(self): """ :return: Returns the auto range """ - return self._auto_range + return self.channels[self._channel].auto_range @auto_range.setter def auto_range(self, range): """ :param range: sets the auto range """ - self._auto_range = range + self.channels[self._channel].auto_range = range @property def manual_range(self): """ :return: the value that has been set for the manual range """ - return self._manual_range + return self.channels[self._channel].manual_range @manual_range.setter def manual_range(self, range): """ :param range: sets the manual range """ - self._manual_range = range - - @property - def total_fields(self): - """ - :return: the total field value X Y Z Field - """ - return self._total_fields - - @total_fields.setter - def total_fields(self, total): - """ - :param total: sets the total field value for X Y Z Field - """ - self._total_fields = total + self.channels[self._channel].manual_range = range @property def source(self): @@ -237,75 +229,78 @@ def channel(self, channel): """ self._channel = channel + @property def display_filter(self): """ - :return: returns the percentage for display field opening + :return: returns the percentage for display filter opening """ - return self._display_filter + return self.channels[self._channel].display_filter_window @display_filter.setter def display_filter(self, percentage): """ :param percentage: sets the percentage for display filter opening """ - self._display_filter = percentage + self.channels[self._channel].display_filter_window = percentage @property def filter_points(self): """ :return: returns filter points value should be between 1 to 10 """ - return self._filter_points + return self.channels[self._channel].filter_points @filter_points.setter def filter_points(self, points): """ :param points: sets filter points value should be between 1 to 10 """ - self._filter_points = points + self.channels[self._channel].filter_points = points + @property def rel_multiplier(self): """ :return: Returns relative multiplier for device """ - return self._rel_multiplier + return self.channels[self._channel].rel_multiplier @rel_multiplier.setter def rel_multiplier(self, multiplier): """ :param multiplier: sets the relative multiplier for the device """ - self._rel_multiplier = multiplier + self.channels[self._channel].rel_multiplier = multiplier @property - def reading_multiplier(self): + def max_reading_multiplier(self): """ - :return: return reading multiplier + :return: return max reading multiplier """ - return self._reading_multiplier + return self.channels[self._channel].max_multiplier - @reading_multiplier.setter - def reading_multiplier(self, multiplier): + + @max_reading_multiplier.setter + def max_reading_multiplier(self, multiplier): """ - :param multiplier: sets the reading multiplier for the device + :param multiplier: sets the max reading multiplier for the device """ - self._reading_multiplier = multiplier + self.channels[self._channel].max_multiplier = multiplier @property def rel_set_point(self): """ :return: return relative mode setpoint for Lakeshore 460 """ - return self._rel_set_point + return self.channels[self._channel].rel_setpoint @rel_set_point.setter def rel_set_point(self, setpoint): """ :param setpoint: sets the relative mode set point """ - self._rel_set_point = setpoint + self.channels[self._channel].rel_setpoint = setpoint @property def max_reading(self): @@ -340,28 +335,28 @@ def rel_mode_reading_multiplier(self): """ :return: Return relative mode multiplier """ - return self._rel_mode_reading_multiplier + return self.channels[self._channel].rel_mode_multiplier @rel_mode_reading_multiplier.setter def rel_mode_reading_multiplier(self, multiplier): """ :param multiplier: sets relative mode multiplier """ - self._rel_mode_reading_multiplier = multiplier + self.channels[self._channel].rel_mode_multiplier = multiplier @property def magnetic_field_multiplier(self): """ :return: Returns Magnetic Field Multiplier """ - return self._magnetic_field_multiplier + return self.channels[self._channel].magnetic_field_multiplier @magnetic_field_multiplier.setter def magnetic_field_multiplier(self, multiplier): """ :param multiplier: Sets Magnetic field multiplier i.e micro, kilo, etc. """ - self._magnetic_field_multiplier = multiplier + self.channels[self._channel].magnetic_field_multiplier = multiplier @property def magnetic_field_reading(self): @@ -376,6 +371,3 @@ def magnetic_field_reading(self, field): :param field: sets Magnetic Field Reading """ self._magnetic_field_reading = field - - - diff --git a/lewis_emulators/lakeshore460/interfaces/stream_interface.py b/lewis_emulators/lakeshore460/interfaces/stream_interface.py index 46bec520..51bd8f1f 100644 --- a/lewis_emulators/lakeshore460/interfaces/stream_interface.py +++ b/lewis_emulators/lakeshore460/interfaces/stream_interface.py @@ -30,7 +30,7 @@ class Lakeshore460StreamInterface(StreamAdapter): CmdBuilder("get_source").escape("VSRC?").build(), CmdBuilder("set_source").escape("VSRC ").digit().build(), CmdBuilder("get_channel").escape("CHNL?").build(), - CmdBuilder("set_channel").escape("CHNL ").digit().build(), + CmdBuilder("set_channel").escape("CHNL ").arg("X|Y|Z|V").build(), CmdBuilder("get_display_filter_window").escape("FWIN?").build(), CmdBuilder("set_display_filter_window").escape("FWIN ").int().build(), CmdBuilder("set_filter_points").escape("FNUM ").int().build(), @@ -49,8 +49,8 @@ class Lakeshore460StreamInterface(StreamAdapter): } def handle_error(self,request, error): - self.log.error("An error occurred at request {0} : {1} ").format(request, error) - print("An error occurred at request {0} : {1} ").format(request, error) + self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) + print("An error occurred at request" + repr(request) + ": " + repr(error)) def get_IDN(self): return "{0}".format(self._device.idn) @@ -149,7 +149,7 @@ def read_max_reading(self): return "{0}".format( self._device.max_reading) def read_max_reading_multiplier(self): - return "{0}".format( self._device.reading_multiplier) + return "{0}".format( self._device.max_reading_multiplier) def get_relative_mode_reading(self): return "{0}".format(self._device.rel_mode_reading) From a8d9fda2afb4011eb0a7b354d847a4f808f86c03 Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 25 Sep 2017 11:10:39 +0100 Subject: [PATCH 0336/1466] Lewis import changed at v1.0.3 --- lewis_emulators/CCD100/interfaces/stream_interface.py | 4 ++-- lewis_emulators/ag33220a/interfaces/stream_interface.py | 4 ++-- lewis_emulators/amint2l/interfaces/stream_interface.py | 4 ++-- lewis_emulators/cybaman/interfaces/stream_interface.py | 5 ++--- lewis_emulators/eurotherm/interfaces/stream_interface.py | 4 ++-- lewis_emulators/fermichopper/interfaces/stream_interface.py | 4 ++-- lewis_emulators/hlg/interfaces/stream_interface.py | 4 ++-- lewis_emulators/ieg/interfaces/stream_interface.py | 4 ++-- .../instron_stress_rig/interfaces/stream_interface.py | 4 ++-- lewis_emulators/iris_cryo_valve/__init__.py | 1 + .../iris_cryo_valve/interfaces/stream_interface.py | 4 ++-- lewis_emulators/keithley_2400/interfaces/stream_interface.py | 4 ++-- lewis_emulators/kepco/interfaces/stream_interface.py | 4 ++-- lewis_emulators/mk2_chopper/interfaces/stream_interface.py | 4 ++-- lewis_emulators/neocera_ltc21/interfaces/stream_interface.py | 4 ++-- lewis_emulators/rknps/interfaces/stream_interface.py | 4 ++-- .../interfaces/HRPD_stream_interface.py | 4 ++-- .../interfaces/POLARIS_stream_interface.py | 4 ++-- lewis_emulators/superlogics/interfaces/stream_interface.py | 4 ++-- .../tdk_lambda_genesys/interfaces/stream_interface.py | 5 ++--- lewis_emulators/tpg26x/interfaces/stream_interface.py | 4 ++-- .../volumetric_rig/interfaces/stream_interface.py | 4 ++-- 22 files changed, 43 insertions(+), 44 deletions(-) diff --git a/lewis_emulators/CCD100/interfaces/stream_interface.py b/lewis_emulators/CCD100/interfaces/stream_interface.py index 2bbda146..4b2af503 100644 --- a/lewis_emulators/CCD100/interfaces/stream_interface.py +++ b/lewis_emulators/CCD100/interfaces/stream_interface.py @@ -1,4 +1,4 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd import random SP_COMM = "spv" @@ -6,7 +6,7 @@ READING_COMM = "r" -class CCD100StreamInterface(StreamAdapter): +class CCD100StreamInterface(StreamInterface): commands = { Cmd("get_sp", "^[a-h]" + SP_COMM + "\?$"), diff --git a/lewis_emulators/ag33220a/interfaces/stream_interface.py b/lewis_emulators/ag33220a/interfaces/stream_interface.py index c3e20752..24a9e7f9 100644 --- a/lewis_emulators/ag33220a/interfaces/stream_interface.py +++ b/lewis_emulators/ag33220a/interfaces/stream_interface.py @@ -1,10 +1,10 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd import traceback NUM_MIN_MAX = "([\-0-9.]+|MAX|MIN)" -class AG33220AStreamInterface(StreamAdapter): +class AG33220AStreamInterface(StreamInterface): commands = { Cmd("get_amplitude", "^VOLT\?$"), diff --git a/lewis_emulators/amint2l/interfaces/stream_interface.py b/lewis_emulators/amint2l/interfaces/stream_interface.py index 180f27c2..13930f87 100644 --- a/lewis_emulators/amint2l/interfaces/stream_interface.py +++ b/lewis_emulators/amint2l/interfaces/stream_interface.py @@ -1,10 +1,10 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder @has_log -class Amint2lStreamInterface(StreamAdapter): +class Amint2lStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/cybaman/interfaces/stream_interface.py b/lewis_emulators/cybaman/interfaces/stream_interface.py index c963e36f..e2973724 100644 --- a/lewis_emulators/cybaman/interfaces/stream_interface.py +++ b/lewis_emulators/cybaman/interfaces/stream_interface.py @@ -1,10 +1,9 @@ -from lewis.adapters.stream import StreamAdapter, Cmd -from time import sleep +from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log -class CybamanStreamInterface(StreamAdapter): +class CybamanStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/eurotherm/interfaces/stream_interface.py b/lewis_emulators/eurotherm/interfaces/stream_interface.py index 81fa259d..c7f3a6a5 100644 --- a/lewis_emulators/eurotherm/interfaces/stream_interface.py +++ b/lewis_emulators/eurotherm/interfaces/stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.utils.command_builder import CmdBuilder -class EurothermStreamInterface(StreamAdapter): +class EurothermStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/fermichopper/interfaces/stream_interface.py b/lewis_emulators/fermichopper/interfaces/stream_interface.py index 88941084..5786d09d 100644 --- a/lewis_emulators/fermichopper/interfaces/stream_interface.py +++ b/lewis_emulators/fermichopper/interfaces/stream_interface.py @@ -1,4 +1,4 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd import math @@ -43,7 +43,7 @@ def append(data): return data + JulichChecksum._calculate(data) -class FermichopperStreamInterface(StreamAdapter): +class FermichopperStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { diff --git a/lewis_emulators/hlg/interfaces/stream_interface.py b/lewis_emulators/hlg/interfaces/stream_interface.py index 7b9d470e..55104f26 100644 --- a/lewis_emulators/hlg/interfaces/stream_interface.py +++ b/lewis_emulators/hlg/interfaces/stream_interface.py @@ -1,4 +1,4 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder @@ -14,7 +14,7 @@ @has_log -class HlgStreamInterface(StreamAdapter): +class HlgStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/ieg/interfaces/stream_interface.py b/lewis_emulators/ieg/interfaces/stream_interface.py index 6f0db03f..b88c9e05 100644 --- a/lewis_emulators/ieg/interfaces/stream_interface.py +++ b/lewis_emulators/ieg/interfaces/stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log -class IegStreamInterface(StreamAdapter): +class IegStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { diff --git a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py index 5c3096b7..a9492664 100644 --- a/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py +++ b/lewis_emulators/instron_stress_rig/interfaces/stream_interface.py @@ -1,7 +1,7 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd -class InstronStreamInterface(StreamAdapter): +class InstronStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { diff --git a/lewis_emulators/iris_cryo_valve/__init__.py b/lewis_emulators/iris_cryo_valve/__init__.py index 79e07f99..ab38c500 100644 --- a/lewis_emulators/iris_cryo_valve/__init__.py +++ b/lewis_emulators/iris_cryo_valve/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedIrisCryoValve +framework_version = '1.1.1' __all__ = ['SimulatedIrisCryoValve'] diff --git a/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py b/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py index 9ef94fcf..f59d01ff 100644 --- a/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py +++ b/lewis_emulators/iris_cryo_valve/interfaces/stream_interface.py @@ -1,7 +1,7 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd -class IrisCryoValveStreamInterface(StreamAdapter): +class IrisCryoValveStreamInterface(StreamInterface): commands = { Cmd("get_status", "^\?$"), diff --git a/lewis_emulators/keithley_2400/interfaces/stream_interface.py b/lewis_emulators/keithley_2400/interfaces/stream_interface.py index d88c7b10..a2a253aa 100644 --- a/lewis_emulators/keithley_2400/interfaces/stream_interface.py +++ b/lewis_emulators/keithley_2400/interfaces/stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd from ..control_modes import OutputMode -class Keithley2400StreamInterface(StreamAdapter): +class Keithley2400StreamInterface(StreamInterface): # Commands that we expect via serial during normal operation serial_commands = { diff --git a/lewis_emulators/kepco/interfaces/stream_interface.py b/lewis_emulators/kepco/interfaces/stream_interface.py index fb9a6be7..9680dd14 100644 --- a/lewis_emulators/kepco/interfaces/stream_interface.py +++ b/lewis_emulators/kepco/interfaces/stream_interface.py @@ -1,9 +1,9 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.utils.command_builder import CmdBuilder from lewis.core.logging import has_log @has_log -class KepcoStreamInterface(StreamAdapter): +class KepcoStreamInterface(StreamInterface): in_terminator="\r\n" out_terminator="\r\n" diff --git a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py index 060690ef..4412f00e 100644 --- a/lewis_emulators/mk2_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/mk2_chopper/interfaces/stream_interface.py @@ -1,4 +1,4 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd from ..chopper_type import ChopperType @@ -18,7 +18,7 @@ def filled_int(val, length): return str(converted_val).zfill(length) -class Mk2ChopperStreamInterface(StreamAdapter): +class Mk2ChopperStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { diff --git a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py index b13f0130..6d4ace43 100644 --- a/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py +++ b/lewis_emulators/neocera_ltc21/interfaces/stream_interface.py @@ -1,6 +1,6 @@ import re -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.neocera_ltc21.constants import HEATER_INDEX, CONTROL_TYPE_MAX, CONTROL_TYPE_MIN, ANALOG_INDEX from lewis_emulators.neocera_ltc21.device_errors import NeoceraDeviceErrors @@ -8,7 +8,7 @@ from lewis_emulators.utils.command_builder import CmdBuilder -class NeoceraStreamInterface(StreamAdapter): +class NeoceraStreamInterface(StreamInterface): """ Stream interface for the serial port. """ diff --git a/lewis_emulators/rknps/interfaces/stream_interface.py b/lewis_emulators/rknps/interfaces/stream_interface.py index cb24b8b4..d59f05fa 100644 --- a/lewis_emulators/rknps/interfaces/stream_interface.py +++ b/lewis_emulators/rknps/interfaces/stream_interface.py @@ -1,9 +1,9 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log @has_log -class RknpsStreamInterface(StreamAdapter): +class RknpsStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/rotating_sample_changer/interfaces/HRPD_stream_interface.py b/lewis_emulators/rotating_sample_changer/interfaces/HRPD_stream_interface.py index 1fdfbb07..0089775e 100644 --- a/lewis_emulators/rotating_sample_changer/interfaces/HRPD_stream_interface.py +++ b/lewis_emulators/rotating_sample_changer/interfaces/HRPD_stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import Cmd, StreamAdapter +from lewis.adapters.stream import Cmd, StreamInterface from ..states import Errors -class HRPDSampleChangerStreamInterface(StreamAdapter): +class HRPDSampleChangerStreamInterface(StreamInterface): protocol = "HRPD" commands = { diff --git a/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py b/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py index 58ad32cf..5b94c181 100644 --- a/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py +++ b/lewis_emulators/rotating_sample_changer/interfaces/POLARIS_stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import Cmd, StreamAdapter +from lewis.adapters.stream import Cmd, StreamInterface from ..states import Errors -class POLARISSampleChangerStreamInterface(StreamAdapter): +class POLARISSampleChangerStreamInterface(StreamInterface): protocol = "POLARIS" commands = { diff --git a/lewis_emulators/superlogics/interfaces/stream_interface.py b/lewis_emulators/superlogics/interfaces/stream_interface.py index c327dcb7..36ccfbbe 100644 --- a/lewis_emulators/superlogics/interfaces/stream_interface.py +++ b/lewis_emulators/superlogics/interfaces/stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.utils.command_builder import CmdBuilder -class SuperlogicsStreamInterface(StreamAdapter): +class SuperlogicsStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py b/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py index 437e20ed..c8ab914e 100644 --- a/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py +++ b/lewis_emulators/tdk_lambda_genesys/interfaces/stream_interface.py @@ -1,9 +1,8 @@ -from lewis.adapters.stream import StreamAdapter, Cmd -from lewis.core.logging import has_log +from lewis.adapters.stream import StreamInterface, Cmd from lewis_emulators.utils.command_builder import CmdBuilder -class TDKLambdaGenesysStreamInterface(StreamAdapter): +class TDKLambdaGenesysStreamInterface(StreamInterface): commands = { CmdBuilder("write_voltage").escape("PV ").float().build(), CmdBuilder("read_setpoint_voltage").escape("PV?").build(), diff --git a/lewis_emulators/tpg26x/interfaces/stream_interface.py b/lewis_emulators/tpg26x/interfaces/stream_interface.py index 07970b82..24cddc77 100644 --- a/lewis_emulators/tpg26x/interfaces/stream_interface.py +++ b/lewis_emulators/tpg26x/interfaces/stream_interface.py @@ -1,8 +1,8 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.utils.command_builder import CmdBuilder -class Tpg26xStreamInterface(StreamAdapter): +class Tpg26xStreamInterface(StreamInterface): """ Stream interface for the serial port """ diff --git a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py index 08d3b216..0f1fe505 100644 --- a/lewis_emulators/volumetric_rig/interfaces/stream_interface.py +++ b/lewis_emulators/volumetric_rig/interfaces/stream_interface.py @@ -1,10 +1,10 @@ -from lewis.adapters.stream import StreamAdapter, Cmd +from lewis.adapters.stream import StreamInterface, Cmd from ..sensor_status import SensorStatus from ..utilities import format_int, convert_raw_to_int, convert_raw_to_float, convert_raw_to_bool from ..valve_status import ValveStatus -class VolumetricRigStreamInterface(StreamAdapter): +class VolumetricRigStreamInterface(StreamInterface): # The rig typically splits a command by whitespace and then uses the arguments it needs and then ignores the rest # so "IDN" will respond as "IDN BLAH BLAH BLAH" and "BCS 01" would be the same as "BCS 01 02 03". From 77399f90c2ead87209060f44dda5fd205a37684e Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 25 Sep 2017 11:13:57 +0100 Subject: [PATCH 0337/1466] Framework versions need to be specified in latest verion of Lewis --- lewis_emulators/CCD100/__init__.py | 1 + lewis_emulators/ag33220a/__init__.py | 1 + lewis_emulators/amint2l/__init__.py | 1 + lewis_emulators/cybaman/__init__.py | 1 + lewis_emulators/eurotherm/__init__.py | 1 + lewis_emulators/fermichopper/__init__.py | 1 + lewis_emulators/hlg/__init__.py | 1 + lewis_emulators/ieg/__init__.py | 1 + lewis_emulators/instron_stress_rig/__init__.py | 1 + lewis_emulators/iris_cryo_valve/__init__.py | 2 +- lewis_emulators/keithley_2400/__init__.py | 1 + lewis_emulators/kepco/__init__.py | 1 + lewis_emulators/mk2_chopper/__init__.py | 1 + lewis_emulators/neocera_ltc21/__init__.py | 1 + lewis_emulators/rknps/__init__.py | 1 + lewis_emulators/rotating_sample_changer/__init__.py | 1 + lewis_emulators/superlogics/__init__.py | 1 + lewis_emulators/tdk_lambda_genesys/__init__.py | 1 + lewis_emulators/tpg26x/__init__.py | 1 + lewis_emulators/volumetric_rig/__init__.py | 1 + 20 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/CCD100/__init__.py b/lewis_emulators/CCD100/__init__.py index b50a6e12..92652d97 100644 --- a/lewis_emulators/CCD100/__init__.py +++ b/lewis_emulators/CCD100/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedCCD100 +framework_version = '1.1.1' __all__ = ['SimulatedCCD100'] diff --git a/lewis_emulators/ag33220a/__init__.py b/lewis_emulators/ag33220a/__init__.py index 2588d335..9887ff0a 100644 --- a/lewis_emulators/ag33220a/__init__.py +++ b/lewis_emulators/ag33220a/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedAG33220A +framework_version = '1.1.1' __all__ = ['SimulatedAG33220A'] diff --git a/lewis_emulators/amint2l/__init__.py b/lewis_emulators/amint2l/__init__.py index 87d46bf2..3563da5e 100644 --- a/lewis_emulators/amint2l/__init__.py +++ b/lewis_emulators/amint2l/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedAmint2l +framework_version = '1.1.1' __all__ = ['SimulatedAmint2l'] diff --git a/lewis_emulators/cybaman/__init__.py b/lewis_emulators/cybaman/__init__.py index 5116ed8f..83512d50 100644 --- a/lewis_emulators/cybaman/__init__.py +++ b/lewis_emulators/cybaman/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedCybaman +framework_version = '1.1.1' __all__ = ['SimulatedCybaman'] diff --git a/lewis_emulators/eurotherm/__init__.py b/lewis_emulators/eurotherm/__init__.py index 49281812..16dc382d 100644 --- a/lewis_emulators/eurotherm/__init__.py +++ b/lewis_emulators/eurotherm/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedEurotherm +framework_version = '1.1.1' __all__ = ['SimulatedEurotherm'] diff --git a/lewis_emulators/fermichopper/__init__.py b/lewis_emulators/fermichopper/__init__.py index 5a8a6feb..48bc68b7 100644 --- a/lewis_emulators/fermichopper/__init__.py +++ b/lewis_emulators/fermichopper/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedFermichopper +framework_version = '1.1.1' __all__ = ['SimulatedFermichopper'] diff --git a/lewis_emulators/hlg/__init__.py b/lewis_emulators/hlg/__init__.py index 841f916d..6ae23d64 100644 --- a/lewis_emulators/hlg/__init__.py +++ b/lewis_emulators/hlg/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedHgl +framework_version = '1.1.1' __all__ = ['SimulatedHgl'] diff --git a/lewis_emulators/ieg/__init__.py b/lewis_emulators/ieg/__init__.py index e0ffc397..fcd287cc 100644 --- a/lewis_emulators/ieg/__init__.py +++ b/lewis_emulators/ieg/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedIeg +framework_version = '1.1.1' __all__ = ['SimulatedIeg'] diff --git a/lewis_emulators/instron_stress_rig/__init__.py b/lewis_emulators/instron_stress_rig/__init__.py index 68fd3cac..0fa8db2c 100644 --- a/lewis_emulators/instron_stress_rig/__init__.py +++ b/lewis_emulators/instron_stress_rig/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedInstron +framework_version = '1.1.1' __all__ = ['SimulatedInstron'] diff --git a/lewis_emulators/iris_cryo_valve/__init__.py b/lewis_emulators/iris_cryo_valve/__init__.py index ab38c500..7ad83ee5 100644 --- a/lewis_emulators/iris_cryo_valve/__init__.py +++ b/lewis_emulators/iris_cryo_valve/__init__.py @@ -1,4 +1,4 @@ from .device import SimulatedIrisCryoValve -framework_version = '1.1.1' +framework_version = '1.1.1' __all__ = ['SimulatedIrisCryoValve'] diff --git a/lewis_emulators/keithley_2400/__init__.py b/lewis_emulators/keithley_2400/__init__.py index ea6e9ec3..bc0ea799 100644 --- a/lewis_emulators/keithley_2400/__init__.py +++ b/lewis_emulators/keithley_2400/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedKeithley2400 +framework_version = '1.1.1' __all__ = ['SimulatedKeithley2400'] diff --git a/lewis_emulators/kepco/__init__.py b/lewis_emulators/kepco/__init__.py index 82be7039..eaeeac74 100644 --- a/lewis_emulators/kepco/__init__.py +++ b/lewis_emulators/kepco/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedKepco +framework_version = '1.1.1' __all__ = ['SimulatedKepco'] diff --git a/lewis_emulators/mk2_chopper/__init__.py b/lewis_emulators/mk2_chopper/__init__.py index 37fe266c..6e6e696d 100644 --- a/lewis_emulators/mk2_chopper/__init__.py +++ b/lewis_emulators/mk2_chopper/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedMk2Chopper +framework_version = '1.1.1' __all__ = ['SimulatedMk2Chopper2400'] diff --git a/lewis_emulators/neocera_ltc21/__init__.py b/lewis_emulators/neocera_ltc21/__init__.py index 3856b097..14e1383e 100644 --- a/lewis_emulators/neocera_ltc21/__init__.py +++ b/lewis_emulators/neocera_ltc21/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedNeocera +framework_version = '1.1.1' __all__ = ['SimulatedNeocera'] diff --git a/lewis_emulators/rknps/__init__.py b/lewis_emulators/rknps/__init__.py index d7f2221d..d8a19c17 100644 --- a/lewis_emulators/rknps/__init__.py +++ b/lewis_emulators/rknps/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedRknps +framework_version = '1.1.1' __all__ = ['SimulatedRknps'] diff --git a/lewis_emulators/rotating_sample_changer/__init__.py b/lewis_emulators/rotating_sample_changer/__init__.py index 9408c5b3..683cd114 100644 --- a/lewis_emulators/rotating_sample_changer/__init__.py +++ b/lewis_emulators/rotating_sample_changer/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedSampleChanger +framework_version = '1.1.1' __all__ = ['SimulatedSampleChanger'] diff --git a/lewis_emulators/superlogics/__init__.py b/lewis_emulators/superlogics/__init__.py index d5942cbe..0f29e4b6 100644 --- a/lewis_emulators/superlogics/__init__.py +++ b/lewis_emulators/superlogics/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedSuperlogics +framework_version = '1.1.1' __all__ = ['SimulatedSuperlogics'] diff --git a/lewis_emulators/tdk_lambda_genesys/__init__.py b/lewis_emulators/tdk_lambda_genesys/__init__.py index be04b16e..50ee3e49 100644 --- a/lewis_emulators/tdk_lambda_genesys/__init__.py +++ b/lewis_emulators/tdk_lambda_genesys/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedTDKLambdaGenesys +framework_version = '1.1.1' __all__ = ['SimulatedTDKLambdaGenesys'] diff --git a/lewis_emulators/tpg26x/__init__.py b/lewis_emulators/tpg26x/__init__.py index 962e63fb..6b92f0df 100644 --- a/lewis_emulators/tpg26x/__init__.py +++ b/lewis_emulators/tpg26x/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedTpg26x +framework_version = '1.1.1' __all__ = ['SimulatedTpg26x'] diff --git a/lewis_emulators/volumetric_rig/__init__.py b/lewis_emulators/volumetric_rig/__init__.py index c45f2475..c752a072 100644 --- a/lewis_emulators/volumetric_rig/__init__.py +++ b/lewis_emulators/volumetric_rig/__init__.py @@ -1,3 +1,4 @@ from .device import SimulatedVolumetricRig +framework_version = '1.1.1' __all__ = ['SimulatedVolumetricRig'] From 27444d84a27309e73f0196e381d1bb93176094de Mon Sep 17 00:00:00 2001 From: AdrianPotter Date: Mon, 25 Sep 2017 12:25:41 +0100 Subject: [PATCH 0338/1466] Pin emulators to latest version --- lewis_emulators/CCD100/__init__.py | 3 ++- lewis_emulators/ag33220a/__init__.py | 3 ++- lewis_emulators/amint2l/__init__.py | 3 ++- lewis_emulators/cybaman/__init__.py | 3 ++- lewis_emulators/eurotherm/__init__.py | 3 ++- lewis_emulators/fermichopper/__init__.py | 3 ++- lewis_emulators/hlg/__init__.py | 3 ++- lewis_emulators/ieg/__init__.py | 3 ++- lewis_emulators/instron_stress_rig/__init__.py | 3 ++- lewis_emulators/iris_cryo_valve/__init__.py | 3 ++- lewis_emulators/keithley_2400/__init__.py | 3 ++- lewis_emulators/kepco/__init__.py | 3 ++- lewis_emulators/lewis_versions.py | 2 ++ lewis_emulators/mk2_chopper/__init__.py | 3 ++- lewis_emulators/neocera_ltc21/__init__.py | 3 ++- lewis_emulators/rknps/__init__.py | 3 ++- lewis_emulators/rotating_sample_changer/__init__.py | 3 ++- lewis_emulators/superlogics/__init__.py | 3 ++- lewis_emulators/tdk_lambda_genesys/__init__.py | 3 ++- lewis_emulators/tpg26x/__init__.py | 3 ++- lewis_emulators/volumetric_rig/__init__.py | 3 ++- 21 files changed, 42 insertions(+), 20 deletions(-) create mode 100644 lewis_emulators/lewis_versions.py diff --git a/lewis_emulators/CCD100/__init__.py b/lewis_emulators/CCD100/__init__.py index 92652d97..6326cc7d 100644 --- a/lewis_emulators/CCD100/__init__.py +++ b/lewis_emulators/CCD100/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedCCD100 +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedCCD100'] diff --git a/lewis_emulators/ag33220a/__init__.py b/lewis_emulators/ag33220a/__init__.py index 9887ff0a..847b7d31 100644 --- a/lewis_emulators/ag33220a/__init__.py +++ b/lewis_emulators/ag33220a/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedAG33220A +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedAG33220A'] diff --git a/lewis_emulators/amint2l/__init__.py b/lewis_emulators/amint2l/__init__.py index 3563da5e..39cb613b 100644 --- a/lewis_emulators/amint2l/__init__.py +++ b/lewis_emulators/amint2l/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedAmint2l +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedAmint2l'] diff --git a/lewis_emulators/cybaman/__init__.py b/lewis_emulators/cybaman/__init__.py index 83512d50..b7a9f38f 100644 --- a/lewis_emulators/cybaman/__init__.py +++ b/lewis_emulators/cybaman/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedCybaman +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedCybaman'] diff --git a/lewis_emulators/eurotherm/__init__.py b/lewis_emulators/eurotherm/__init__.py index 16dc382d..b73f0ebc 100644 --- a/lewis_emulators/eurotherm/__init__.py +++ b/lewis_emulators/eurotherm/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedEurotherm +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedEurotherm'] diff --git a/lewis_emulators/fermichopper/__init__.py b/lewis_emulators/fermichopper/__init__.py index 48bc68b7..b5d250d6 100644 --- a/lewis_emulators/fermichopper/__init__.py +++ b/lewis_emulators/fermichopper/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedFermichopper +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedFermichopper'] diff --git a/lewis_emulators/hlg/__init__.py b/lewis_emulators/hlg/__init__.py index 6ae23d64..9c311b5f 100644 --- a/lewis_emulators/hlg/__init__.py +++ b/lewis_emulators/hlg/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedHgl +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedHgl'] diff --git a/lewis_emulators/ieg/__init__.py b/lewis_emulators/ieg/__init__.py index fcd287cc..a5c59068 100644 --- a/lewis_emulators/ieg/__init__.py +++ b/lewis_emulators/ieg/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedIeg +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedIeg'] diff --git a/lewis_emulators/instron_stress_rig/__init__.py b/lewis_emulators/instron_stress_rig/__init__.py index 0fa8db2c..3a9c999e 100644 --- a/lewis_emulators/instron_stress_rig/__init__.py +++ b/lewis_emulators/instron_stress_rig/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedInstron +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedInstron'] diff --git a/lewis_emulators/iris_cryo_valve/__init__.py b/lewis_emulators/iris_cryo_valve/__init__.py index 7ad83ee5..d6c31e64 100644 --- a/lewis_emulators/iris_cryo_valve/__init__.py +++ b/lewis_emulators/iris_cryo_valve/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedIrisCryoValve +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedIrisCryoValve'] diff --git a/lewis_emulators/keithley_2400/__init__.py b/lewis_emulators/keithley_2400/__init__.py index bc0ea799..b75bd460 100644 --- a/lewis_emulators/keithley_2400/__init__.py +++ b/lewis_emulators/keithley_2400/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedKeithley2400 +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedKeithley2400'] diff --git a/lewis_emulators/kepco/__init__.py b/lewis_emulators/kepco/__init__.py index eaeeac74..3f4ae5e6 100644 --- a/lewis_emulators/kepco/__init__.py +++ b/lewis_emulators/kepco/__init__.py @@ -1,5 +1,6 @@ from .device import SimulatedKepco +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedKepco'] diff --git a/lewis_emulators/lewis_versions.py b/lewis_emulators/lewis_versions.py new file mode 100644 index 00000000..79a658b6 --- /dev/null +++ b/lewis_emulators/lewis_versions.py @@ -0,0 +1,2 @@ +LEWIS_1_1_1 = "1.1.1" +LEWIS_LATEST = LEWIS_1_1_1 \ No newline at end of file diff --git a/lewis_emulators/mk2_chopper/__init__.py b/lewis_emulators/mk2_chopper/__init__.py index 6e6e696d..9b32df93 100644 --- a/lewis_emulators/mk2_chopper/__init__.py +++ b/lewis_emulators/mk2_chopper/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedMk2Chopper +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedMk2Chopper2400'] diff --git a/lewis_emulators/neocera_ltc21/__init__.py b/lewis_emulators/neocera_ltc21/__init__.py index 14e1383e..e2e2d374 100644 --- a/lewis_emulators/neocera_ltc21/__init__.py +++ b/lewis_emulators/neocera_ltc21/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedNeocera +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedNeocera'] diff --git a/lewis_emulators/rknps/__init__.py b/lewis_emulators/rknps/__init__.py index d8a19c17..41aea57d 100644 --- a/lewis_emulators/rknps/__init__.py +++ b/lewis_emulators/rknps/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedRknps +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedRknps'] diff --git a/lewis_emulators/rotating_sample_changer/__init__.py b/lewis_emulators/rotating_sample_changer/__init__.py index 683cd114..625a9b8b 100644 --- a/lewis_emulators/rotating_sample_changer/__init__.py +++ b/lewis_emulators/rotating_sample_changer/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedSampleChanger +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedSampleChanger'] diff --git a/lewis_emulators/superlogics/__init__.py b/lewis_emulators/superlogics/__init__.py index 0f29e4b6..96701462 100644 --- a/lewis_emulators/superlogics/__init__.py +++ b/lewis_emulators/superlogics/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedSuperlogics +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedSuperlogics'] diff --git a/lewis_emulators/tdk_lambda_genesys/__init__.py b/lewis_emulators/tdk_lambda_genesys/__init__.py index 50ee3e49..19756a5a 100644 --- a/lewis_emulators/tdk_lambda_genesys/__init__.py +++ b/lewis_emulators/tdk_lambda_genesys/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedTDKLambdaGenesys +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedTDKLambdaGenesys'] diff --git a/lewis_emulators/tpg26x/__init__.py b/lewis_emulators/tpg26x/__init__.py index 6b92f0df..d6e82fca 100644 --- a/lewis_emulators/tpg26x/__init__.py +++ b/lewis_emulators/tpg26x/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedTpg26x +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedTpg26x'] diff --git a/lewis_emulators/volumetric_rig/__init__.py b/lewis_emulators/volumetric_rig/__init__.py index c752a072..a878b971 100644 --- a/lewis_emulators/volumetric_rig/__init__.py +++ b/lewis_emulators/volumetric_rig/__init__.py @@ -1,4 +1,5 @@ from .device import SimulatedVolumetricRig +from ..lewis_versions import LEWIS_LATEST -framework_version = '1.1.1' +framework_version = LEWIS_LATEST __all__ = ['SimulatedVolumetricRig'] From a8961d00237402744ef4078941a3a7fd87d47a17 Mon Sep 17 00:00:00 2001 From: esouthren Date: Tue, 26 Sep 2017 15:51:54 +0100 Subject: [PATCH 0339/1466] Started FSM, attemping alernative MID value thing --- lewis_emulators/HFMAGPSU/device.py | 18 ++++++++++++++++++ .../HFMAGPSU/interfaces/stream_interface.py | 1 - lewis_emulators/HFMAGPSU/states.py | 8 +++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 655c863b..f12d57ad 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -18,7 +18,9 @@ def _initialize_data(self): self._ramp_rate = 0.5 self._limit = 5 self._log_message = "this is the initial log message" + self._error_message = "this is the initial error message" self._constant = 0.00029 + self._zero_target = 0 self.ready = True @@ -88,10 +90,18 @@ def limit(self): def log_message(self): return self._log_message + @property + def error_message(self): + return self._error_message + @property def constant(self): return self._constant + @property + def zero_value(self): + return self._zero_value + @direction.setter def direction(self, d): self._direction = d @@ -136,6 +146,10 @@ def limit(self, lim): def log_message(self, lm): self._log_message = lm + @error_message.setter + def error_message(self, em): + self._error_message = em + @output.setter def output(self, op): self._output = op @@ -143,3 +157,7 @@ def output(self, op): @constant.setter def constant(self, cons): self._constant = cons + + @zero_value.setter + def zero_value(self, zv): + self._zero_value = zv diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 273b33fe..e1f6a37a 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -56,7 +56,6 @@ def _get_ramp_target_value(self): target = self._device.mid_target elif self._device.ramp_target == "MAX": target = self._device.max_target - return target def read_direction(self): diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py index 7a6d36d7..296613c5 100644 --- a/lewis_emulators/HFMAGPSU/states.py +++ b/lewis_emulators/HFMAGPSU/states.py @@ -27,11 +27,17 @@ class DefaultStartedState(State): def in_state(self, dt): device = self._context SMALL = 0.00001 + ''' target = get_target_value(device._ramp_target, + device._zero device._mid_target, device._max_target) + + # This is now set by SNL file? SNL file finds target and sets MID value + # to that, so the device is ramping towards an adapted MID target. + ''' + target = device._mid_target rate = device._ramp_rate - # Starting ramping towards target value device._output = approaches.linear(float(device._output), float(target), float(rate), dt) # If the output equals the target, trigger not_ramping state with _is_paused variable From 0d74f02b933d6c17336e1b5e45f38ef5f1c1f427 Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 1 Nov 2017 11:56:11 +0000 Subject: [PATCH 0340/1466] unit conversion working at emulator level --- lewis_emulators/HFMAGPSU/__init__.pyc | Bin 270 -> 270 bytes lewis_emulators/HFMAGPSU/device.py | 27 ++++++---- lewis_emulators/HFMAGPSU/device.pyc | Bin 6404 -> 9647 bytes .../HFMAGPSU/interfaces/__init__.pyc | Bin 297 -> 297 bytes .../HFMAGPSU/interfaces/stream_interface.py | 46 +++++++++++++----- .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 7790 -> 11476 bytes lewis_emulators/HFMAGPSU/states.py | 29 +++++------ lewis_emulators/HFMAGPSU/states.pyc | Bin 527 -> 2009 bytes 8 files changed, 63 insertions(+), 39 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/__init__.pyc b/lewis_emulators/HFMAGPSU/__init__.pyc index 9b2b9eaafa451a583d95a6e8c49eaa333c69b209..768f8f6707435f86ba197f9d14a930faed108e70 100644 GIT binary patch delta 16 XcmeBU>SJPO{>;mD+-L1Zc6LSpDJBH9 delta 16 XcmeBU>SJPO{>;l2nl*7FJ3Au)CNKm= diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index f12d57ad..40ce48c6 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -9,18 +9,19 @@ def _initialize_data(self): self._is_output_mode_tesla = False self._is_heater_on = True self._is_paused = True - self._output = 0 - self._direction = '0' + self._output = 0.0 + self._direction = '+' self._ramp_target = 'MID' - self._heater_value = 0 + self._heater_value = 0.0 self._max_target = 34.92 - self._mid_target = 10.0 + self._mid_target = 0.0 self._ramp_rate = 0.5 - self._limit = 5 + self._limit = 5.0 self._log_message = "this is the initial log message" self._error_message = "this is the initial error message" self._constant = 0.00029 - self._zero_target = 0 + self._zero_target = 0.0 + self._mid_final_target = 0 self.ready = True @@ -68,11 +69,11 @@ def heater_value(self): @property def max_target(self): - return self._max_target + return float(self._max_target) @property def mid_target(self): - return self._mid_target + return float(self._mid_target) @property def ramp_rate(self): @@ -96,12 +97,16 @@ def error_message(self): @property def constant(self): - return self._constant + return float(self._constant) @property def zero_value(self): return self._zero_value + @property + def mid_final_target(self): + return self._mid_final_target + @direction.setter def direction(self, d): self._direction = d @@ -161,3 +166,7 @@ def constant(self, cons): @zero_value.setter def zero_value(self, zv): self._zero_value = zv + + @mid_final_target.setter + def mid_final_target(self, mft): + self._mid_final_target = mft diff --git a/lewis_emulators/HFMAGPSU/device.pyc b/lewis_emulators/HFMAGPSU/device.pyc index da5b45d1bcfb4bf6725505b206c6b2e2381a8565..470839b51c8c84388e06cc8f434449d130c13e12 100644 GIT binary patch literal 9647 zcmc&(-ESL35MSq#*v@y-H0gKK4~k2nKnr;&RX<2uTBL?*4M>eFPFLS1x#-Sk-L4C* zDjsN65Dy^nzylII@W2D&uR!Ae;D10u;tA$A=d}|jZRtrp+quom+@_QM&g*;;TgYSAx2!0G^qm=89VNTA{ho2n_$$?CJ_hkQn9fAdj0(E1#zR)gWqx zG_imE+Re-F-Ku`nF~b1eeU~nMo+u@tJt;ARO2N#GvpUCWeF>7XBdUhh(rK zv#IvPmY5y3cgxHcEjz+&$+Dx&mMuHR?1*K@nH{z41hZq7on&_0vQx}XSazD(Nz2YK zJ7w8j%uZW&mf0E0?q+tEW#@z{ij8^U&sqtoTw=*?DF50a?nah{TY@VvJ$euE|wg&5;8)W zB@0$Ui#5xVBUVC|+0BxpRzk*^W63cqnG>>FIZj?who9quTP@M0f276;@jwW-Aj|+0 zjwQ&U3M?6di=hv>mBU2Q-l9+%WJ%ddMi6P}Jc=j-F(&*X6XU`!F)<bS5v|t zVac?eeFmOhtswH2*Qnu-0s2!0;dk@Km9@VswQqlKUB1-5|L4UvF?&fLrY>eZ&~=%bP(PHeBWee)BK{KDCb0reIY#J3ru=9(93Wp)$g z2HK5Vrr9!X6#L3GN>els(wz^JmNMh*F6)XyJaOZO8EH$JUP~+AAP=<%NN@Q;qUt7~ zo@2O`cu~`(T(YK2I#_$8yIx4c!$5B2eP(;4eBfK2C*vU!n%@kgdl*E4Ne3r;xNUow z3EX4lJEAf~^|+yp*J%IZzDi;{II@=;(PXPZ!wanx=1G+2-%FDxETL7nTA}}f8-1Io zrE|3#G;Y$0RKwIRH=DY4_12B0YE8GA%{Vc&Gyz}b+!!ad$kSxMTBtrHd#koo*KE$L z`JAs$H}7#`cH0Xc+*hve8LtEoM}eSFe)@Ny6DDvo4io$?pt#`ugMwm_{O}=fLW>kM zpd7JStw7ObUU*E-E7OP#uV2tu<8{VD5X~EiL>Nl^NMa^7mfR1Du3QWSXLiZ0Tv}eE z+;y+vhbqwrNUUKnQE&<)^zB##;l~yMYBEH%0v(p+L26Uf=#j4y*tc1U_KK&<93!a) zIMGE9--o63C2D9fDJ^{|xHfRaV+$(;gc)I(g&-riU+y0jDrS-|cw>YezLgZ;W6k>eUG2ykKRp+jTbI z)0mGlNOwnoRhJ-t0&Wb@2i&tX?M_9=89;qb6;Q79K?UUIw1`^F0D7`Npb@&8sbEFJf>5VUdR9)&%b?wzPSQ^cL(V3d5%+fbNYa@K_wfuZG`YLXI?JjSHsvd z5_N1JR6z;RE#0pX*Bh<>OA(39QKAm&mkyoBjrVuZ;aN!&#Zn)DBUg8xi4jMeeZ_?8dKC$4_l0LW)a(oxS>htR$_tJ0r036$U z@6pc2Cirk3dv*5O2kD6+mC-#X$sT7$&Q)*+IiDiqz&##^zg{II`CxPQou zolnWOVvqB4_!ETtr_9*7l<#84=`>30+&umaP55(W^k?ape0-A@lF;LRemm=1G~F+m zv2!KgwLDTyw3^@6`Zmq?YZrV4XZNN=jq5hNNV#tM`WbJrTOqL$F9PKPnj>hAQN#V4 zN!(P4xyOaIM1?Bhk`ncvgda&%AQD@-#I7i@xk#jGiF7BCHzWd3!WSfrDgln)PSaa; zde7VSMy6iu!N0`W#4ug@I*5VtY`Iu=%B9J{r+;S#pY~m@;K_ks+4D>5)9D*(ElDc) x$+RAaVfvmnEh4heUmQz zH8no{J0cV!a>URP4&;gmD-7TZ=_WpUYxYb>rZRKS&k%Z#*&3x$)E_Q?noYDw9($ zO1|~d6J^XQ#!nS_aSF$tY6DlFX}!%K3owZF>)@*1fMvl2IRX=PoF zAm^TX6F*cb+o-`NoVihOJT|I9oy?XOOp(`VtOPR9$;z-Os^{lBXnQE+4r>;nL;5XC0IeB`_mr zEb82Ep82uCZ!b0o0pscXIpeZqkB@Wh$WI7_k#$H{R%lhm78~zZqjSDrpraQ@ zg|XwAZjhUuQ~pKv)D*`4GI9Tqo1L@%MfOu&7&|K3A9J&F+JBDyQDN+_5;r~T=k#|@ z{1@4y;(X>c;vVN_=j1B8XO1bA+Et_l|L=!M0x-=*6EZUa`-zHT@h&8fTc0MF0Q* delta 16 XcmZ3`IIPDLe#< diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index e1f6a37a..89d29ea8 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -79,12 +79,28 @@ def read_output(self): return "HH.MM.SS OUTPUT: {} {} AT {} VOLTS".format(self._device.output, mode, self._device.heater_value) def write_output_mode(self, output_mode): + constant = self._device.constant + output = self._device.output + max_target = self._device.max_target + mid_target = self._device.max_target + in_tesla = self._device.is_output_mode_tesla if output_mode == "TESLA": - self._device.is_output_mode_tesla = True - self._create_log_message("OUTPUT MODE", output_mode) + if not self._device.is_output_mode_tesla: + self._device.output *= constant + self._device.max_target *= constant + self._device.mid_target *= constant + self._device.is_output_mode_tesla = True + self._create_log_message("OUTPUT MODE", output_mode) elif output_mode == "AMPS": - self._device.is_output_mode_tesla = False - self._create_log_message("OUTPUT MODE", output_mode) + if constant == 0: + self._device.error_message = "------> No field constant has been entered" + else: + if self._device.is_output_mode_tesla: + self._device.output /= constant + self._device.max_target /= constant + self._device.mid_target /= constant + self._device.is_output_mode_tesla = False + self._create_log_message("OUTPUT MODE", output_mode) else: raise AssertionError("Invalid arguments sent") return self._device.log_message @@ -98,7 +114,7 @@ def write_ramp_target(self, ramp_target): return self._device.log_message def read_ramp_rate(self): - return "HH:MM:SS RAMP RATE {} A/SEC".format(self._device.ramp_rate) + return "HH:MM:SS RAMP RATE {:.4} A/SEC".format(self._device.ramp_rate) def write_ramp_rate(self, ramp_rate): self._device.ramp_rate = ramp_rate @@ -131,16 +147,20 @@ def read_pause(self): def write_pause(self, paused): mode = self._get_output_mode_string() target = self._get_ramp_target_value() + rate = self._device.ramp_rate + output = "HOLDING ON PAUSE AT {} {}".format(self._device.output, mode) if paused == "ON": self._device.is_paused = True - output = "HOLDING ON PAUSE AT {} {}".format(self._device.output, mode) self._create_log_message("RAMP STATUS", output) elif paused == "OFF": self._device.is_paused = False - output = "RAMPING FROM {} TO {} {} AT A/SEC".format(self._device.output, - target, mode, - self._device.ramp_rate) - self._create_log_message("RAMP STATUS", output) + ramp_complete = abs(float(self._device.output) - float(target)) < 0.00001 + if ramp_complete: + self._create_log_message("RAMP STATUS", output) + else: + output = "RAMPING FROM {:.6} TO {:.6} {} AT {:.6} A/SEC".format(self._device.output, + target, mode, rate) + self._create_log_message("RAMP STATUS", output) else: raise AssertionError("Invalid arguments sent") @@ -156,7 +176,7 @@ def write_heater_value(self, heater_value): def read_max_target(self): mode = self._get_output_mode_string() - return "HH:MM:SS MAX SETTING: {} {}".format(self._device.max_target, mode) + return "HH:MM:SS MAX SETTING: {:.4} {}".format(self._device.max_target, mode) def write_max_target(self, max_target): self._device.max_target = max_target @@ -165,7 +185,7 @@ def write_max_target(self, max_target): def read_mid_target(self): mode = self._get_output_mode_string() - return "HH:MM:SS MID SETTING: {} {}".format(self._device.mid_target, mode) + return "HH:MM:SS MID SETTING: {:.4} {}".format(self._device.mid_target, mode) def write_mid_target(self, mid_target): self._device.mid_target = mid_target @@ -181,7 +201,7 @@ def write_limit(self, limit): return self._device.log_message def read_constant(self): - return "HH:MM:SS FIELD CONSTANT: {} T/A".format(self._device.constant) + return "HH:MM:SS FIELD CONSTANT: {:.7} T/A".format(self._device.constant) def write_constant(self, constant): self._device.constant = constant diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc index 7eed44639c53b6eb68ee7c2807aaf7cde1cafa21..c320849b0aab29050513fec562005c7710bfac5d 100644 GIT binary patch literal 11476 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 Q^2%rEZd9+Hx8_s-0uJsA9smFU diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py index 296613c5..7204313b 100644 --- a/lewis_emulators/HFMAGPSU/states.py +++ b/lewis_emulators/HFMAGPSU/states.py @@ -26,24 +26,19 @@ def in_state(self, dt): class DefaultStartedState(State): def in_state(self, dt): device = self._context - SMALL = 0.00001 - ''' - target = get_target_value(device._ramp_target, - device._zero - device._mid_target, - device._max_target) - - # This is now set by SNL file? SNL file finds target and sets MID value - # to that, so the device is ramping towards an adapted MID target. - ''' - target = device._mid_target - rate = device._ramp_rate - # Starting ramping towards target value + + rate = float(device._ramp_rate) + target = float(device._mid_target) + constant = float(device._constant) + + # conversion logic + + if device._is_output_mode_tesla: + rate *= constant + device._output = approaches.linear(float(device._output), float(target), float(rate), dt) - # If the output equals the target, trigger not_ramping state with _is_paused variable - ramp_complete = abs(float(device._output) - float(target)) < SMALL - if ramp_complete: - device._is_paused = True + + diff --git a/lewis_emulators/HFMAGPSU/states.pyc b/lewis_emulators/HFMAGPSU/states.pyc index 1c0607bca8614ff31c39dda59b7047ff74bf37f4..9b20b289ae1d663a2aeb6287fcecd6c5a670d4c1 100644 GIT binary patch literal 2009 zcmc&#-D}i95T8wQU$wOrX$AcU4j=mDiy~res~*@=atQVagpllMYQD%$YK1EZ^}q4Y z^T{Wj-{!8@2YnGv)6VS7?CkD*pZk4d?c&>y)0pO;E9-LsQwmDtUr|PsE1D=$;2p{w z%3Yec!n^d9XyQ@gkybQSlyo4uB08OPDCsVwy=!TglKw(ExR&-PS)=jrmb{t%ly9hn z;*Ov(N32_RP1vZa>N1KyXcNOyPrwn_7ch?m&8)>A9jdo!rs&e8;(--91mQ!PIaVlU zxFBY(Jd0b78TTEH4%&6^T`DXMEy6x`fuj1oX6^}@r%8AoWi1_DSbF_h1#F|+(;_5u zDro;nFe(h!O|AdM-7= z>z7A+uTI8q1B1|*-Rd)M$?alT*xJ`Vj^G(sM9#VzC;_MISj_`Hjhc){Mank1F_iEj z=r06JUyzo>D)Psz69_Ux9EL@d>o8$MGkT7)+3 zJFo%olNzFMeWb%j|M;7NmNxy1bfC(%1%8{3sOG;-2U-qgY}4^Y2s{Mlx%|dR9j>fIgL$$hle|<33sA_<(brW2NHZXa;>5`l*}QZ!GSf2SlZz_za}(3!3yMo) zic1npQj7HpDuX}-v8#xIg%QSpSrD@X(9N2>o?V6wq*;Io0Affr AvH$=8 From 301bddcbfadebfdd3eff3e55f1cb5acb188630ab Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 1 Nov 2017 15:05:07 +0000 Subject: [PATCH 0341/1466] Initial emulator commit --- lewis_emulators/skf_mb350_chopper/__init__.py | 5 ++++ lewis_emulators/skf_mb350_chopper/device.py | 25 +++++++++++++++++++ .../skf_mb350_chopper/interfaces/__init__.py | 3 +++ .../interfaces/stream_interface.py | 19 ++++++++++++++ 4 files changed, 52 insertions(+) create mode 100644 lewis_emulators/skf_mb350_chopper/__init__.py create mode 100644 lewis_emulators/skf_mb350_chopper/device.py create mode 100644 lewis_emulators/skf_mb350_chopper/interfaces/__init__.py create mode 100644 lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py diff --git a/lewis_emulators/skf_mb350_chopper/__init__.py b/lewis_emulators/skf_mb350_chopper/__init__.py new file mode 100644 index 00000000..2724ed1e --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedSkfMb350Chopper +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedSkfMb350Chopper'] diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py new file mode 100644 index 00000000..6dfe9eca --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict + +from lewis.core.statemachine import State +from lewis.devices import StateMachineDevice + + +class SimulatedSkfMb350Chopper(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + + def _get_state_handlers(self): + return { + 'init': State(), + } + + def _get_initial_state(self): + return 'init' + + def _get_transition_handlers(self): + return OrderedDict([ + (('init', 'stopped'), lambda: False), + ]) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/__init__.py b/lewis_emulators/skf_mb350_chopper/interfaces/__init__.py new file mode 100644 index 00000000..5137ec38 --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import SkfMb350ChopperStreamInterface + +__all__ = ['SkfMb350ChopperStreamInterface'] diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py new file mode 100644 index 00000000..384f6d58 --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -0,0 +1,19 @@ +from lewis.adapters.stream import StreamInterface, Cmd + + +class SkfMb350ChopperStreamInterface(StreamInterface): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("get_number", "^get_number$"), + } + + in_terminator = "\r" + out_terminator = "\r" + + def handle_error(self, request, error): + print "An error occurred at request " + repr(request) + ": " + repr(error) + return str(error) + + def get_number(self): + return 2.0 From 0a02ab5a983810a0b3cb10a3dfd4098cf9776983 Mon Sep 17 00:00:00 2001 From: esouthren Date: Thu, 2 Nov 2017 16:12:25 +0000 Subject: [PATCH 0342/1466] tesla/amp/gauss conversions working? --- lewis_emulators/HFMAGPSU/states.py | 2 +- lewis_emulators/HFMAGPSU/states.pyc | Bin 2009 -> 2009 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py index 7204313b..115a50d0 100644 --- a/lewis_emulators/HFMAGPSU/states.py +++ b/lewis_emulators/HFMAGPSU/states.py @@ -34,7 +34,7 @@ def in_state(self, dt): # conversion logic if device._is_output_mode_tesla: - rate *= constant + rate = rate * constant device._output = approaches.linear(float(device._output), float(target), float(rate), dt) diff --git a/lewis_emulators/HFMAGPSU/states.pyc b/lewis_emulators/HFMAGPSU/states.pyc index 9b20b289ae1d663a2aeb6287fcecd6c5a670d4c1..1d9dfbc4334b70faf40b36abef8d6bda283d352c 100644 GIT binary patch delta 22 ecmcb~f0Lh$`76MLaZ&?^cHh*Dx$^-yixd=P} delta 22 ecmcb~f0Lh$`7 Date: Thu, 2 Nov 2017 17:17:11 +0000 Subject: [PATCH 0343/1466] Interpret incoming modbus packet, give response as standard ascii --- lewis_emulators/skf_mb350_chopper/device.py | 33 ++++++++- .../skf_mb350_chopper/interfaces/crc16.py | 6 ++ .../interfaces/stream_interface.py | 70 +++++++++++++++++-- 3 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 lewis_emulators/skf_mb350_chopper/interfaces/crc16.py diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 6dfe9eca..cc82bf47 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -5,12 +5,17 @@ class SimulatedSkfMb350Chopper(StateMachineDevice): - def _initialize_data(self): """ Initialize all of the device's attributes. """ + # Chopper is only simulated for this one address. + self.ADDRESS = 1 + + self._started = False + self.phase = 0 + def _get_state_handlers(self): return { 'init': State(), @@ -23,3 +28,29 @@ def _get_transition_handlers(self): return OrderedDict([ (('init', 'stopped'), lambda: False), ]) + + def check_address(self, address): + if address != self.ADDRESS: + raise NotImplementedError("Only address 1 is implemented") + + def set_nominal_frequency(self, address, frequency): + pass + + def set_nominal_phase(self, address, phase): + self.check_address(address) + self.phase = phase + + def set_nominal_phase_window(self, address, phase_window): + pass + + def start(self, address): + self.check_address(address) + self._started = True + + def stop(self, address): + self.check_address(address) + self._started = False + + def get_phase(self, address): + self.check_address(address) + return self.phase diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py b/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py new file mode 100644 index 00000000..4dd8e2c5 --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py @@ -0,0 +1,6 @@ +def crc16_matches(input, expected): + return True + + +def crc16(input): + pass \ No newline at end of file diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 384f6d58..146aa750 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -1,19 +1,77 @@ from lewis.adapters.stream import StreamInterface, Cmd +from lewis.core.logging import has_log + +from crc16 import crc16_matches class SkfMb350ChopperStreamInterface(StreamInterface): - # Commands that we expect via serial during normal operation + # Commands that we expect via serial during normal operation. Match anything! commands = { - Cmd("get_number", "^get_number$"), + Cmd("any_command", "^([\s\S]*)$"), } - in_terminator = "\r" - out_terminator = "\r" + in_terminator = "[terminator]" + out_terminator = in_terminator def handle_error(self, request, error): print "An error occurred at request " + repr(request) + ": " + repr(error) return str(error) - def get_number(self): - return 2.0 + @has_log + def any_command(self, command): + + command_mapping = { + 0x20: self.start, + 0x30: self.stop, + 0x60: self.set_rotational_speed, + 0xC0: self.get_phase_info, + } + + address = ord(command[0]) + if not 0 <= address < 16: + raise ValueError("Address should be in range 0-15") + + # Constant function code. Should always be 0x80 + function_code = ord(command[1]) + if function_code != 0x80: + raise ValueError("Function code should always be 0x80") + + command_number = ord(command[2]) + if command_number not in command_mapping.keys(): + raise ValueError("Command number should be in map") + + command_data = [ord(c) for c in command[3:-2]] + + crc = [ord(c) for c in command[-2:]] + + if not crc16_matches(command[:-2], crc): + raise ValueError("CRC Checksum didn't match") + + return command_mapping[command_number](address, command_data) + + def start(self, address, data): + self._device.start(address) + + def stop(self, address, data): + self._device.stop(address) + + @has_log + def set_rotational_speed(self, address, data): + self.log.info("Setting rotational speed") + self.log.info("Address = {}".format(address)) + self.log.info("Data = {}".format(data)) + + byte = 2**8 + nominal_phase = (byte**3)*data[0] + (byte**2)*data[1] + byte*data[2] + data[3] + self.log.info("Setting nominal phase to {}".format(nominal_phase)) + self._device.set_nominal_phase(address, nominal_phase) + + @has_log + def get_phase_info(self, address, data): + self.log.info("Getting phase info") + self.log.info("Address = {}".format(address)) + + phase = self._device.get_phase(address) + self.log.info("Returning phase as {}".format(phase)) + return phase From efd26c0d288d654c748e0773c7021bcb631b6fe8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Nov 2017 11:31:01 +0000 Subject: [PATCH 0344/1466] Stream interface now returning binary results in same syntax as real device --- .../interfaces/stream_interface.py | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 146aa750..4f1e405c 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -1,7 +1,34 @@ from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log -from crc16 import crc16_matches +from .crc16 import crc16_matches + +BYTE = 2**8 + + +@has_log +def int_to_raw_bytes(integer, length): + """ + Converts an integer to an unsigned, big-endian set of bytes with the specified length + """ + result = r"" + for index in range(length): + result += chr((integer // (BYTE**index)) % BYTE) + + return result[::-1] + + +@has_log +def raw_bytes_to_int(raw_bytes): + """ + Converts an unsigned, big-endian set of bytes to an integer. + """ + multiplier = 1 + result = 0 + for b in reversed(raw_bytes): + result += ord(b) * multiplier + multiplier *= BYTE + return result class SkfMb350ChopperStreamInterface(StreamInterface): @@ -11,7 +38,7 @@ class SkfMb350ChopperStreamInterface(StreamInterface): Cmd("any_command", "^([\s\S]*)$"), } - in_terminator = "[terminator]" + in_terminator = str(chr(0x0)) * 16 out_terminator = in_terminator def handle_error(self, request, error): @@ -41,7 +68,7 @@ def any_command(self, command): if command_number not in command_mapping.keys(): raise ValueError("Command number should be in map") - command_data = [ord(c) for c in command[3:-2]] + command_data = [c for c in command[3:-2]] crc = [ord(c) for c in command[-2:]] @@ -61,9 +88,7 @@ def set_rotational_speed(self, address, data): self.log.info("Setting rotational speed") self.log.info("Address = {}".format(address)) self.log.info("Data = {}".format(data)) - - byte = 2**8 - nominal_phase = (byte**3)*data[0] + (byte**2)*data[1] + byte*data[2] + data[3] + nominal_phase = raw_bytes_to_int(data) self.log.info("Setting nominal phase to {}".format(nominal_phase)) self._device.set_nominal_phase(address, nominal_phase) @@ -74,4 +99,17 @@ def get_phase_info(self, address, data): phase = self._device.get_phase(address) self.log.info("Returning phase as {}".format(phase)) - return phase + + return ResponseBuilder().add_int(address, 1).add_int(0xC0, 1).add_int(0x00, 1).add_int(phase, 4).build() + + +class ResponseBuilder(object): + def __init__(self): + self.response = "" + + def add_int(self, value, length): + self.response += int_to_raw_bytes(value, length) + return self + + def build(self): + return self.response From 8991a7e794a3a9bbe5901ec67ab069693759c2c1 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Nov 2017 12:06:36 +0000 Subject: [PATCH 0345/1466] Support both frequency and phase setting/reading --- lewis_emulators/skf_mb350_chopper/device.py | 10 ++++- .../interfaces/stream_interface.py | 37 ++++++++++++++++--- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index cc82bf47..01fdd152 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -15,6 +15,7 @@ def _initialize_data(self): self._started = False self.phase = 0 + self.frequency = 0 def _get_state_handlers(self): return { @@ -33,8 +34,13 @@ def check_address(self, address): if address != self.ADDRESS: raise NotImplementedError("Only address 1 is implemented") - def set_nominal_frequency(self, address, frequency): - pass + def set_frequency(self, address, frequency): + self.check_address(address) + self.frequency = frequency + + def get_frequency(self, address): + self.check_address(address) + return self.frequency def set_nominal_phase(self, address, phase): self.check_address(address) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 4f1e405c..4b5c1c71 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -5,7 +5,6 @@ BYTE = 2**8 - @has_log def int_to_raw_bytes(integer, length): """ @@ -52,6 +51,7 @@ def any_command(self, command): 0x20: self.start, 0x30: self.stop, 0x60: self.set_rotational_speed, + 0x90: self.set_nominal_phase, 0xC0: self.get_phase_info, } @@ -84,26 +84,51 @@ def stop(self, address, data): self._device.stop(address) @has_log - def set_rotational_speed(self, address, data): - self.log.info("Setting rotational speed") + def set_nominal_phase(self, address, data): + self.log.info("Setting phase") self.log.info("Address = {}".format(address)) self.log.info("Data = {}".format(data)) nominal_phase = raw_bytes_to_int(data) self.log.info("Setting nominal phase to {}".format(nominal_phase)) self._device.set_nominal_phase(address, nominal_phase) + @has_log + def set_rotational_speed(self, address, data): + self.log.info("Setting frequency") + self.log.info("Address = {}".format(address)) + self.log.info("Data = {}".format(data)) + freq = raw_bytes_to_int(data) + self.log.info("Setting frequency to {}".format(freq)) + self._device.set_frequency(address, freq) + @has_log def get_phase_info(self, address, data): self.log.info("Getting phase info") self.log.info("Address = {}".format(address)) + return Responses.phase_information_response_packet(address, self._device) - phase = self._device.get_phase(address) - self.log.info("Returning phase as {}".format(phase)) - return ResponseBuilder().add_int(address, 1).add_int(0xC0, 1).add_int(0x00, 1).add_int(phase, 4).build() +class Responses(object): + """ + Utility class containing common responses (which are shared between many commands). + """ + + @staticmethod + def phase_information_response_packet(address, device): + return ResponseBuilder()\ + .add_int(address, 1)\ + .add_int(0xC0, 1)\ + .add_int(0x00, 1)\ + .add_int(device.get_phase(address), 4)\ + .add_int(device.get_frequency(address), 4) \ + .build() class ResponseBuilder(object): + """ + Response builder which formats the responses as bytes. + """ + def __init__(self): self.response = "" From 31a18808d279f03dd63c6904bd0dfafeef552575 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Nov 2017 15:57:15 +0000 Subject: [PATCH 0346/1466] Byte converters, ordering and phase diagnostics Adds byte converters in a separate module, corrects the message ordering and typing to be as given in the manual, and adds two phase diagnostic outputs (repeatability and % OK) --- lewis_emulators/skf_mb350_chopper/device.py | 10 +++++ .../interfaces/byte_conversions.py | 34 ++++++++++++++ .../interfaces/stream_interface.py | 45 ++++++------------- 3 files changed, 58 insertions(+), 31 deletions(-) create mode 100644 lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 01fdd152..670fc0ea 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -16,6 +16,8 @@ def _initialize_data(self): self._started = False self.phase = 0 self.frequency = 0 + self.phase_percent_ok = 100. + self.phase_repeatability = 100. def _get_state_handlers(self): return { @@ -60,3 +62,11 @@ def stop(self, address): def get_phase(self, address): self.check_address(address) return self.phase + + def get_phase_percent_ok(self, address): + self.check_address(address) + return self.phase_percent_ok + + def get_phase_repeatability(self, address): + self.check_address(address) + return self.phase_repeatability diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py new file mode 100644 index 00000000..7699c524 --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py @@ -0,0 +1,34 @@ +import struct + +BYTE = 2**8 + + +def int_to_raw_bytes(integer, length): + """ + Converts an integer to an unsigned, big-endian set of bytes with the specified length + """ + result = r"" + for index in range(length): + result += chr((integer // (BYTE**index)) % BYTE) + + return result[::-1] + + +def raw_bytes_to_int(raw_bytes): + """ + Converts an unsigned, big-endian set of bytes to an integer. + """ + multiplier = 1 + result = 0 + for b in reversed(raw_bytes): + result += ord(b) * multiplier + multiplier *= BYTE + return result + + +def float_to_raw_bytes(fl): + return "".join(chr(c) for c in bytearray(struct.pack(">f", fl))) + + +def raw_bytes_to_float(raw_bytes): + return struct.unpack('f', raw_bytes)[0] \ No newline at end of file diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 4b5c1c71..efd49994 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -1,34 +1,9 @@ from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log +from byte_conversions import raw_bytes_to_int, int_to_raw_bytes, float_to_raw_bytes from .crc16 import crc16_matches -BYTE = 2**8 - -@has_log -def int_to_raw_bytes(integer, length): - """ - Converts an integer to an unsigned, big-endian set of bytes with the specified length - """ - result = r"" - for index in range(length): - result += chr((integer // (BYTE**index)) % BYTE) - - return result[::-1] - - -@has_log -def raw_bytes_to_int(raw_bytes): - """ - Converts an unsigned, big-endian set of bytes to an integer. - """ - multiplier = 1 - result = 0 - for b in reversed(raw_bytes): - result += ord(b) * multiplier - multiplier *= BYTE - return result - class SkfMb350ChopperStreamInterface(StreamInterface): @@ -37,7 +12,7 @@ class SkfMb350ChopperStreamInterface(StreamInterface): Cmd("any_command", "^([\s\S]*)$"), } - in_terminator = str(chr(0x0)) * 16 + in_terminator = "[terminator]" + str(chr(0x0)) * 16 out_terminator = in_terminator def handle_error(self, request, error): @@ -88,7 +63,7 @@ def set_nominal_phase(self, address, data): self.log.info("Setting phase") self.log.info("Address = {}".format(address)) self.log.info("Data = {}".format(data)) - nominal_phase = raw_bytes_to_int(data) + nominal_phase = raw_bytes_to_int(data) / 1000. self.log.info("Setting nominal phase to {}".format(nominal_phase)) self._device.set_nominal_phase(address, nominal_phase) @@ -105,7 +80,9 @@ def set_rotational_speed(self, address, data): def get_phase_info(self, address, data): self.log.info("Getting phase info") self.log.info("Address = {}".format(address)) - return Responses.phase_information_response_packet(address, self._device) + response = Responses.phase_information_response_packet(address, self._device) + self.log.info("Response is: {}".format(response)) + return response class Responses(object): @@ -119,8 +96,10 @@ def phase_information_response_packet(address, device): .add_int(address, 1)\ .add_int(0xC0, 1)\ .add_int(0x00, 1)\ - .add_int(device.get_phase(address), 4)\ - .add_int(device.get_frequency(address), 4) \ + .add_int(device.get_frequency(address), 2) \ + .add_float(device.get_phase(address)) \ + .add_float(device.get_phase_repeatability(address)) \ + .add_float(device.get_phase_percent_ok(address)) \ .build() @@ -136,5 +115,9 @@ def add_int(self, value, length): self.response += int_to_raw_bytes(value, length) return self + def add_float(self, value): + self.response += float_to_raw_bytes(value) + return self + def build(self): return self.response From 7b7f523df4db830d0e270ef0dbf42f16d6f06f8c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Nov 2017 16:42:02 +0000 Subject: [PATCH 0347/1466] Basic implementation of interlock bit-field --- lewis_emulators/skf_mb350_chopper/device.py | 25 +++++++++ .../interfaces/response_utilities.py | 52 +++++++++++++++++++ .../interfaces/stream_interface.py | 41 +-------------- 3 files changed, 79 insertions(+), 39 deletions(-) create mode 100644 lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 670fc0ea..b754fb65 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -19,6 +19,31 @@ def _initialize_data(self): self.phase_percent_ok = 100. self.phase_repeatability = 100. + self.interlocks = OrderedDict([ + ("DSP_WD_FAIL", True), + ("OSCILLATOR_FAIL", False), + ("POSITION_SHUTDOWN", False), + ("EMERGENCY_STOP", False), + ("UPS_FAIL", False), + ("EXTERNAL_FAULT ", False), + ("CC_WD_FAIL", False), + ("OVERSPEED_TRIP", False), + ("VACUUM_FAIL", False), + ("MOTOR_OVER_TEMP", False), + ("REFERENCE_SIGNAL_LOSS ", False), + ("SPEED_SENSOR_LOSS", False), + ("COOLING_LOSS", False), + ("DSP_SUMMARY_SHUTDOWN", False), + ("CC_SHUTDOWN_REQ", False), + ("TEST_MODE", False), + ]) + + def set_interlock_state(self, item, value): + self.interlocks[item] = value + + def get_interlocks(self): + return self.interlocks + def _get_state_handlers(self): return { 'init': State(), diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py new file mode 100644 index 00000000..ead05164 --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -0,0 +1,52 @@ +from byte_conversions import int_to_raw_bytes, float_to_raw_bytes + + +def build_interlock_status(device): + interlocks = device.get_interlocks() + + multiplier = 1 + result = 0 + + for key in interlocks.keys(): + result += multiplier if interlocks[key] else 0 + multiplier *= 2 + + return result + + +class Responses(object): + """ + Utility class containing common responses (which may be shared between several commands). + """ + @staticmethod + def phase_information_response_packet(address, device): + return ResponseBuilder()\ + .add_int(address, 1)\ + .add_int(0xC0, 1)\ + .add_int(0x00, 1)\ + .add_int(build_interlock_status(device), 2) \ + .add_int(device.get_frequency(address), 2) \ + .add_float(device.get_phase(address)) \ + .add_float(device.get_phase_repeatability(address)) \ + .add_float(device.get_phase_percent_ok(address)) \ + .build() + + +class ResponseBuilder(object): + """ + Response builder which formats the responses as bytes. + """ + + def __init__(self): + self.response = "" + + def add_int(self, value, length): + self.response += int_to_raw_bytes(value, length) + return self + + def add_float(self, value): + self.response += float_to_raw_bytes(value) + return self + + def build(self): + return self.response \ No newline at end of file diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index efd49994..95a131ff 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -1,7 +1,8 @@ from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log -from byte_conversions import raw_bytes_to_int, int_to_raw_bytes, float_to_raw_bytes +from byte_conversions import raw_bytes_to_int +from response_utilities import Responses from .crc16 import crc16_matches @@ -83,41 +84,3 @@ def get_phase_info(self, address, data): response = Responses.phase_information_response_packet(address, self._device) self.log.info("Response is: {}".format(response)) return response - - -class Responses(object): - """ - Utility class containing common responses (which are shared between many commands). - """ - - @staticmethod - def phase_information_response_packet(address, device): - return ResponseBuilder()\ - .add_int(address, 1)\ - .add_int(0xC0, 1)\ - .add_int(0x00, 1)\ - .add_int(device.get_frequency(address), 2) \ - .add_float(device.get_phase(address)) \ - .add_float(device.get_phase_repeatability(address)) \ - .add_float(device.get_phase_percent_ok(address)) \ - .build() - - -class ResponseBuilder(object): - """ - Response builder which formats the responses as bytes. - """ - - def __init__(self): - self.response = "" - - def add_int(self, value, length): - self.response += int_to_raw_bytes(value, length) - return self - - def add_float(self, value): - self.response += float_to_raw_bytes(value) - return self - - def build(self): - return self.response From 274782fb8de1f0b1debaed1ba86ed3ab5442dcf9 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Nov 2017 16:49:09 +0000 Subject: [PATCH 0348/1466] Device is little-endian not big-endian --- .../skf_mb350_chopper/interfaces/byte_conversions.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py index 7699c524..fe4240ab 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py @@ -5,22 +5,22 @@ def int_to_raw_bytes(integer, length): """ - Converts an integer to an unsigned, big-endian set of bytes with the specified length + Converts an integer to an unsigned, little-endian set of bytes with the specified length """ result = r"" for index in range(length): result += chr((integer // (BYTE**index)) % BYTE) - return result[::-1] + return result def raw_bytes_to_int(raw_bytes): """ - Converts an unsigned, big-endian set of bytes to an integer. + Converts an unsigned, little-endian set of bytes to an integer. """ multiplier = 1 result = 0 - for b in reversed(raw_bytes): + for b in raw_bytes: result += ord(b) * multiplier multiplier *= BYTE return result From 2ee698d3a552b69e199b024496ad6936ec9c63c8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 3 Nov 2017 17:15:45 +0000 Subject: [PATCH 0349/1466] No interlock errors by default --- lewis_emulators/skf_mb350_chopper/device.py | 2 +- .../skf_mb350_chopper/interfaces/response_utilities.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index b754fb65..b52fda2b 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -20,7 +20,7 @@ def _initialize_data(self): self.phase_repeatability = 100. self.interlocks = OrderedDict([ - ("DSP_WD_FAIL", True), + ("DSP_WD_FAIL", False), ("OSCILLATOR_FAIL", False), ("POSITION_SHUTDOWN", False), ("EMERGENCY_STOP", False), diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index ead05164..42f0f726 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -4,12 +4,11 @@ def build_interlock_status(device): interlocks = device.get_interlocks() - multiplier = 1 + bit = 1 result = 0 - - for key in interlocks.keys(): - result += multiplier if interlocks[key] else 0 - multiplier *= 2 + for key in reversed(interlocks.keys()): + result += bit if interlocks[key] else 0 + bit *= 2 return result From ff6656804be1cf84558232410fe983f40027d9ff Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 6 Nov 2017 14:47:02 +0000 Subject: [PATCH 0350/1466] changes --- .../skf_mb350_chopper/interfaces/byte_conversions.py | 11 ++++++----- .../interfaces/response_utilities.py | 10 +++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py index fe4240ab..a6652431 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py @@ -3,20 +3,21 @@ BYTE = 2**8 -def int_to_raw_bytes(integer, length): +def int_to_raw_bytes(integer, length, low_byte_first): """ - Converts an integer to an unsigned, little-endian set of bytes with the specified length + Converts an integer to an unsigned set of bytes with the specified length (represented as a string). """ result = r"" + for index in range(length): result += chr((integer // (BYTE**index)) % BYTE) - return result + return result if low_byte_first else result[::-1] def raw_bytes_to_int(raw_bytes): """ - Converts an unsigned, little-endian set of bytes to an integer. + Converts an unsigned set of bytes to an integer. """ multiplier = 1 result = 0 @@ -31,4 +32,4 @@ def float_to_raw_bytes(fl): def raw_bytes_to_float(raw_bytes): - return struct.unpack('f', raw_bytes)[0] \ No newline at end of file + return struct.unpack('f', raw_bytes)[0] diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index 42f0f726..a5e0d8e5 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -6,8 +6,8 @@ def build_interlock_status(device): bit = 1 result = 0 - for key in reversed(interlocks.keys()): - result += bit if interlocks[key] else 0 + for ilk in interlocks.values(): + result += bit if ilk else 0 bit *= 2 return result @@ -23,7 +23,7 @@ def phase_information_response_packet(address, device): .add_int(address, 1)\ .add_int(0xC0, 1)\ .add_int(0x00, 1)\ - .add_int(build_interlock_status(device), 2) \ + .add_int(build_interlock_status(device), 2, low_byte_first=False) \ .add_int(device.get_frequency(address), 2) \ .add_float(device.get_phase(address)) \ .add_float(device.get_phase_repeatability(address)) \ @@ -39,8 +39,8 @@ class ResponseBuilder(object): def __init__(self): self.response = "" - def add_int(self, value, length): - self.response += int_to_raw_bytes(value, length) + def add_int(self, value, length, low_byte_first=True): + self.response += int_to_raw_bytes(value, length, low_byte_first) return self def add_float(self, value): From 295e076f5d414fe19801b8dad3042a100d2c747d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 6 Nov 2017 15:18:11 +0000 Subject: [PATCH 0351/1466] Remove references to address. Not needed. --- lewis_emulators/skf_mb350_chopper/device.py | 35 ++++++------------- .../interfaces/byte_conversions.py | 2 ++ .../interfaces/response_utilities.py | 8 ++--- .../interfaces/stream_interface.py | 14 +++----- 4 files changed, 21 insertions(+), 38 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index b52fda2b..1cec7cb2 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -10,9 +10,6 @@ def _initialize_data(self): Initialize all of the device's attributes. """ - # Chopper is only simulated for this one address. - self.ADDRESS = 1 - self._started = False self.phase = 0 self.frequency = 0 @@ -25,12 +22,12 @@ def _initialize_data(self): ("POSITION_SHUTDOWN", False), ("EMERGENCY_STOP", False), ("UPS_FAIL", False), - ("EXTERNAL_FAULT ", False), + ("EXTERNAL_FAULT", False), ("CC_WD_FAIL", False), ("OVERSPEED_TRIP", False), ("VACUUM_FAIL", False), ("MOTOR_OVER_TEMP", False), - ("REFERENCE_SIGNAL_LOSS ", False), + ("REFERENCE_SIGNAL_LOSS", False), ("SPEED_SENSOR_LOSS", False), ("COOLING_LOSS", False), ("DSP_SUMMARY_SHUTDOWN", False), @@ -57,41 +54,29 @@ def _get_transition_handlers(self): (('init', 'stopped'), lambda: False), ]) - def check_address(self, address): - if address != self.ADDRESS: - raise NotImplementedError("Only address 1 is implemented") - - def set_frequency(self, address, frequency): - self.check_address(address) + def set_frequency(self, frequency): self.frequency = frequency - def get_frequency(self, address): - self.check_address(address) + def get_frequency(self): return self.frequency - def set_nominal_phase(self, address, phase): - self.check_address(address) + def set_nominal_phase(self, phase): self.phase = phase def set_nominal_phase_window(self, address, phase_window): pass - def start(self, address): - self.check_address(address) + def start(self): self._started = True - def stop(self, address): - self.check_address(address) + def stop(self): self._started = False - def get_phase(self, address): - self.check_address(address) + def get_phase(self): return self.phase - def get_phase_percent_ok(self, address): - self.check_address(address) + def get_phase_percent_ok(self): return self.phase_percent_ok - def get_phase_repeatability(self, address): - self.check_address(address) + def get_phase_repeatability(self): return self.phase_repeatability diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py index a6652431..c69de054 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py @@ -6,6 +6,8 @@ def int_to_raw_bytes(integer, length, low_byte_first): """ Converts an integer to an unsigned set of bytes with the specified length (represented as a string). + + If low byte first is True, the least significant byte comes first, otherwise the most significant byte comes first. """ result = r"" diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index a5e0d8e5..291d4cae 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -24,10 +24,10 @@ def phase_information_response_packet(address, device): .add_int(0xC0, 1)\ .add_int(0x00, 1)\ .add_int(build_interlock_status(device), 2, low_byte_first=False) \ - .add_int(device.get_frequency(address), 2) \ - .add_float(device.get_phase(address)) \ - .add_float(device.get_phase_repeatability(address)) \ - .add_float(device.get_phase_percent_ok(address)) \ + .add_int(device.get_frequency(), 2) \ + .add_float(device.get_phase()) \ + .add_float(device.get_phase_repeatability()) \ + .add_float(device.get_phase_percent_ok()) \ .build() diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 95a131ff..55ed2752 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -36,8 +36,7 @@ def any_command(self, command): raise ValueError("Address should be in range 0-15") # Constant function code. Should always be 0x80 - function_code = ord(command[1]) - if function_code != 0x80: + if ord(command[1]) != 0x80: raise ValueError("Function code should always be 0x80") command_number = ord(command[2]) @@ -54,33 +53,30 @@ def any_command(self, command): return command_mapping[command_number](address, command_data) def start(self, address, data): - self._device.start(address) + self._device.start() def stop(self, address, data): - self._device.stop(address) + self._device.stop() @has_log def set_nominal_phase(self, address, data): self.log.info("Setting phase") - self.log.info("Address = {}".format(address)) self.log.info("Data = {}".format(data)) nominal_phase = raw_bytes_to_int(data) / 1000. self.log.info("Setting nominal phase to {}".format(nominal_phase)) - self._device.set_nominal_phase(address, nominal_phase) + self._device.set_nominal_phase(nominal_phase) @has_log def set_rotational_speed(self, address, data): self.log.info("Setting frequency") - self.log.info("Address = {}".format(address)) self.log.info("Data = {}".format(data)) freq = raw_bytes_to_int(data) self.log.info("Setting frequency to {}".format(freq)) - self._device.set_frequency(address, freq) + self._device.set_frequency(freq) @has_log def get_phase_info(self, address, data): self.log.info("Getting phase info") - self.log.info("Address = {}".format(address)) response = Responses.phase_information_response_packet(address, self._device) self.log.info("Response is: {}".format(response)) return response From 84e929236592278d6cc79a253140cd63c08d3332 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 6 Nov 2017 16:15:00 +0000 Subject: [PATCH 0352/1466] Skeletal implementation of status bitfield --- lewis_emulators/skf_mb350_chopper/device.py | 24 ++++++++++ .../interfaces/byte_conversions.py | 8 ++++ .../interfaces/response_utilities.py | 48 ++++++++++++------- .../interfaces/stream_interface.py | 4 +- 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 1cec7cb2..d8c6021d 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -72,6 +72,30 @@ def start(self): def stop(self): self._started = False + def is_controller_ok(self): + return True # TODO + + def is_up_to_speed(self): + return self.frequency > 10 # TODO + + def is_able_to_run(self): + return True # TODO + + def is_shutting_down(self): + return False # TODO + + def is_levitation_complete(self): + return True # TODO + + def is_phase_locked(self): + return True # TODO + + def get_motor_direction(self): + return 1 # TODO + + def is_avc_on(self): + return True # TODO + def get_phase(self): return self.phase diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py index c69de054..17548428 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py @@ -8,6 +8,11 @@ def int_to_raw_bytes(integer, length, low_byte_first): Converts an integer to an unsigned set of bytes with the specified length (represented as a string). If low byte first is True, the least significant byte comes first, otherwise the most significant byte comes first. + + :param integer: (int) The integer to convert + :param length: (int) The length of the result + :param low_byte_first: (bool) Whether to put the least significant byte first + :return (str) A string representation of the bytes. """ result = r"" @@ -20,6 +25,9 @@ def int_to_raw_bytes(integer, length, low_byte_first): def raw_bytes_to_int(raw_bytes): """ Converts an unsigned set of bytes to an integer. + + :param raw_bytes: (str) A string representation of the raw bytes. + :return (int): The integer represented by the raw bytes passed in. """ multiplier = 1 result = 0 diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index 291d4cae..e2a72be5 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -13,22 +13,38 @@ def build_interlock_status(device): return result -class Responses(object): - """ - Utility class containing common responses (which may be shared between several commands). - """ - @staticmethod - def phase_information_response_packet(address, device): - return ResponseBuilder()\ - .add_int(address, 1)\ - .add_int(0xC0, 1)\ - .add_int(0x00, 1)\ - .add_int(build_interlock_status(device), 2, low_byte_first=False) \ - .add_int(device.get_frequency(), 2) \ - .add_float(device.get_phase()) \ - .add_float(device.get_phase_repeatability()) \ - .add_float(device.get_phase_percent_ok()) \ - .build() +def build_device_status(device): + status_bits = [ + device.is_controller_ok(), + device.is_up_to_speed(), + device.is_able_to_run(), + device.is_shutting_down(), + device.is_levitation_complete(), + device.is_phase_locked(), + device.get_motor_direction() > 0, + device.is_avc_on() + ] + + bit = 1 + result = 0 + for stat in status_bits: + result += bit if stat else 0 + bit *= 2 + return result + + +def phase_information_response_packet(address, device): + return ResponseBuilder() \ + .add_int(address, 1) \ + .add_int(0xC0, 1) \ + .add_int(0x00, 1) \ + .add_int(build_device_status(device), 1) \ + .add_int(build_interlock_status(device), 2, low_byte_first=False) \ + .add_int(device.get_frequency(), 2) \ + .add_float(device.get_phase()) \ + .add_float(device.get_phase_repeatability()) \ + .add_float(device.get_phase_percent_ok()) \ + .build() class ResponseBuilder(object): diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 55ed2752..7f10197b 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -2,7 +2,7 @@ from lewis.core.logging import has_log from byte_conversions import raw_bytes_to_int -from response_utilities import Responses +from response_utilities import phase_information_response_packet from .crc16 import crc16_matches @@ -77,6 +77,6 @@ def set_rotational_speed(self, address, data): @has_log def get_phase_info(self, address, data): self.log.info("Getting phase info") - response = Responses.phase_information_response_packet(address, self._device) + response = phase_information_response_packet(address, self._device) self.log.info("Response is: {}".format(response)) return response From c7e289a714a31c6bc7aad2df91872fb2c72920ac Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 6 Nov 2017 16:37:32 +0000 Subject: [PATCH 0353/1466] Allow starting/stopping chopper --- lewis_emulators/skf_mb350_chopper/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index d8c6021d..7e85cf1b 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -76,7 +76,7 @@ def is_controller_ok(self): return True # TODO def is_up_to_speed(self): - return self.frequency > 10 # TODO + return self._started # TODO def is_able_to_run(self): return True # TODO From b6ae915fe94b6f5662b3795777d5911dc4ea660c Mon Sep 17 00:00:00 2001 From: esouthren Date: Tue, 7 Nov 2017 09:59:37 +0000 Subject: [PATCH 0354/1466] changed line terminators to keep hardware happy --- lewis_emulators/HFMAGPSU/device.py | 2 +- lewis_emulators/HFMAGPSU/device.pyc | Bin 9647 -> 9647 bytes .../HFMAGPSU/interfaces/stream_interface.py | 9 ++++----- .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 11476 -> 11480 bytes 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 40ce48c6..6085603e 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -101,7 +101,7 @@ def constant(self): @property def zero_value(self): - return self._zero_value + return self._zero_target @property def mid_final_target(self): diff --git a/lewis_emulators/HFMAGPSU/device.pyc b/lewis_emulators/HFMAGPSU/device.pyc index 470839b51c8c84388e06cc8f434449d130c13e12..0e123b990cccc2947fc20861daa480e4c6cb3a87 100644 GIT binary patch delta 80 zcmZ4Qz22LH`7o=xH32e~O};2;Ir+I#J)`O5bmi%6Ah96R%`7Ub83CIO8Pfm& diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 89d29ea8..1fe54bbf 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -5,9 +5,8 @@ class HFMAGPSUStreamInterface(StreamAdapter): - # terminators set to ascii ETX in_terminator = "\r\n" - out_terminator = "\r\n" + out_terminator = chr(19) commands = { CmdBuilder("read_direction").escape("GET SIGN").build(), @@ -24,7 +23,7 @@ class HFMAGPSUStreamInterface(StreamAdapter): CmdBuilder("read_constant").escape("GET TPA").build(), CmdBuilder("write_direction").escape("D ").arg("-|0|\+").build(), - CmdBuilder("write_output_mode").escape("T ").arg("AMPS|TESLA").build(), + CmdBuilder("write_output_mode").escape("T ").arg("OFF|ON").build(), CmdBuilder("write_ramp_target").escape("RAMP ").arg("ZERO|MID|MAX").build(), CmdBuilder("write_heater_status").escape("H ").arg("OFF|ON").build(), CmdBuilder("write_pause").escape("P ").arg("OFF|ON").build(), @@ -84,14 +83,14 @@ def write_output_mode(self, output_mode): max_target = self._device.max_target mid_target = self._device.max_target in_tesla = self._device.is_output_mode_tesla - if output_mode == "TESLA": + if output_mode == "ON": if not self._device.is_output_mode_tesla: self._device.output *= constant self._device.max_target *= constant self._device.mid_target *= constant self._device.is_output_mode_tesla = True self._create_log_message("OUTPUT MODE", output_mode) - elif output_mode == "AMPS": + elif output_mode == "OFF": if constant == 0: self._device.error_message = "------> No field constant has been entered" else: diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc index c320849b0aab29050513fec562005c7710bfac5d..da6783af6a32a094891aeb3b7841e6beb8d9cb40 100644 GIT binary patch delta 1499 zcmZ{i$!}9v6vof-@Gy=YCvoC9cH${^Z~~=_X_;a>#KsI6>NuiUswi+$F$N?DHdH(z zx?sTu?t*r~qN-G-LZYC{q6k5&4hs-SNPuY7O}k_T68`}A9&v+Sl`eeV?|$ER$Kz-F z{@mwtzX|%E*x0!2dv0HH1Iz+65J*)Q%({UPE+8~KFzW?!X+CNJxfzzh1(jF2$LLTO1Ecj_HR4?Fcv9@^?=T}~rnCUiUX zsL^|zdd%o?1|LwbGtg%O2@^;z25f%Jlu4vbBD0v_K)(rOP2l|!9LSl#!2bkRm_VLE zbpdpD?04-PH1?3ge&?<f9=EF(C`DThp*Hhvju1a zLjY(aLl9_~p%v&Oh7i!l3~fN07}|k0GjsrLVF&|lW#|Ojroe_q7tjbx5ugG?H&Brw z3RGg~0UBk90c~f91C25C0+kv1fGP|LpdAcJpejQOXeUD&Xct2UXt#!ZqaSDwOIe_C zh8)zU^AiBjbg2^b9|^0EYA8RbjEGO-6)_NcfMfD?sDQXju4zAw$$7ooTv?b!HHci4)!9;)kv~I45r9M#N9)ptupak4>e| zVpY7#L{%;2&wG2-5@%E@paEPQ9fZ%Y1MM6UaqC4>!te4 zbgd|M##N9f zX$$eDSjg7JUzyu@o9}lnT@^3;@8cc0ko6(nlecoq5P#*V){49Mhf0lH$q#!Cowi@V-u|!CsAjTnyWJtFiKM_D+MF5GSmZg*aI;Wh?_vd9!S{3Srh2A2f9pP%X--rTJ&H%CX3MLULV0pj^jfptWlL!@#p3GBv|Oo6z(bst9Tm1$2q zV!zz&J%czXulNFp!}2eG3*v~p*BC=A$ftok;)LAR{3S+9eR?;qUTv;2J6B0f%^WC{ z`qfzc-SPnQ;$rJ1JS2CuWe`t^)8Q%cU&bkZ4PL_~v60&=p7|{CC|fUn3vFOk`HNT- z$y`L$$Eh_}Os(^kSdNqeRrQRK^AHS1Ju@Z*HmBJ@4sph20mC?#vxilso#7_?u zbvUidur4FIOz6_Bi(Z@0bL@-ds!I)ypISOrRlBVuZlI9)WCHQBTu8YQuZRoTqvD_R z2L3MBGD^56FJx7N{}gMPig?j^6*ssIPr7d74Y80qB&^(Zyrs0NGp|+_k3-+OS{2;k t-Zk~aRZDe$^1464gmUhxhp!XRhfUbsB_8z*$ne$$bUG}w Date: Tue, 7 Nov 2017 11:46:49 +0000 Subject: [PATCH 0355/1466] Add support for getting rotator angle Also add more documentation --- lewis_emulators/skf_mb350_chopper/device.py | 5 ++ .../interfaces/response_utilities.py | 90 +++++++++++++++++-- .../interfaces/stream_interface.py | 10 ++- 3 files changed, 98 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 7e85cf1b..b7a22cf1 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -35,6 +35,8 @@ def _initialize_data(self): ("TEST_MODE", False), ]) + self.rotator_angle = 90 + def set_interlock_state(self, item, value): self.interlocks[item] = value @@ -104,3 +106,6 @@ def get_phase_percent_ok(self): def get_phase_repeatability(self): return self.phase_repeatability + + def get_rotator_angle(self): + return self.rotator_angle diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index e2a72be5..83965f50 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -2,6 +2,11 @@ def build_interlock_status(device): + """ + Builds an integer representation of the interlock bit-field. + :param device: the lewis device + :return: int representation of the bit field + """ interlocks = device.get_interlocks() bit = 1 @@ -14,6 +19,11 @@ def build_interlock_status(device): def build_device_status(device): + """ + Builds an integer representation of the device status bit-field. + :param device: the lewis device + :return: int representation of the bit field + """ status_bits = [ device.is_controller_ok(), device.is_up_to_speed(), @@ -34,19 +44,45 @@ def build_device_status(device): def phase_information_response_packet(address, device): + """ + Returns the response to the "get_phase_information" command. + + Response structure is: + 8 bytes common header (see ResponseBuilder.add_common_header) + 4 bytes (IEEE single-precision float): The current phase + 4 bytes (IEEE single-precision float): Phase repeatability + 4 bytes (IEEE single-precision float): Phase percent OK + + :param address: The address of this device + :param device: The lewis device + :return: The response + """ return ResponseBuilder() \ - .add_int(address, 1) \ - .add_int(0xC0, 1) \ - .add_int(0x00, 1) \ - .add_int(build_device_status(device), 1) \ - .add_int(build_interlock_status(device), 2, low_byte_first=False) \ - .add_int(device.get_frequency(), 2) \ + .add_common_header(address, 0xC0, device) \ .add_float(device.get_phase()) \ .add_float(device.get_phase_repeatability()) \ .add_float(device.get_phase_percent_ok()) \ .build() +def rotator_angle_response_packet(address, device): + """ + Returns the response to the "get_rotator_angle" command. + + Response structure is: + 8 bytes common header (see ResponseBuilder.add_common_header) + 4 bytes (IEEE single-precision float): The current rotator angle + + :param address: The address of this device + :param device: The lewis device + :return: The response + """ + return ResponseBuilder() \ + .add_common_header(address, 0x81, device) \ + .add_float(device.get_rotator_angle()) \ + .build() + + class ResponseBuilder(object): """ Response builder which formats the responses as bytes. @@ -56,12 +92,54 @@ def __init__(self): self.response = "" def add_int(self, value, length, low_byte_first=True): + """ + Adds an integer to the builder + :param value: The integer to add + :param length: How many bytes should the integer be represented as + :param low_byte_first: If true (default), put the least significant byte first. + If false, put the most significant byte first. + :return: The builder + """ self.response += int_to_raw_bytes(value, length, low_byte_first) return self def add_float(self, value): + """ + Adds an float to the builder (4 bytes, IEEE single-precision) + :param value: The float to add + :param length: How many bytes should the integer be represented as + :return: The builder + """ self.response += float_to_raw_bytes(value) return self + def add_common_header(self, address, command_number, device): + """ + Adds the common header. + + The header bytes are as follows: + 1 byte (unsigned int): Device address + 1 byte (unsigned int): Command number + 1 byte (unsigned int): Error status (always zero in the emulator) + 1 byte (bit field): Device status bit-field + 2 bytes (bit field): Device interlock status bit-field + 2 bytes (unsigned int): Current frequency of the chopper in rpm. + + :param address: The address of this device + :param command_number: The command number that this is a reply to + :param device: The lewis device + :return: (ResponseBuilder) the builder with the common header bytes. + """ + return self.add_int(address, 1) \ + .add_int(command_number, 1) \ + .add_int(0x00, 1) \ + .add_int(build_device_status(device), 1) \ + .add_int(build_interlock_status(device), 2, low_byte_first=False) \ + .add_int(device.get_frequency(), 2) + def build(self): + """ + Gets the response from the builder + :return: the response + """ return self.response \ No newline at end of file diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 7f10197b..ac50dfb1 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -2,7 +2,7 @@ from lewis.core.logging import has_log from byte_conversions import raw_bytes_to_int -from response_utilities import phase_information_response_packet +from response_utilities import phase_information_response_packet, rotator_angle_response_packet from .crc16 import crc16_matches @@ -27,6 +27,7 @@ def any_command(self, command): 0x20: self.start, 0x30: self.stop, 0x60: self.set_rotational_speed, + 0x81: self.get_rotator_angle, 0x90: self.set_nominal_phase, 0xC0: self.get_phase_info, } @@ -80,3 +81,10 @@ def get_phase_info(self, address, data): response = phase_information_response_packet(address, self._device) self.log.info("Response is: {}".format(response)) return response + + @has_log + def get_rotator_angle(self, address, data): + self.log.info("Getting rotator angle") + response = rotator_angle_response_packet(address, self._device) + self.log.info("Response is: {}".format(response)) + return response From 80ce4f93b1151d110f9f518900eb878374067d84 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 7 Nov 2017 15:21:24 +0000 Subject: [PATCH 0356/1466] Gate width setting --- lewis_emulators/skf_mb350_chopper/device.py | 6 ++--- .../interfaces/response_utilities.py | 22 +++++++++++++++++-- .../interfaces/stream_interface.py | 22 ++++++++++++++++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index b7a22cf1..728637ca 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -65,9 +65,6 @@ def get_frequency(self): def set_nominal_phase(self, phase): self.phase = phase - def set_nominal_phase_window(self, address, phase_window): - pass - def start(self): self._started = True @@ -107,5 +104,8 @@ def get_phase_percent_ok(self): def get_phase_repeatability(self): return self.phase_repeatability + def set_phase_repeatability(self, value): + self.phase_repeatability = value + def get_rotator_angle(self): return self.rotator_angle diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index 83965f50..ffc520c7 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -71,7 +71,7 @@ def rotator_angle_response_packet(address, device): Response structure is: 8 bytes common header (see ResponseBuilder.add_common_header) - 4 bytes (IEEE single-precision float): The current rotator angle + 4 bytes (unsigned int): The current rotator angle :param address: The address of this device :param device: The lewis device @@ -79,7 +79,25 @@ def rotator_angle_response_packet(address, device): """ return ResponseBuilder() \ .add_common_header(address, 0x81, device) \ - .add_float(device.get_rotator_angle()) \ + .add_int(int(device.get_rotator_angle()), 4) \ + .build() + + +def phase_time_response_packet(address, device): + """ + Returns the response to the "get_phase_information" command. + + Response structure is: + 8 bytes common header (see ResponseBuilder.add_common_header) + 4 bytes (unsigned int): The current rotator angle + + :param address: The address of this device + :param device: The lewis device + :return: The response + """ + return ResponseBuilder() \ + .add_common_header(address, 0x81, device) \ + .add_int(int(device.get_phase()/100), 4) \ .build() diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index ac50dfb1..86cf56aa 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -2,7 +2,8 @@ from lewis.core.logging import has_log from byte_conversions import raw_bytes_to_int -from response_utilities import phase_information_response_packet, rotator_angle_response_packet +from response_utilities import phase_information_response_packet, rotator_angle_response_packet, \ + phase_time_response_packet from .crc16 import crc16_matches @@ -28,6 +29,8 @@ def any_command(self, command): 0x30: self.stop, 0x60: self.set_rotational_speed, 0x81: self.get_rotator_angle, + 0x85: self.get_phase_delay, + 0x8E: self.set_gate_width, 0x90: self.set_nominal_phase, 0xC0: self.get_phase_info, } @@ -53,9 +56,11 @@ def any_command(self, command): return command_mapping[command_number](address, command_data) + @has_log def start(self, address, data): self._device.start() + @has_log def stop(self, address, data): self._device.stop() @@ -67,6 +72,14 @@ def set_nominal_phase(self, address, data): self.log.info("Setting nominal phase to {}".format(nominal_phase)) self._device.set_nominal_phase(nominal_phase) + @has_log + def set_gate_width(self, address, data): + self.log.info("Setting gate width") + self.log.info("Data = {}".format(data)) + width = raw_bytes_to_int(data) + self.log.info("Setting gate width to {}".format(width)) + self._device.set_phase_repeatability(width / 10.) + @has_log def set_rotational_speed(self, address, data): self.log.info("Setting frequency") @@ -88,3 +101,10 @@ def get_rotator_angle(self, address, data): response = rotator_angle_response_packet(address, self._device) self.log.info("Response is: {}".format(response)) return response + + @has_log + def get_phase_delay(self, address, data): + self.log.info("Getting phase time") + response = phase_time_response_packet(address, self._device) + self.log.info("Response is: {}".format(response)) + return response From f644a56b6ef3606ea78fa5da54c8bd91645abb10 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 7 Nov 2017 17:13:10 +0000 Subject: [PATCH 0357/1466] Stateful simulator --- lewis_emulators/skf_mb350_chopper/device.py | 26 ++++++++++++------- .../interfaces/response_utilities.py | 6 ++--- lewis_emulators/skf_mb350_chopper/states.py | 18 +++++++++++++ 3 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 lewis_emulators/skf_mb350_chopper/states.py diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 728637ca..a3521152 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -1,8 +1,9 @@ from collections import OrderedDict -from lewis.core.statemachine import State from lewis.devices import StateMachineDevice +from states import DefaultState, StoppingState, GoingState + class SimulatedSkfMb350Chopper(StateMachineDevice): def _initialize_data(self): @@ -13,6 +14,7 @@ def _initialize_data(self): self._started = False self.phase = 0 self.frequency = 0 + self.frequency_setpoint = 0 self.phase_percent_ok = 100. self.phase_repeatability = 100. @@ -45,19 +47,23 @@ def get_interlocks(self): def _get_state_handlers(self): return { - 'init': State(), + 'default': DefaultState(), + 'going': GoingState(), + 'stopping': StoppingState(), } def _get_initial_state(self): - return 'init' + return 'default' def _get_transition_handlers(self): return OrderedDict([ - (('init', 'stopped'), lambda: False), + (('default', 'going'), lambda: self.frequency_setpoint != self.frequency and self._started), + (('going', 'stopping'), lambda: not self._started), + (('stopping', 'default'), lambda: self.frequency == 0 and not self._started), ]) def set_frequency(self, frequency): - self.frequency = frequency + self.frequency_setpoint = frequency def get_frequency(self): return self.frequency @@ -75,7 +81,7 @@ def is_controller_ok(self): return True # TODO def is_up_to_speed(self): - return self._started # TODO + return self.frequency == self.frequency_setpoint and self._started def is_able_to_run(self): return True # TODO @@ -84,16 +90,16 @@ def is_shutting_down(self): return False # TODO def is_levitation_complete(self): - return True # TODO + return self.is_up_to_speed() # Not really the correct condition but close enough def is_phase_locked(self): - return True # TODO + return self.is_up_to_speed() # Not really the correct condition but close enough def get_motor_direction(self): - return 1 # TODO + return 1 # Not clear if this can be set externally or only from front panel of physical device. def is_avc_on(self): - return True # TODO + return True # Don't know what this is/represents. Manual doesn't help. def get_phase(self): return self.phase diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index ffc520c7..893578e3 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -96,8 +96,8 @@ def phase_time_response_packet(address, device): :return: The response """ return ResponseBuilder() \ - .add_common_header(address, 0x81, device) \ - .add_int(int(device.get_phase()/100), 4) \ + .add_common_header(address, 0x85, device) \ + .add_int(int(device.get_phase()*10), 4) \ .build() @@ -153,7 +153,7 @@ def add_common_header(self, address, command_number, device): .add_int(0x00, 1) \ .add_int(build_device_status(device), 1) \ .add_int(build_interlock_status(device), 2, low_byte_first=False) \ - .add_int(device.get_frequency(), 2) + .add_int(int(device.get_frequency()), 2) def build(self): """ diff --git a/lewis_emulators/skf_mb350_chopper/states.py b/lewis_emulators/skf_mb350_chopper/states.py new file mode 100644 index 00000000..ecd050ec --- /dev/null +++ b/lewis_emulators/skf_mb350_chopper/states.py @@ -0,0 +1,18 @@ +from lewis.core import approaches +from lewis.core.statemachine import State + + +class DefaultState(State): + pass + + +class StoppingState(State): + def in_state(self, dt): + device = self._context + device.frequency = approaches.linear(device.frequency, 0, 50, dt) + + +class GoingState(State): + def in_state(self, dt): + device = self._context + device.frequency = approaches.linear(device.frequency, device.frequency_setpoint, 50, dt) From fa92b2a9ee90e02dbb3d549e21ca3cdf7c16483a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 8 Nov 2017 11:16:15 +0000 Subject: [PATCH 0358/1466] Set rotator angle command --- lewis_emulators/skf_mb350_chopper/device.py | 3 ++ .../interfaces/response_utilities.py | 19 +++++++++++- .../interfaces/stream_interface.py | 31 +++++++++++++------ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index a3521152..1844ad2e 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -115,3 +115,6 @@ def set_phase_repeatability(self, value): def get_rotator_angle(self): return self.rotator_angle + + def set_rotator_angle(self, angle): + self.rotator_angle = angle diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index 893578e3..ad811aaa 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -43,6 +43,23 @@ def build_device_status(device): return result +def general_status_response_packet(address, device, command): + """ + Returns the general response packet, the default response to any command that doesn't have a more specific response. + + Response structure is: + 8 bytes common header (see ResponseBuilder.add_common_header) + + :param address: The address of this device + :param device: The lewis device + :param command: The command number that this is a reply to + :return: The response + """ + return ResponseBuilder() \ + .add_common_header(address, command, device) \ + .build() + + def phase_information_response_packet(address, device): """ Returns the response to the "get_phase_information" command. @@ -79,7 +96,7 @@ def rotator_angle_response_packet(address, device): """ return ResponseBuilder() \ .add_common_header(address, 0x81, device) \ - .add_int(int(device.get_rotator_angle()), 4) \ + .add_int(int(device.get_rotator_angle()*10), 4) \ .build() diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 86cf56aa..3a4fa7bd 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -3,7 +3,7 @@ from byte_conversions import raw_bytes_to_int from response_utilities import phase_information_response_packet, rotator_angle_response_packet, \ - phase_time_response_packet + phase_time_response_packet, general_status_response_packet from .crc16 import crc16_matches @@ -29,6 +29,7 @@ def any_command(self, command): 0x30: self.stop, 0x60: self.set_rotational_speed, 0x81: self.get_rotator_angle, + 0x82: self.set_rotator_angle, 0x85: self.get_phase_delay, 0x8E: self.set_gate_width, 0x90: self.set_nominal_phase, @@ -51,6 +52,8 @@ def any_command(self, command): crc = [ord(c) for c in command[-2:]] + self.log.info("Checksum bytes are: {}".format(crc)) + if not crc16_matches(command[:-2], crc): raise ValueError("CRC Checksum didn't match") @@ -59,10 +62,12 @@ def any_command(self, command): @has_log def start(self, address, data): self._device.start() + return general_status_response_packet(address, self.device, 0x20) @has_log def stop(self, address, data): self._device.stop() + return general_status_response_packet(address, self.device, 0x30) @has_log def set_nominal_phase(self, address, data): @@ -71,6 +76,7 @@ def set_nominal_phase(self, address, data): nominal_phase = raw_bytes_to_int(data) / 1000. self.log.info("Setting nominal phase to {}".format(nominal_phase)) self._device.set_nominal_phase(nominal_phase) + return general_status_response_packet(address, self.device, 0x90) @has_log def set_gate_width(self, address, data): @@ -79,6 +85,7 @@ def set_gate_width(self, address, data): width = raw_bytes_to_int(data) self.log.info("Setting gate width to {}".format(width)) self._device.set_phase_repeatability(width / 10.) + return general_status_response_packet(address, self.device, 0x8E) @has_log def set_rotational_speed(self, address, data): @@ -87,24 +94,28 @@ def set_rotational_speed(self, address, data): freq = raw_bytes_to_int(data) self.log.info("Setting frequency to {}".format(freq)) self._device.set_frequency(freq) + return general_status_response_packet(address, self.device, 0x60) + + @has_log + def set_rotator_angle(self, address, data): + self.log.info("Setting rotator angle") + self.log.info("Data = {}".format(data)) + angle_times_ten = raw_bytes_to_int(data) + self.log.info("Setting rotator angle to {}".format(angle_times_ten / 10.)) + self._device.set_rotator_angle(angle_times_ten / 10.) + return general_status_response_packet(address, self.device, 0x82) @has_log def get_phase_info(self, address, data): self.log.info("Getting phase info") - response = phase_information_response_packet(address, self._device) - self.log.info("Response is: {}".format(response)) - return response + return phase_information_response_packet(address, self._device) @has_log def get_rotator_angle(self, address, data): self.log.info("Getting rotator angle") - response = rotator_angle_response_packet(address, self._device) - self.log.info("Response is: {}".format(response)) - return response + return rotator_angle_response_packet(address, self._device) @has_log def get_phase_delay(self, address, data): self.log.info("Getting phase time") - response = phase_time_response_packet(address, self._device) - self.log.info("Response is: {}".format(response)) - return response + return phase_time_response_packet(address, self._device) From 1679078dea6107a3c299219050be0f451a8f5822 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 8 Nov 2017 12:11:12 +0000 Subject: [PATCH 0359/1466] Implement checksum in emulator --- .../skf_mb350_chopper/interfaces/crc16.py | 34 ++++++++++++++++--- .../interfaces/response_utilities.py | 2 ++ .../interfaces/stream_interface.py | 11 +++--- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py b/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py index 4dd8e2c5..47031611 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py @@ -1,6 +1,32 @@ -def crc16_matches(input, expected): - return True +from byte_conversions import int_to_raw_bytes, BYTE -def crc16(input): - pass \ No newline at end of file +def crc16_matches(data, expected): + """ + :param data: The input, an iterable of characters + :param expected: The expected checksum, an iterable of two characters + :return: true if the checksum of 'input' is equal to 'expected', false otherwise. + """ + return crc16(data) == expected + + +def crc16(data): + """ + CRC algorithm, translated to python from the C code in appendix A of the manual. + :param data: the data to checksum + :return: the checksum + """ + crc = 0xFFFF + + for b in [ord(c) for c in data]: + crc ^= b + for _ in range(8): + if crc & 1: + crc >>= 1 + crc ^= 0xA001 + else: + crc >>= 1 + + crc %= BYTE**2 + + return int_to_raw_bytes(crc, 2, low_byte_first=False) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index ad811aaa..582a168a 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -1,4 +1,5 @@ from byte_conversions import int_to_raw_bytes, float_to_raw_bytes +from crc16 import crc16 def build_interlock_status(device): @@ -177,4 +178,5 @@ def build(self): Gets the response from the builder :return: the response """ + self.response += crc16(self.response) return self.response \ No newline at end of file diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 3a4fa7bd..757e4472 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -4,7 +4,7 @@ from byte_conversions import raw_bytes_to_int from response_utilities import phase_information_response_packet, rotator_angle_response_packet, \ phase_time_response_packet, general_status_response_packet -from .crc16 import crc16_matches +from .crc16 import crc16_matches, crc16 class SkfMb350ChopperStreamInterface(StreamInterface): @@ -50,12 +50,9 @@ def any_command(self, command): command_data = [c for c in command[3:-2]] - crc = [ord(c) for c in command[-2:]] - - self.log.info("Checksum bytes are: {}".format(crc)) - - if not crc16_matches(command[:-2], crc): - raise ValueError("CRC Checksum didn't match") + if not crc16_matches(command[:-2], command[-2:]): + raise ValueError("CRC Checksum didn't match. Expected {} but got {}" + .format(crc16(command[:-2]), command[-2:])) return command_mapping[command_number](address, command_data) From 538b14756d5ae999dfdf36bc976197011e950c61 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 10 Nov 2017 09:29:17 +0000 Subject: [PATCH 0360/1466] CRC checksum used wrong byte order --- lewis_emulators/skf_mb350_chopper/interfaces/crc16.py | 2 +- .../skf_mb350_chopper/interfaces/stream_interface.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py b/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py index 47031611..9a7862fa 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/crc16.py @@ -29,4 +29,4 @@ def crc16(data): crc %= BYTE**2 - return int_to_raw_bytes(crc, 2, low_byte_first=False) + return int_to_raw_bytes(crc, 2, low_byte_first=True) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 757e4472..9fe8eb50 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -18,7 +18,7 @@ class SkfMb350ChopperStreamInterface(StreamInterface): out_terminator = in_terminator def handle_error(self, request, error): - print "An error occurred at request " + repr(request) + ": " + repr(error) + print("An error occurred at request " + repr(request) + ": " + repr(error)) return str(error) @has_log From 0c42cdd5bd1d903026d10585887db2bde2a5f5c1 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Fri, 10 Nov 2017 10:41:53 +0000 Subject: [PATCH 0361/1466] Remove false terminator --- .../skf_mb350_chopper/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 9fe8eb50..cfa99e4b 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -14,7 +14,7 @@ class SkfMb350ChopperStreamInterface(StreamInterface): Cmd("any_command", "^([\s\S]*)$"), } - in_terminator = "[terminator]" + str(chr(0x0)) * 16 + in_terminator = str(chr(0x0)) * 16 out_terminator = in_terminator def handle_error(self, request, error): From 920028b0bfa5e0e804da51c8666f4279b7177346 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Mon, 13 Nov 2017 16:03:59 +0000 Subject: [PATCH 0362/1466] Created emulator for fermi chopper --- .../fzj_dd_fermi_chopper/__init__.py | 5 +++ .../fzj_dd_fermi_chopper/device.py | 34 ++++++++++++++++ .../interfaces/__init__.py | 3 ++ .../interfaces/stream_interface.py | 39 +++++++++++++++++++ .../fzj_dd_fermi_chopper/states.py | 8 ++++ 5 files changed, 89 insertions(+) create mode 100644 lewis_emulators/fzj_dd_fermi_chopper/__init__.py create mode 100644 lewis_emulators/fzj_dd_fermi_chopper/device.py create mode 100644 lewis_emulators/fzj_dd_fermi_chopper/interfaces/__init__.py create mode 100644 lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py create mode 100644 lewis_emulators/fzj_dd_fermi_chopper/states.py diff --git a/lewis_emulators/fzj_dd_fermi_chopper/__init__.py b/lewis_emulators/fzj_dd_fermi_chopper/__init__.py new file mode 100644 index 00000000..7d9246a7 --- /dev/null +++ b/lewis_emulators/fzj_dd_fermi_chopper/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedFZJDDFCH +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedFZJDDFCH'] diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py new file mode 100644 index 00000000..74d122d6 --- /dev/null +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -0,0 +1,34 @@ +from collections import OrderedDict + +from lewis.devices import StateMachineDevice +from .states import DefaultState + + +class SimulatedFZJDDFCH(StateMachineDevice): + """ + Simulated . + """ + + def _initialize_data(self): + """ + Sets the initial state of the device. + """ + self.magnetic_bearing_state = "OFF" + + def _get_state_handlers(self): + """ + Returns: states and their names + """ + return {DefaultState.NAME: DefaultState()} + + def _get_initial_state(self): + """ + Returns: the name of the initial state + """ + return DefaultState.NAME + + def _get_transition_handlers(self): + """ + Returns: the state transitions + """ + return OrderedDict() diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/__init__.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/__init__.py new file mode 100644 index 00000000..9d0fa4e5 --- /dev/null +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import FZJDDFCHStreamInterface + +__all__ = ['FZJDDFCHStreamInterface'] diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py new file mode 100644 index 00000000..2980918f --- /dev/null +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -0,0 +1,39 @@ +from lewis.adapters.stream import StreamInterface +from lewis.core.logging import has_log + +from lewis_emulators.utils.command_builder import CmdBuilder + +@has_log +class FZJDDFCHStreamInterface(StreamInterface): + """ + Stream interface for the Ethernet port + """ + + commands = { + CmdBuilder("get_magnetic_bearing_state").escape("MBON?").build() + } + + in_terminator = "\r\n" + out_terminator = "\r\n" + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def get_magnetic_bearing_state(self): + + """ + Gets the + + :param address: address of request + Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected + + """ + return self._device.magnetic_bearing_state diff --git a/lewis_emulators/fzj_dd_fermi_chopper/states.py b/lewis_emulators/fzj_dd_fermi_chopper/states.py new file mode 100644 index 00000000..c2beedfb --- /dev/null +++ b/lewis_emulators/fzj_dd_fermi_chopper/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + """ + Device is in default state. + """ + NAME = 'Default' From 49d45f48093cf83a4596e11270ad3896cb5a41ed Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 15 Nov 2017 09:52:00 +0000 Subject: [PATCH 0363/1466] made output changes to reflect hardware output --- lewis_emulators/HFMAGPSU/__init__.py | 3 ++ lewis_emulators/HFMAGPSU/__init__.pyc | Bin 270 -> 369 bytes lewis_emulators/HFMAGPSU/device.py | 4 +- lewis_emulators/HFMAGPSU/device.pyc | Bin 9647 -> 9593 bytes .../HFMAGPSU/interfaces/stream_interface.py | 47 ++++++++++-------- .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 11480 -> 11429 bytes 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/__init__.py b/lewis_emulators/HFMAGPSU/__init__.py index 6d27d064..ea2a7eee 100644 --- a/lewis_emulators/HFMAGPSU/__init__.py +++ b/lewis_emulators/HFMAGPSU/__init__.py @@ -1,3 +1,6 @@ from .device import SimulatedHFMAGPSU +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST __all__ = ['SimulatedHFMAGPSU'] diff --git a/lewis_emulators/HFMAGPSU/__init__.pyc b/lewis_emulators/HFMAGPSU/__init__.pyc index 768f8f6707435f86ba197f9d14a930faed108e70..ae43f25087362963db7b4958fcd969c36379fef2 100644 GIT binary patch delta 206 zcmXwx!3u&v6h-gMs0*pEZ)oGbbx;czfwXXhz>S16#gHbbDA6|*_^Htk^hWCO-nra& z;lB8rOYxce_uk10{DzZm>Do0x(K3`k#gH*D#i0aBj!GaCV4B2%a&V#Cg>oT7Z``bu zu{9whbTdL69rrSicWETkggg$RXEO_>dsFD?+bzhKdN6I?>!vmJtxcwmCH>FMsnSE; QT>shlc>1;=U;+2|7s(JN;{X5v delta 107 zcmey!)W>AY{F#^QxX;?iWCkc;0@4mZTr3MDQhmyscgk%2LVi6I@xVq!>P pW(d|`nHa1r9RyUy;HSY1Bum&pT2jk0lT*Q>6L0mhfs7Mi0sz(U5oQ1Y diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 6085603e..456e1a67 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -6,11 +6,11 @@ class SimulatedHFMAGPSU(StateMachineDevice): def _initialize_data(self): - self._is_output_mode_tesla = False + self._is_output_mode_tesla = True self._is_heater_on = True self._is_paused = True self._output = 0.0 - self._direction = '+' + self._direction = 'POSITIVE' self._ramp_target = 'MID' self._heater_value = 0.0 self._max_target = 34.92 diff --git a/lewis_emulators/HFMAGPSU/device.pyc b/lewis_emulators/HFMAGPSU/device.pyc index 0e123b990cccc2947fc20861daa480e4c6cb3a87..b3fc79c71d58bb430abe0f00f07f5b42a8166861 100644 GIT binary patch delta 1241 zcmZuwOHUI~6u#5W^fA-Nw3LCC(o#UEs8}>6vM4s9F^EAO2#IbQbZRA1X*yFMgvicB ziMlw6F($fjVcZx)SeUq>{s0qj>(-SkjVm!m&$;cCN@#LVzx$nYzw>I}P5wITcK-Cs z)7$czd&RlJ*fL{z2eazH$qa!rA~PHuNX&3@;IdtAW{8~e*o>DM5@%#)xHwRl;pV`{ z3=ao>W_URW*sp@jkTaZ8?ermLFgBf57-LgKH2h4}+%+0!sAE)Ry0utYR7 zm1@CiN06U4^NUrhT($D0MWc|n3g*Jx`WNxALx{nqdjW2{C)79wrtz?}fJP_BHy4q5 zH`F`_)hM~}H2ouyB3$)qu;J0*iMLawy8a7D#fm}-lJc=VU6)fBe*lk(hov28^m@AH zwVSv^^GZl3u3jOP*{GEe9{UP{_X>PaW?|JYV~4eH8CHE~8oi$IpHqA-vDCjKFb1lR zV(IoiFs{fg5w&0$s=*xO0$eRvQ`8nOz1Y-5_^qB&_am=|_Hb^5#uVC%JzFa2PB;tO zb;1c;wO$z zJ%-4lX&P`bJ}>nlx(Qp+QN_Cx0xvpEp&vrgEDlO?)!)ITs3{D7?1QkhUTr)Z1o}d M+?QE*_rDST0k9147ytkO delta 1235 zcmZXT%TH556o==w_x3e?0;Q!7=mW6`NHGuvtchTJ)sQ$4w>)yOsTQcc7mZ4+7o^b7_JGs@ zSL}UW=k9P$NwZaB-Te-3WbbKr10Dzcn)W-FfEbq2mtgFlCdA;nTB(yDtL zZbo;YQO_~z{lAtFdD}BZeGTlR_oR0pGS<`(xZ@k6{O;;6<4Ds{u4Dbu+lYQPSNHq6 zIYa#oMRP6Vw6%`CZFy&`7^s8%4b0eO_~Abc-&(8f)?3GDTSL}lV1lA>L|h0yl9z#! z)CnEI6DU+Js406eR)UTAA$Sm?G>kL+P7vvEDVSIGqI+3H%TbCI-`g~Wzo%=0Wy733 z4rx+1c)}UK5Q|r{TAu}@4}Uecc#Y2xu9n0`Q0j{o){6;uMbnXX-t<#sS?NP|Pq3vJ zXZNEeX+QjpX8bJ}sT>GB-HfJ?hhp%8(vR*MukkY>Y;7TNs)EQ#W6mSdwkZ2jEqlN#V7G2I-_MJ>x}ACS=NV%)^JNutqhXo{t!;(STJ$~rmbSHQZs;c_#sFYAV<$+U?LZhlU_kp2N;1oM>u diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 1fe54bbf..6dcf339d 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -1,9 +1,9 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.utils.command_builder import CmdBuilder from datetime import datetime -class HFMAGPSUStreamInterface(StreamAdapter): +class HFMAGPSUStreamInterface(StreamInterface): in_terminator = "\r\n" out_terminator = chr(19) @@ -12,17 +12,16 @@ class HFMAGPSUStreamInterface(StreamAdapter): CmdBuilder("read_direction").escape("GET SIGN").build(), CmdBuilder("read_output_mode").escape("T").build(), CmdBuilder("read_output").escape("GET O").build(), - CmdBuilder("read_ramp_target").escape("RAMP").build(), CmdBuilder("read_heater_status").escape("HEATER").build(), CmdBuilder("read_max_target").escape("GET MAX").build(), CmdBuilder("read_mid_target").escape("GET MID").build(), CmdBuilder("read_ramp_rate").escape("GET RATE").build(), - CmdBuilder("read_limit").escape("H V").build(), + CmdBuilder("read_limit").escape("GET VL").build(), CmdBuilder("read_pause").escape("P").build(), CmdBuilder("read_heater_value").escape("GET H").build(), CmdBuilder("read_constant").escape("GET TPA").build(), - CmdBuilder("write_direction").escape("D ").arg("-|0|\+").build(), + CmdBuilder("write_direction").escape("D ").arg("ZERO|NEGATIVE|POSITIVE").build(), CmdBuilder("write_output_mode").escape("T ").arg("OFF|ON").build(), CmdBuilder("write_ramp_target").escape("RAMP ").arg("ZERO|MID|MAX").build(), CmdBuilder("write_heater_status").escape("H ").arg("OFF|ON").build(), @@ -58,7 +57,7 @@ def _get_ramp_target_value(self): return target def read_direction(self): - return "HH.MM.SS CURRENT DIRECTION: {}".format(self._device.direction) + return "CURRENT DIRECTION: {}".format(self._device.direction) def write_direction(self, direction): self._device.direction = direction @@ -69,20 +68,17 @@ def read_output_mode(self): mode = "AMPS" if self._device.is_output_mode_tesla: mode = "TESLA" - return "HH.MM.SS UNITS: {}".format(mode) + return "........ UNITS: {}".format(mode) def read_output(self): # If target is + or - the current output, we are ramping up or down # new output = current output +/- ramp rate mode = self._get_output_mode_string() - return "HH.MM.SS OUTPUT: {} {} AT {} VOLTS".format(self._device.output, mode, self._device.heater_value) + return "OUTPUT: {} {} AT {} VOLTS".format(self._device.output, mode, self._device.heater_value) def write_output_mode(self, output_mode): + # Convert values if output mode is changing between amps(OFF) / tesla(ON) constant = self._device.constant - output = self._device.output - max_target = self._device.max_target - mid_target = self._device.max_target - in_tesla = self._device.is_output_mode_tesla if output_mode == "ON": if not self._device.is_output_mode_tesla: self._device.output *= constant @@ -105,7 +101,7 @@ def write_output_mode(self, output_mode): return self._device.log_message def read_ramp_target(self): - return "HH:MM:SS RAMP TARGET: {}".format(self._device.ramp_target) + return "........ RAMP TARGET: {}".format(self._device.ramp_target) def write_ramp_target(self, ramp_target): self._device.ramp_target = ramp_target @@ -113,7 +109,7 @@ def write_ramp_target(self, ramp_target): return self._device.log_message def read_ramp_rate(self): - return "HH:MM:SS RAMP RATE {:.4} A/SEC".format(self._device.ramp_rate) + return "........ RAMP RATE: {:.4} A/SEC".format(self._device.ramp_rate) def write_ramp_rate(self, ramp_rate): self._device.ramp_rate = ramp_rate @@ -122,9 +118,16 @@ def write_ramp_rate(self, ramp_rate): def read_heater_status(self): heater_value = "OFF" - if self._device.heater_value: + if self._device.is_heater_on: heater_value = "ON" - return "HH:MM:SS HEATER STATUS: {}".format(heater_value) + return "........ HEATER STATUS: {}".format(heater_value) + else: + if self._device.is_output_mode_tesla: + mode = "TESLA" + else: + mode = "AMPS" + #return "........ HEATER STATUS: {} AT {} {}".format(heater_value, self._device.output, mode) + return "........ HEATER STATUS: {}".format(heater_value) def write_heater_status(self, heater_status): if heater_status == "ON": @@ -141,7 +144,7 @@ def read_pause(self): paused = "OFF" if self._device.is_paused: paused = "ON" - return "HH:MM:SS PAUSE STATUS: {}".format(paused) + return "........ PAUSE STATUS: {}".format(paused) def write_pause(self, paused): mode = self._get_output_mode_string() @@ -166,7 +169,7 @@ def write_pause(self, paused): return self._device.log_message def read_heater_value(self): - return "HH:MM:SS HEATER OUTPUT: {} VOLTS".format(self._device.heater_value) + return "........ HEATER OUTPUT: {} VOLTS".format(self._device.heater_value) def write_heater_value(self, heater_value): self._device.heater_value = heater_value @@ -175,7 +178,7 @@ def write_heater_value(self, heater_value): def read_max_target(self): mode = self._get_output_mode_string() - return "HH:MM:SS MAX SETTING: {:.4} {}".format(self._device.max_target, mode) + return "........ MAX SETTING: {:.4} {}".format(self._device.max_target, mode) def write_max_target(self, max_target): self._device.max_target = max_target @@ -184,7 +187,7 @@ def write_max_target(self, max_target): def read_mid_target(self): mode = self._get_output_mode_string() - return "HH:MM:SS MID SETTING: {:.4} {}".format(self._device.mid_target, mode) + return "........ MID SETTING: {:.4} {}".format(self._device.mid_target, mode) def write_mid_target(self, mid_target): self._device.mid_target = mid_target @@ -192,7 +195,7 @@ def write_mid_target(self, mid_target): return self._device.log_message def read_limit(self): - return "HH:MM:SS VOLTAGE LIMIT: {} VOLTS".format(self._device.limit) + return "........ VOLTAGE LIMIT: {} VOLTS".format(self._device.limit) def write_limit(self, limit): self._device.limit = limit @@ -200,7 +203,7 @@ def write_limit(self, limit): return self._device.log_message def read_constant(self): - return "HH:MM:SS FIELD CONSTANT: {:.7} T/A".format(self._device.constant) + return "........ FIELD CONSTANT: {:.7} T/A".format(self._device.constant) def write_constant(self, constant): self._device.constant = constant diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc index da6783af6a32a094891aeb3b7841e6beb8d9cb40..50c0159b0d3d0c9755984ed2d3f4c852536f4aa7 100644 GIT binary patch delta 2732 zcmZ`*Urdu%6hB|vuRvR*P$*dbeIQ_wiJKryP_SUdD)Q&HI5p9(;}$0q3jIE^2QihX zOEk+2xm)(MCCmQ2Y%y7g@x?4YxWpGT-?k?c-}kgVY%fc8&iPs>iiCUm>$&IN-#vff zkMrN3FLC}|?%D75u9qwaDgYpEWz(sJ*rJur&ZTD4>vv9jYEeCc%d~!e(Wm7~L5AR_ z2JY?MS+=oH9-~ss#+yc1Z1$mHx4`dI6a*(|Yc7Z&^paNtc13$=q29+S+Wl#n3FoOWd z0S46|2N~3WJi;Id@+gB^kV9}QhpcrVhgr27k~S?7Rxgbks=`yL zb*dK6dCgGQJgr(QYn!|c+I}_YT}SORZ$D~v-w#@k`pI|PRfBh^|9tiCW(2}eP35(G zYE5^cVo*IQpWAEDRzHaj6qXZ(X=OzVeuf>Srq_sm`3A#pK{H+@*4ciYxKP4Jsm7}UYo?Lak~U0bKG=*wj9tFzhpuu6P9RrwRAE579`%n z_a#dKEp0rH?+R#YxGGe}v;&T$OHEZZR_}DXTC4i}Z3CUON>R*a7ELR)XxT9xM2+Yr z(L)9lpG0;gEjm;*&|`1{ISUj_%ZnnjVlAy$$%Twei(v|GMF6#vxJ=4DIMjIcxt`5r zipZHMBM}i;zgsD_D+!IoK@?$(JXQ$abt7o`5#tz>$!uz2DQTs$m($i|wOD&bJ#TfX z`!$u#23gZNuC-Td>>xc!ug2|p^~>I&14Om`F(Q(P;!_5?d#@Xj1M%pnU1s^}ejY4T zw+~NdkokR^@x+@LPY`2A2)DDiOg9S9e41aICBGTI~-z2{oa_&@ucXZErSH7mczl_ zaEx>`EwKp$j zI{i0rU)|j^U0R@2k6Rj=i{>Vs??q8Gw7D8|4=Oj*TC231foxvm;KS4mdL6=tL^N6; zGS0+~8}Vo&5t|q*G#?Ilsz>n(Kh2@n_$iN%kUydRZS&i2AiEvk(1UNdtv#mp_ZetZ zc!T<`K_YnsM4QQ zLFFpAJ_CfJB&tc!a1%5(1ogR~h7{C0g33}*MG2}FL3tOHPC*$63hGipK@#M*AYVj- edb#hOHm8=mn$&t%*jrcTEYo?FZ#?O$*8T@(cINm1 delta 2742 zcmZuyO-xi*6h3ce9zVm6{0ssE4380Y3K6yZ2_i5IBZD{ycLpQGI+=3E2+E-Ip6x;? zHEK877`RQkX`61eO==Sx+eH_qiK%VUq)nTqsk^qO+ji5WyCyy7%&$t|o%!Cm=ic*k zzjNN5`6u(m_J1mzKW=E7#h0op03c$e67dzkj9;`8sm+^H&IVMD;bY$V?DszHOfg&o zNa!Hla7}~dB9KiCNE*7{`zr4+|9J3Cho3r>*Ta2;Tw3}g?3a*(|YDnRxzs07*1UXgBd+NwCa zm+Zs<2_FiJ4lu2h*oD778>uOJF~GZA2uIYN%E-iiQg)#*Yf$8Znwy@CM)HC1*$2p! zLdqY<1zcH`38n3ML)UR_Ts_)xs)FouXDLrrYuUlqW4pfz;r<4N~{=dNZKW znUw&JWjxXb1GbnA)JU~^19$VQ4i;`^$FcP3#URmEgKIX>E(NsYyL`5t&+gZ7mvk`x z8DutuSP7tICVU5^vN+>)ki4c>9m|-G4F=Vd>T>mOwac}gPvxn3=KvMhDnYThnl!C= z(h^7TMf8#2d+H?vDrhuyIU#)NX-#(-Q-SJ=+%alhhM!=5A8(;Emy$6nVP1&SrEWDj zYG+Vq5=THQ&-|&!wWqp6p}}x?5GhP8WJLX9A{fnchM2aMGE zGp}fwIa|n8^@H~WpXWj7cj|2nTbxbP0axs>QTZz2) zt2*8{m>fZ{zP6Pk2yGVu=|V)%nr4Kr(Jq<_RD&wj+;lWFiZ5aWh3Q6-D@G`YkqILb z^+)HPZ=qq@@@xxr~|i!K1PBJ@(KwE9}L+4y<`JE3pQGiQrYWw_gW$55Xh z66J`N%-FdEW}J$xCeuY7r}ZF-f>EGW+)A%U@Pq)t8wy17Qghh4Tb3>=E^OmY?J##ph zW~Qnq=AtumOkm^WiR018bBq#nc?CpLA67cFkC8|+O(MQqj=Z4GxFvnTIu-U4HNR`N zB~-|Y6d zb7O(;^up50l2uSteFxeCTR_rc!&U&QH;U%}$s0Kg!$p%>V!Z From 509840aa89a6a0e2b6518b6aea932acac0bd00f0 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Wed, 15 Nov 2017 14:18:27 +0000 Subject: [PATCH 0364/1466] added commands --- .../fzj_dd_fermi_chopper/device.py | 6 +++-- .../interfaces/stream_interface.py | 25 ++++++++++++++----- .../fzj_dd_fermi_chopper/states.py | 2 ++ 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 74d122d6..e14969b5 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -6,14 +6,16 @@ class SimulatedFZJDDFCH(StateMachineDevice): """ - Simulated . + Simulated FZJ Digital Drive Fermi Chopper Controller. """ def _initialize_data(self): """ Sets the initial state of the device. """ - self.magnetic_bearing_state = "OFF" + self.magnetic_bearing_status = "OFF" + self.reference_frequency = 0 + self.frequency_setpoint = 0 def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 2980918f..8cfa0289 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -10,7 +10,8 @@ class FZJDDFCHStreamInterface(StreamInterface): """ commands = { - CmdBuilder("get_magnetic_bearing_state").escape("MBON?").build() + CmdBuilder("get_magnetic_bearing_status").escape("MBON?").build(), + CmdBuilder("get_all_status").escape("ASTA?").build() } in_terminator = "\r\n" @@ -27,13 +28,25 @@ def handle_error(self, request, error): """ self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) - def get_magnetic_bearing_state(self): + def get_magnetic_bearing_status(self): """ - Gets the + Gets the magnetic bearing status for the FZJ Digital Drive Fermi Chopper Controller - :param address: address of request - Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected + :param + Returns: """ - return self._device.magnetic_bearing_state + return self._device.magnetic_bearing_status + + def get_all_status(self): + + """ + Gets the all status for the FZJ Digital Drive Fermi Chopper Controller + + :param + Returns: + + """ + return "{ref_freq:.2f};{freq_setp:.2f}".format(ref_freq=self._device.reference_frequency, + freq_setp=self._device.frequency_setpoint) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/states.py b/lewis_emulators/fzj_dd_fermi_chopper/states.py index c2beedfb..36c87802 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/states.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/states.py @@ -1,5 +1,7 @@ from lewis.core.statemachine import State +# FZJ Digital Drive Fermi Chopper Controller + class DefaultState(State): """ From d5f62b4c42f087bf1eb9d4e57f109d243705755a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 16 Nov 2017 11:41:24 +0000 Subject: [PATCH 0365/1466] Make emulator act like real device --- .../skf_mb350_chopper/interfaces/byte_conversions.py | 4 ++-- .../skf_mb350_chopper/interfaces/response_utilities.py | 2 +- .../skf_mb350_chopper/interfaces/stream_interface.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py index 17548428..a9ee6ff2 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/byte_conversions.py @@ -38,8 +38,8 @@ def raw_bytes_to_int(raw_bytes): def float_to_raw_bytes(fl): - return "".join(chr(c) for c in bytearray(struct.pack(">f", fl))) + return "".join(chr(c) for c in bytearray(struct.pack(">f", fl)))[::-1] def raw_bytes_to_float(raw_bytes): - return struct.unpack('f', raw_bytes)[0] + return struct.unpack('f', raw_bytes[::-1])[0] diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py index 582a168a..a3af92c2 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/response_utilities.py @@ -115,7 +115,7 @@ def phase_time_response_packet(address, device): """ return ResponseBuilder() \ .add_common_header(address, 0x85, device) \ - .add_int(int(device.get_phase()*10), 4) \ + .add_float(device.get_phase()/1000.) \ .build() diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index cfa99e4b..559e0e60 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -14,7 +14,7 @@ class SkfMb350ChopperStreamInterface(StreamInterface): Cmd("any_command", "^([\s\S]*)$"), } - in_terminator = str(chr(0x0)) * 16 + in_terminator = "\r\n" out_terminator = in_terminator def handle_error(self, request, error): From 8dbed776d24e7f58a80ffcda2e6db78697a3f212 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Thu, 16 Nov 2017 13:22:04 +0000 Subject: [PATCH 0366/1466] added commands --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 1 + .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index e14969b5..463b5289 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -16,6 +16,7 @@ def _initialize_data(self): self.magnetic_bearing_status = "OFF" self.reference_frequency = 0 self.frequency_setpoint = 0 + self.frequency = 0 def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 8cfa0289..86a0cb39 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -3,6 +3,7 @@ from lewis_emulators.utils.command_builder import CmdBuilder + @has_log class FZJDDFCHStreamInterface(StreamInterface): """ @@ -48,5 +49,6 @@ def get_all_status(self): Returns: """ - return "{ref_freq:.2f};{freq_setp:.2f}".format(ref_freq=self._device.reference_frequency, - freq_setp=self._device.frequency_setpoint) + return "{freq_ref:.2f};{freq_setp:.2f};{freq:.2f}".format(freq_ref=self._device.reference_frequency, + freq_setp=self._device.frequency_setpoint, + freq=self._device.frequency) From f768a16ffeb53976feecd0b79cc9b63822b9330a Mon Sep 17 00:00:00 2001 From: David Keymer Date: Thu, 16 Nov 2017 15:26:45 +0000 Subject: [PATCH 0367/1466] added commands --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 3 +++ .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 463b5289..e05a636f 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -17,6 +17,9 @@ def _initialize_data(self): self.reference_frequency = 0 self.frequency_setpoint = 0 self.frequency = 0 + self.phase_setpoint = 0 + self.phase = 0 + self.phase_status = "NOK" def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 86a0cb39..740a724b 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -49,6 +49,7 @@ def get_all_status(self): Returns: """ - return "{freq_ref:.2f};{freq_setp:.2f};{freq:.2f}".format(freq_ref=self._device.reference_frequency, - freq_setp=self._device.frequency_setpoint, - freq=self._device.frequency) + return "{freq_ref:.2f};{freq_setp:.2f};{freq:.2f};{phas_setp:.2f};{phas:.2f};{phas_stat:s}"\ + .format(freq_ref=self._device.reference_frequency, freq_setp=self._device.frequency_setpoint, + freq=self._device.frequency, phas_setp=self._device.phase_setpoint, phas=self._device.phase, + phas_stat=self._device.phase_status) From a288600453794de10201a4a30b40828df6f6a2aa Mon Sep 17 00:00:00 2001 From: David Keymer Date: Fri, 17 Nov 2017 17:17:35 +0000 Subject: [PATCH 0368/1466] added commands. refactored code --- .../fzj_dd_fermi_chopper/device.py | 5 ++-- .../interfaces/stream_interface.py | 26 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index e05a636f..bbb00894 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -13,13 +13,14 @@ def _initialize_data(self): """ Sets the initial state of the device. """ - self.magnetic_bearing_status = "OFF" - self.reference_frequency = 0 + self.frequency_reference = 0 self.frequency_setpoint = 0 self.frequency = 0 self.phase_setpoint = 0 self.phase = 0 self.phase_status = "NOK" + self.magnetic_bearing = "OFF" + self.magnetic_bearing_status = "NOK" def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 740a724b..5e9f4ad0 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -34,11 +34,14 @@ def get_magnetic_bearing_status(self): """ Gets the magnetic bearing status for the FZJ Digital Drive Fermi Chopper Controller - :param + :param Returns: """ - return self._device.magnetic_bearing_status + pass + + # return self._device.magnetic_bearing_status + return def get_all_status(self): @@ -49,7 +52,18 @@ def get_all_status(self): Returns: """ - return "{freq_ref:.2f};{freq_setp:.2f};{freq:.2f};{phas_setp:.2f};{phas:.2f};{phas_stat:s}"\ - .format(freq_ref=self._device.reference_frequency, freq_setp=self._device.frequency_setpoint, - freq=self._device.frequency, phas_setp=self._device.phase_setpoint, phas=self._device.phase, - phas_stat=self._device.phase_status) + device = self._device + values = [ + "{0:.2f}".format(device.frequency_reference), + "{0:.2f}".format(device.frequency_setpoint), + "{0:.2f}".format(device.frequency), + "{0:.2f}".format(device.phase_setpoint), + "{0:.2f}".format(device.phase), + "{0:s}".format(device.phase_status), + "{0:s}".format(device.magnetic_bearing), + "{0:s}".format(device.magnetic_bearing_status) + ] + + return_string = ";".join(values) + self.log.error(return_string) + return return_string From b2ee64e28449c69538caf5cfcaa3590a72b512c3 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Mon, 20 Nov 2017 10:31:00 +0000 Subject: [PATCH 0369/1466] added command --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 1 + .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index bbb00894..1e6aa1b4 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -21,6 +21,7 @@ def _initialize_data(self): self.phase_status = "NOK" self.magnetic_bearing = "OFF" self.magnetic_bearing_status = "NOK" + self.magnetic_bearing_integrator = 0 def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 5e9f4ad0..e690a075 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -61,7 +61,8 @@ def get_all_status(self): "{0:.2f}".format(device.phase), "{0:s}".format(device.phase_status), "{0:s}".format(device.magnetic_bearing), - "{0:s}".format(device.magnetic_bearing_status) + "{0:s}".format(device.magnetic_bearing_status), + "{0:.1f}".format(device.magnetic_bearing_integrator) ] return_string = ";".join(values) From 31f01d5aa630f6a4bdd239fb19970a9a2f5b1237 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Mon, 20 Nov 2017 11:14:32 +0000 Subject: [PATCH 0370/1466] added function to reset to default values --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 1e6aa1b4..c87600bd 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -40,3 +40,11 @@ def _get_transition_handlers(self): Returns: the state transitions """ return OrderedDict() + + def reset(self): + """ + Reset device to defaults + :return: + """ + + self._initialize_data() From 950f749652517c99acece200c1c29c86d98aef62 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Mon, 20 Nov 2017 13:35:09 +0000 Subject: [PATCH 0371/1466] Update device.py --- lewis_emulators/skf_mb350_chopper/device.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/device.py b/lewis_emulators/skf_mb350_chopper/device.py index 1844ad2e..14470899 100644 --- a/lewis_emulators/skf_mb350_chopper/device.py +++ b/lewis_emulators/skf_mb350_chopper/device.py @@ -78,16 +78,16 @@ def stop(self): self._started = False def is_controller_ok(self): - return True # TODO + return True def is_up_to_speed(self): return self.frequency == self.frequency_setpoint and self._started def is_able_to_run(self): - return True # TODO + return True def is_shutting_down(self): - return False # TODO + return False def is_levitation_complete(self): return self.is_up_to_speed() # Not really the correct condition but close enough From 698145b4645adc368aa4947ced709c37fb2244eb Mon Sep 17 00:00:00 2001 From: David Keymer Date: Mon, 20 Nov 2017 14:55:39 +0000 Subject: [PATCH 0372/1466] added commands --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 9 +++++++++ .../interfaces/stream_interface.py | 11 ++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index c87600bd..6be152d3 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -22,6 +22,15 @@ def _initialize_data(self): self.magnetic_bearing = "OFF" self.magnetic_bearing_status = "NOK" self.magnetic_bearing_integrator = 0 + self.drive = "OFF" + self.drive_status = "START" + self.drive_l1_current = 0 + self.drive_l2_current = 0 + self.drive_l3_current = 0 + self.drive_direction = "CW" + self.parked_open_status = "OK" + self.drive_temperature = 0 + self.input_clock = 0 def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index e690a075..0a87bb22 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -62,7 +62,16 @@ def get_all_status(self): "{0:s}".format(device.phase_status), "{0:s}".format(device.magnetic_bearing), "{0:s}".format(device.magnetic_bearing_status), - "{0:.1f}".format(device.magnetic_bearing_integrator) + "{0:.1f}".format(device.magnetic_bearing_integrator), + "{0:s}".format(device.drive), + "{0:s}".format(device.drive_status), + "{0:.2f}".format(device.drive_l1_current), + "{0:.2f}".format(device.drive_l2_current), + "{0:.2f}".format(device.drive_l3_current), + "{0:s}".format(device.drive_direction), + "{0:s}".format(device.parked_open_status), + "{0:.2f}".format(device.drive_temperature), + "{0:.2f}".format(device.input_clock) ] return_string = ";".join(values) From e6a45c8c38d7fd718097f3789732ae86eeb1e4ff Mon Sep 17 00:00:00 2001 From: David Keymer Date: Mon, 20 Nov 2017 17:10:59 +0000 Subject: [PATCH 0373/1466] added commands --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 6 ++++++ .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 6be152d3..37ed5b56 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -31,6 +31,12 @@ def _initialize_data(self): self.parked_open_status = "OK" self.drive_temperature = 0 self.input_clock = 0 + self.phase_outage = 0 + self.master_chopper = "C01" + self.logging = "ON" + self.lmsr_status = "OK" + self.dsp_status = "OK" + self.interlock_er_status = "OK" def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 0a87bb22..4e3052f5 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -71,7 +71,13 @@ def get_all_status(self): "{0:s}".format(device.drive_direction), "{0:s}".format(device.parked_open_status), "{0:.2f}".format(device.drive_temperature), - "{0:.2f}".format(device.input_clock) + "{0:.2f}".format(device.input_clock), + "{0:.2f}".format(device.phase_outage), + "{0:s}".format(device.master_chopper), + "{0:s}".format(device.logging), + "{0:s}".format(device.lmsr_status), + "{0:s}".format(device.dsp_status), + "{0:s}".format(device.interlock_er_status) ] return_string = ";".join(values) From a81d1ffdd2f0a3a2de2fa73d972d092f2d11c87e Mon Sep 17 00:00:00 2001 From: esouthren Date: Tue, 21 Nov 2017 11:21:19 +0000 Subject: [PATCH 0374/1466] added more test, for constant --- lewis_emulators/HFMAGPSU/device.py | 2 +- lewis_emulators/HFMAGPSU/device.pyc | Bin 9593 -> 9586 bytes .../HFMAGPSU/interfaces/stream_interface.py | 11 +++++++++-- .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 11429 -> 11572 bytes lewis_emulators/HFMAGPSU/states.py | 3 ++- lewis_emulators/HFMAGPSU/states.pyc | Bin 2009 -> 2065 bytes 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 456e1a67..74ca95b3 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -10,7 +10,7 @@ def _initialize_data(self): self._is_heater_on = True self._is_paused = True self._output = 0.0 - self._direction = 'POSITIVE' + self._direction = '+' self._ramp_target = 'MID' self._heater_value = 0.0 self._max_target = 34.92 diff --git a/lewis_emulators/HFMAGPSU/device.pyc b/lewis_emulators/HFMAGPSU/device.pyc index b3fc79c71d58bb430abe0f00f07f5b42a8166861..bfd11392559e6336bcf04ce2a6f4c4d13afd9295 100644 GIT binary patch delta 27 icmezA^~sB!`7VM&l>`E-Gj0_A6+M7*TToeI*w+AZ# delta 34 pcmez5_0x-;`7`E*=91IK$0sg_BA)aBboAp>+6am2F37P-^ diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py index 6dcf339d..de27ea9f 100644 --- a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py +++ b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py @@ -21,7 +21,7 @@ class HFMAGPSUStreamInterface(StreamInterface): CmdBuilder("read_heater_value").escape("GET H").build(), CmdBuilder("read_constant").escape("GET TPA").build(), - CmdBuilder("write_direction").escape("D ").arg("ZERO|NEGATIVE|POSITIVE").build(), + CmdBuilder("write_direction").escape("D ").arg("0|-|\+").build(), CmdBuilder("write_output_mode").escape("T ").arg("OFF|ON").build(), CmdBuilder("write_ramp_target").escape("RAMP ").arg("ZERO|MID|MAX").build(), CmdBuilder("write_heater_status").escape("H ").arg("OFF|ON").build(), @@ -57,7 +57,14 @@ def _get_ramp_target_value(self): return target def read_direction(self): - return "CURRENT DIRECTION: {}".format(self._device.direction) + dir = self._device.direction + if dir == "+": + dir_string = "POSITIVE" + elif dir == "-": + dir_string = "NEGATIVE" + elif dir == "0": + dir_string = "ZERO"; + return "CURRENT DIRECTION: {}".format(dir_string) def write_direction(self, direction): self._device.direction = direction diff --git a/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc b/lewis_emulators/HFMAGPSU/interfaces/stream_interface.pyc index 50c0159b0d3d0c9755984ed2d3f4c852536f4aa7..e3dec20f7b7c4540a63e4ed5260d4debeea260c8 100644 GIT binary patch delta 813 zcmX|;OH30{6o&69ofaxZ1e(@9>;tH^O%oEMaU)5A;nmQV+Y#iUrlg9Xsj;1qm=qdg zToE+nB)Sk1UBFr$Scr*AzEx}%##l!=UY?8@zm#oDrH0;3w7O}PE|QcPo247rJg_pbJg^%kRt&PdiOri> zAu3^wglAdaETKa}MZzS@TO?#GZ)LM4RyMJFz_O8AGEeD18q-CcNoNPN_JI*qvbba^ zwJ)JnQHO4(wsDoG@pF9u_v@R&+)pGT{+WkJbW!DJin{IKd8MQmXA08@;RRf>*EKg1 zW>8O8U3K9T`xKnVzxDv>PuE?1+F%CNSZnYC4C40D=g=jvVx^NA<9Fj%fNuQQGz)za zHxnK)UN=Vp3K$Oz;c3S!xNYn?Yyfi-s7@E8Fy#;`jNEkM4*qeLExlB66)UbuOOGhU zhwj4yP9J~fQVEpoB>ieaQ+Xh(CA14!-irZuo4a3h2ZW@BgoO+WN#bSqIxM2%xm0z( z?%9P$MyHnpsJQHFfnHqq6{b?6U4+NQpt*D$7S(?JoB>L{6xf)35SwoICLtRibyI~iMO5;&!?4j;2xO7sd uDXsb!m1Fe&O-|!S#79j0!omE!wDTQTPE|ldZ?qGJjEQI~fX%4H!tf6o(7rtY delta 640 zcmX|;O=uHQ6oqq1CTUYLt+q|lWN0R-jcKa5X;GVGYz)&nMID`t#a0DVtBgT}rnixDs^hz0>ICoAb^)?>^?d_1R}LA^)#P z_+YT(b?Cj6i28iKS=}<-)B0rDHl}s=j9E2Uh|khy5S!6qJQ_*GQk1h)g40g8y3m-v z;w;pc@Q}KKvfPe?L!CGxpNGSEUsj0_yDjadq$Wl>6@Ut!?Ro`co*g3F5G#AO0SY+N zy$HuVyPIrNO!edeu48)W6mIsug+<{;!vJ;9_!5i51b*vf^85syXyCoL8`MZyC6xOv z28%3rFKQ!s6My$Tjh9F*M+hhMvaMTc)h^rTs+NW?6Z;b5eO|eubvkQY?THs{K8n;b{NT+yuwHiVCd883yD3Fvm~J+T5}u(f8Vodd;ya z97q##+=!K7Ofkk7qXZ{FXw;l}tAJAKF+9R2sRvNR)!bdFi?-IhS}yMoU?d$UpQ|Zy zHMo%uzzh67Z~@iKkyh_vIw8bPrVZc?ZDGsqAp0HeWNe%sT!#&GqZ%67Pw=5NTmjP? yYfLu#D6x&h`CD)j*YXwPv&4%;NxaUVPFW>U}Rum@YCR# zY{sgoAH)IVl<)wF_@czzg7}ieqV&{~5^j({Ze|K9Ke1wR2J0UtMW)SZY{iTKsAnlR delta 132 zcmbOzaFd^d`76M?68#xxSFg8qH%_3)C!^Duq%+SopP|Lzl!pH#PvVgg)U^Xj5 zkq<)+GeZq4gGeniLmmS|2^&KeJ4`9ZWGPk!J`RQy28Lh_Mg|53KMl^w9;}+2CEP$x Vd~RmS Date: Tue, 21 Nov 2017 13:40:15 +0000 Subject: [PATCH 0375/1466] added remaining commands for status string --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 7 +++++++ .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 9 ++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 37ed5b56..506dad65 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -37,6 +37,13 @@ def _initialize_data(self): self.lmsr_status = "OK" self.dsp_status = "OK" self.interlock_er_status = "OK" + self.interlock_vacuum_status = "OK" + self.interlock_frequency_monitoring_status = "OK" + self.interlock_magnetic_bearing_amplifier_temperature_status = "OK" + self.interlock_magnetic_bearing_amplifier_current_status = "OK" + self.interlock_drive_amplifier_temperature_status = "OK" + self.interlock_drive_amplifier_current_status = "OK" + self.interlock_ups_status = "OK" def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 4e3052f5..07326cf7 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -77,7 +77,14 @@ def get_all_status(self): "{0:s}".format(device.logging), "{0:s}".format(device.lmsr_status), "{0:s}".format(device.dsp_status), - "{0:s}".format(device.interlock_er_status) + "{0:s}".format(device.interlock_er_status), + "{0:s}".format(device.interlock_vacuum_status), + "{0:s}".format(device.interlock_frequency_monitoring_status), + "{0:s}".format(device.interlock_magnetic_bearing_amplifier_temperature_status), + "{0:s}".format(device.interlock_magnetic_bearing_amplifier_current_status), + "{0:s}".format(device.interlock_drive_amplifier_temperature_status), + "{0:s}".format(device.interlock_drive_amplifier_current_status), + "{0:s}".format(device.interlock_ups_status) ] return_string = ";".join(values) From c98bcf3a00b79453eae41b3ab3958bcf0159c978 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 23 Nov 2017 11:11:26 +0000 Subject: [PATCH 0376/1466] Put has_log on class --- .../skf_mb350_chopper/interfaces/stream_interface.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py index 559e0e60..3fa6fe16 100644 --- a/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/skf_mb350_chopper/interfaces/stream_interface.py @@ -7,6 +7,7 @@ from .crc16 import crc16_matches, crc16 +@has_log class SkfMb350ChopperStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation. Match anything! @@ -21,7 +22,6 @@ def handle_error(self, request, error): print("An error occurred at request " + repr(request) + ": " + repr(error)) return str(error) - @has_log def any_command(self, command): command_mapping = { @@ -56,17 +56,14 @@ def any_command(self, command): return command_mapping[command_number](address, command_data) - @has_log def start(self, address, data): self._device.start() return general_status_response_packet(address, self.device, 0x20) - @has_log def stop(self, address, data): self._device.stop() return general_status_response_packet(address, self.device, 0x30) - @has_log def set_nominal_phase(self, address, data): self.log.info("Setting phase") self.log.info("Data = {}".format(data)) @@ -75,7 +72,6 @@ def set_nominal_phase(self, address, data): self._device.set_nominal_phase(nominal_phase) return general_status_response_packet(address, self.device, 0x90) - @has_log def set_gate_width(self, address, data): self.log.info("Setting gate width") self.log.info("Data = {}".format(data)) @@ -84,7 +80,6 @@ def set_gate_width(self, address, data): self._device.set_phase_repeatability(width / 10.) return general_status_response_packet(address, self.device, 0x8E) - @has_log def set_rotational_speed(self, address, data): self.log.info("Setting frequency") self.log.info("Data = {}".format(data)) @@ -93,7 +88,6 @@ def set_rotational_speed(self, address, data): self._device.set_frequency(freq) return general_status_response_packet(address, self.device, 0x60) - @has_log def set_rotator_angle(self, address, data): self.log.info("Setting rotator angle") self.log.info("Data = {}".format(data)) @@ -102,17 +96,14 @@ def set_rotator_angle(self, address, data): self._device.set_rotator_angle(angle_times_ten / 10.) return general_status_response_packet(address, self.device, 0x82) - @has_log def get_phase_info(self, address, data): self.log.info("Getting phase info") return phase_information_response_packet(address, self._device) - @has_log def get_rotator_angle(self, address, data): self.log.info("Getting rotator angle") return rotator_angle_response_packet(address, self._device) - @has_log def get_phase_delay(self, address, data): self.log.info("Getting phase time") return phase_time_response_packet(address, self._device) From b3f24ad2ae0b9edb46727253bb852585b1c794c6 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Thu, 23 Nov 2017 14:31:47 +0000 Subject: [PATCH 0377/1466] Updated emulator with new default values --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 6 +++--- .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 506dad65..022b1fac 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -18,16 +18,16 @@ def _initialize_data(self): self.frequency = 0 self.phase_setpoint = 0 self.phase = 0 - self.phase_status = "NOK" + self.phase_status = "OK" self.magnetic_bearing = "OFF" - self.magnetic_bearing_status = "NOK" + self.magnetic_bearing_status = "OK" self.magnetic_bearing_integrator = 0 self.drive = "OFF" self.drive_status = "START" self.drive_l1_current = 0 self.drive_l2_current = 0 self.drive_l3_current = 0 - self.drive_direction = "CW" + self.drive_direction = "CLOCK" self.parked_open_status = "OK" self.drive_temperature = 0 self.input_clock = 0 diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 07326cf7..3ef96f50 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -88,5 +88,8 @@ def get_all_status(self): ] return_string = ";".join(values) - self.log.error(return_string) + + # print reply string in log + # self.log.info(return_string) + return return_string From 5dbf9c6fdd0fcc6c9a32c311ded9aa5a26193e81 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Thu, 23 Nov 2017 16:03:22 +0000 Subject: [PATCH 0378/1466] Updated emulator to account for boolean direction parameter --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 2 +- .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 022b1fac..63e62024 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -27,7 +27,7 @@ def _initialize_data(self): self.drive_l1_current = 0 self.drive_l2_current = 0 self.drive_l3_current = 0 - self.drive_direction = "CLOCK" + self.is_drive_direction_clockwise = True self.parked_open_status = "OK" self.drive_temperature = 0 self.input_clock = 0 diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 3ef96f50..73913a42 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -53,6 +53,7 @@ def get_all_status(self): """ device = self._device + drive_direction = "CLOCK" if device.is_drive_direction_clockwise else "ANTICLOCK" values = [ "{0:.2f}".format(device.frequency_reference), "{0:.2f}".format(device.frequency_setpoint), @@ -68,7 +69,7 @@ def get_all_status(self): "{0:.2f}".format(device.drive_l1_current), "{0:.2f}".format(device.drive_l2_current), "{0:.2f}".format(device.drive_l3_current), - "{0:s}".format(device.drive_direction), + "{0:s}".format(drive_direction), "{0:s}".format(device.parked_open_status), "{0:.2f}".format(device.drive_temperature), "{0:.2f}".format(device.input_clock), From 7ebd134f2e06a9958871a209190e848d4c7b32bd Mon Sep 17 00:00:00 2001 From: esouthren Date: Thu, 23 Nov 2017 16:51:49 +0000 Subject: [PATCH 0379/1466] emulating direction working --- lewis_emulators/HFMAGPSU/states.py | 3 +++ lewis_emulators/HFMAGPSU/states.pyc | Bin 2065 -> 2123 bytes 2 files changed, 3 insertions(+) diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py index 75a2321e..347e65d3 100644 --- a/lewis_emulators/HFMAGPSU/states.py +++ b/lewis_emulators/HFMAGPSU/states.py @@ -37,6 +37,9 @@ def in_state(self, dt): if device._is_output_mode_tesla: rate = rate * constant + if device._direction == '-': + target *= -1 + device._output = approaches.linear(float(device._output), float(target), float(rate), dt) diff --git a/lewis_emulators/HFMAGPSU/states.pyc b/lewis_emulators/HFMAGPSU/states.pyc index 170c0bd60c59c882cb2739cc2ac253e70106f049..02d7e7c50dc9c8847729dff8c73655c4dd5e59af 100644 GIT binary patch delta 144 zcmbOza9V(!`7oB1CIGtFj0V=`7lE_!W$&kgxP{PPi!^DuqJ=u&^fsdOZg@GYh ogOP!O!B2x{az1POWLCDr{Cq$@10yFBKcgZOKMUvP+iV4l0BSlBsQ>@~ From b6e84da93dbfabd0b5d140bfab7c292eb7c0c2fd Mon Sep 17 00:00:00 2001 From: David Keymer Date: Thu, 23 Nov 2017 17:40:40 +0000 Subject: [PATCH 0380/1466] Updated emulator to include option for chopper address --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 1 + .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 63e62024..03f7dc37 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -13,6 +13,7 @@ def _initialize_data(self): """ Sets the initial state of the device. """ + self.chopper_name = "C01" self.frequency_reference = 0 self.frequency_setpoint = 0 self.frequency = 0 diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 73913a42..30c42d5a 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -12,7 +12,7 @@ class FZJDDFCHStreamInterface(StreamInterface): commands = { CmdBuilder("get_magnetic_bearing_status").escape("MBON?").build(), - CmdBuilder("get_all_status").escape("ASTA?").build() + CmdBuilder("get_all_status").arg(".{3}").escape(";ASTA?").build() } in_terminator = "\r\n" @@ -43,7 +43,7 @@ def get_magnetic_bearing_status(self): # return self._device.magnetic_bearing_status return - def get_all_status(self): + def get_all_status(self, chopper_name): """ Gets the all status for the FZJ Digital Drive Fermi Chopper Controller @@ -53,8 +53,11 @@ def get_all_status(self): """ device = self._device + if chopper_name != device.chopper_name: + return None drive_direction = "CLOCK" if device.is_drive_direction_clockwise else "ANTICLOCK" values = [ + "{0:3s}".format(device.chopper_name), "{0:.2f}".format(device.frequency_reference), "{0:.2f}".format(device.frequency_setpoint), "{0:.2f}".format(device.frequency), From 60991bcaa1a880759993b270f073cf73657b1823 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 27 Nov 2017 11:57:01 +0000 Subject: [PATCH 0381/1466] Add template emulator --- lewis_emulators/samsm300/__init__.py | 5 ++++ lewis_emulators/samsm300/device.py | 25 +++++++++++++++++++ .../samsm300/interfaces/__init__.py | 3 +++ .../samsm300/interfaces/stream_interface.py | 12 +++++++++ lewis_emulators/samsm300/states.py | 5 ++++ 5 files changed, 50 insertions(+) create mode 100644 lewis_emulators/samsm300/__init__.py create mode 100644 lewis_emulators/samsm300/device.py create mode 100644 lewis_emulators/samsm300/interfaces/__init__.py create mode 100644 lewis_emulators/samsm300/interfaces/stream_interface.py create mode 100644 lewis_emulators/samsm300/states.py diff --git a/lewis_emulators/samsm300/__init__.py b/lewis_emulators/samsm300/__init__.py new file mode 100644 index 00000000..9f2034a9 --- /dev/null +++ b/lewis_emulators/samsm300/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedSamsm300 +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedSamsm300'] diff --git a/lewis_emulators/samsm300/device.py b/lewis_emulators/samsm300/device.py new file mode 100644 index 00000000..f6a525e3 --- /dev/null +++ b/lewis_emulators/samsm300/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedSamsm300(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + pass + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + ]) + diff --git a/lewis_emulators/samsm300/interfaces/__init__.py b/lewis_emulators/samsm300/interfaces/__init__.py new file mode 100644 index 00000000..ce994ff4 --- /dev/null +++ b/lewis_emulators/samsm300/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Samsm300StreamInterface + +__all__ = ['Samsm300StreamInterface'] diff --git a/lewis_emulators/samsm300/interfaces/stream_interface.py b/lewis_emulators/samsm300/interfaces/stream_interface.py new file mode 100644 index 00000000..843df8a5 --- /dev/null +++ b/lewis_emulators/samsm300/interfaces/stream_interface.py @@ -0,0 +1,12 @@ +from lewis.adapters.stream import StreamInterface, Cmd + + +class Samsm300StreamInterface(StreamInterface): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + } + + def catch_all(self): + pass diff --git a/lewis_emulators/samsm300/states.py b/lewis_emulators/samsm300/states.py new file mode 100644 index 00000000..e4ca48e8 --- /dev/null +++ b/lewis_emulators/samsm300/states.py @@ -0,0 +1,5 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + pass From 54b9c12ff0e342aa3ec87c26bf840cf355d103a2 Mon Sep 17 00:00:00 2001 From: esouthren Date: Tue, 28 Nov 2017 10:24:05 +0000 Subject: [PATCH 0382/1466] neg ramping fix --- lewis_emulators/HFMAGPSU/states.py | 1 - lewis_emulators/HFMAGPSU/states.pyc | Bin 2123 -> 2123 bytes 2 files changed, 1 deletion(-) diff --git a/lewis_emulators/HFMAGPSU/states.py b/lewis_emulators/HFMAGPSU/states.py index 347e65d3..305abc02 100644 --- a/lewis_emulators/HFMAGPSU/states.py +++ b/lewis_emulators/HFMAGPSU/states.py @@ -45,4 +45,3 @@ def in_state(self, dt): - diff --git a/lewis_emulators/HFMAGPSU/states.pyc b/lewis_emulators/HFMAGPSU/states.pyc index 02d7e7c50dc9c8847729dff8c73655c4dd5e59af..2b603ac84271ba860ff86a0a3c640485ae011aff 100644 GIT binary patch delta 16 XcmX>ta9V(!`7@FMtF0=&R delta 16 XcmX>ta9V(!`7@FMtE;|J0 From 6918d073a4554a9af716a90569e5958349b58150 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Tue, 28 Nov 2017 13:57:36 +0000 Subject: [PATCH 0383/1466] Added command for setting frequency. Added error handling. --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 5 ++++- .../interfaces/stream_interface.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index 03f7dc37..c9b5fcd2 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -14,7 +14,8 @@ def _initialize_data(self): Sets the initial state of the device. """ self.chopper_name = "C01" - self.frequency_reference = 0 + # reference frequency set to 50Hz to match actual device + self.frequency_reference = 50 self.frequency_setpoint = 0 self.frequency = 0 self.phase_setpoint = 0 @@ -46,6 +47,8 @@ def _initialize_data(self): self.interlock_drive_amplifier_current_status = "OK" self.interlock_ups_status = "OK" + self.error_on_set_frequency = None + def _get_state_handlers(self): """ Returns: states and their names diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 30c42d5a..666c9c88 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -12,7 +12,8 @@ class FZJDDFCHStreamInterface(StreamInterface): commands = { CmdBuilder("get_magnetic_bearing_status").escape("MBON?").build(), - CmdBuilder("get_all_status").arg(".{3}").escape(";ASTA?").build() + CmdBuilder("get_all_status").arg(".{3}").escape(";ASTA?").build(), + CmdBuilder("set_frequency", arg_sep="").arg(".{3}").escape("!;FACT!;").int().build() } in_terminator = "\r\n" @@ -29,6 +30,17 @@ def handle_error(self, request, error): """ self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + def set_frequency(self, chopper_name, frequency): + + if self._device.error_on_set_frequency is None: + self._device.frequency_setpoint = int(frequency) * self._device.frequency_reference + reply = "{chopper_name}OK".format(chopper_name=chopper_name) + else: + reply = self._device.error_on_set_frequency + + self.log.info(reply) + return reply + def get_magnetic_bearing_status(self): """ From 5dd565572d2f262092721b20407fca6399297733 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 29 Nov 2017 11:17:11 +0000 Subject: [PATCH 0384/1466] Start emmulator --- lewis_emulators/samsm300/device.py | 25 -------- .../samsm300/interfaces/__init__.py | 3 - .../samsm300/interfaces/stream_interface.py | 12 ---- lewis_emulators/samsm300/states.py | 5 -- .../{samsm300 => sm300}/__init__.py | 4 +- lewis_emulators/sm300/device.py | 61 +++++++++++++++++++ lewis_emulators/sm300/interfaces/__init__.py | 3 + .../sm300/interfaces/stream_interface.py | 52 ++++++++++++++++ lewis_emulators/sm300/states.py | 8 +++ lewis_emulators/utils/command_builder.py | 10 +++ lewis_emulators/utils/constants.py | 10 +++ 11 files changed, 146 insertions(+), 47 deletions(-) delete mode 100644 lewis_emulators/samsm300/device.py delete mode 100644 lewis_emulators/samsm300/interfaces/__init__.py delete mode 100644 lewis_emulators/samsm300/interfaces/stream_interface.py delete mode 100644 lewis_emulators/samsm300/states.py rename lewis_emulators/{samsm300 => sm300}/__init__.py (52%) create mode 100644 lewis_emulators/sm300/device.py create mode 100644 lewis_emulators/sm300/interfaces/__init__.py create mode 100644 lewis_emulators/sm300/interfaces/stream_interface.py create mode 100644 lewis_emulators/sm300/states.py create mode 100644 lewis_emulators/utils/constants.py diff --git a/lewis_emulators/samsm300/device.py b/lewis_emulators/samsm300/device.py deleted file mode 100644 index f6a525e3..00000000 --- a/lewis_emulators/samsm300/device.py +++ /dev/null @@ -1,25 +0,0 @@ -from collections import OrderedDict -from states import DefaultState -from lewis.devices import StateMachineDevice - - -class SimulatedSamsm300(StateMachineDevice): - - def _initialize_data(self): - """ - Initialize all of the device's attributes. - """ - pass - - def _get_state_handlers(self): - return { - 'default': DefaultState(), - } - - def _get_initial_state(self): - return 'default' - - def _get_transition_handlers(self): - return OrderedDict([ - ]) - diff --git a/lewis_emulators/samsm300/interfaces/__init__.py b/lewis_emulators/samsm300/interfaces/__init__.py deleted file mode 100644 index ce994ff4..00000000 --- a/lewis_emulators/samsm300/interfaces/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .stream_interface import Samsm300StreamInterface - -__all__ = ['Samsm300StreamInterface'] diff --git a/lewis_emulators/samsm300/interfaces/stream_interface.py b/lewis_emulators/samsm300/interfaces/stream_interface.py deleted file mode 100644 index 843df8a5..00000000 --- a/lewis_emulators/samsm300/interfaces/stream_interface.py +++ /dev/null @@ -1,12 +0,0 @@ -from lewis.adapters.stream import StreamInterface, Cmd - - -class Samsm300StreamInterface(StreamInterface): - - # Commands that we expect via serial during normal operation - commands = { - Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging - } - - def catch_all(self): - pass diff --git a/lewis_emulators/samsm300/states.py b/lewis_emulators/samsm300/states.py deleted file mode 100644 index e4ca48e8..00000000 --- a/lewis_emulators/samsm300/states.py +++ /dev/null @@ -1,5 +0,0 @@ -from lewis.core.statemachine import State - - -class DefaultState(State): - pass diff --git a/lewis_emulators/samsm300/__init__.py b/lewis_emulators/sm300/__init__.py similarity index 52% rename from lewis_emulators/samsm300/__init__.py rename to lewis_emulators/sm300/__init__.py index 9f2034a9..25f45fe4 100644 --- a/lewis_emulators/samsm300/__init__.py +++ b/lewis_emulators/sm300/__init__.py @@ -1,5 +1,5 @@ -from .device import SimulatedSamsm300 +from .device import SimulatedSm300 from ..lewis_versions import LEWIS_LATEST framework_version = LEWIS_LATEST -__all__ = ['SimulatedSamsm300'] +__all__ = ['SimulatedSm300'] diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py new file mode 100644 index 00000000..d8e3e29a --- /dev/null +++ b/lewis_emulators/sm300/device.py @@ -0,0 +1,61 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice +from lewis.core import approaches + + +class Axis(): + def __init__(self): + self.rbv = 0 + self.sp = 0 + self.moving = False + self.speed = 0.1 + + def home(self): + self.sp = 0 + self.moving = True + + def simulate(self, dt): + self.rbv = approaches.linear(self.rbv, self.sp, self.speed, dt) + self.moving = self.rbv == self.sp + + +class SimulatedSm300(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + # Is the device initialised, if not it won't talk to me + self.initialised = False + + self.x_axis = Axis() + self.y_axis = Axis() + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + ]) + + @property + def x_axis_rbv(self): + return self.x_axis.rbv + + @x_axis_rbv.setter + def x_axis_rbv(self, position): + self.x_axis.rbv = position + + @property + def y_axis_rbv(self): + return self.y_axis.rbv + + @y_axis_rbv.setter + def y_axis_rbv(self, position): + self.y_axis.rbv = position diff --git a/lewis_emulators/sm300/interfaces/__init__.py b/lewis_emulators/sm300/interfaces/__init__.py new file mode 100644 index 00000000..94d7b2e0 --- /dev/null +++ b/lewis_emulators/sm300/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import Sm300StreamInterface + +__all__ = ['Sm300StreamInterface'] diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py new file mode 100644 index 00000000..774d2796 --- /dev/null +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -0,0 +1,52 @@ +from lewis.adapters.stream import StreamInterface +from lewis.core.logging import has_log + +from lewis_emulators.utils.command_builder import CmdBuilder +from lewis_emulators.utils.constants import ACK, STX, EOT, COMMAND_CHARS + + +@has_log +class Sm300StreamInterface(StreamInterface): + + out_terminator = EOT + + def __init__(self): + + super(Sm300StreamInterface, self).__init__() + + # Commands that we expect via serial during normal operation + self.commands = { + CmdBuilder(self.get_position).escape("LQ").build(), # Catch-all command for debugging + CmdBuilder(self.home_axis).escape("BR").char().build(), # Catch-all command for debugging + CmdBuilder(self.get_status).escape("LM").char().build(), # Catch-all command for debugging + } + + self.device = self._device + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def get_position(self): + return "{ACK}{STX}X{0:d.0},Y{1:d.0}".format(self.device.x_axis.rbv, self.device.y_axis.rbv, **COMMAND_CHARS) + + def home_axis(self, axis): + if axis == "X": + self.device.x_axis.home() + elif axis == "Y": + self.device.y_axis.home() + return "{ACK}" + + def get_status(self): + if self.device.x_axis.moving or self.device.y_axis.moving: + status = "N" + else: + status = "P" + return "{ACK}{STX}{status}".format(status=status, **COMMAND_CHARS) diff --git a/lewis_emulators/sm300/states.py b/lewis_emulators/sm300/states.py new file mode 100644 index 00000000..38aafdfe --- /dev/null +++ b/lewis_emulators/sm300/states.py @@ -0,0 +1,8 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + def in_state(self, dt): + device = self._context + device.x_axis.simulate(dt) + device.y_axis.simulate(dt) diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index b321730b..e4781a47 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -51,6 +51,16 @@ def escape(self, text): self._reg_ex += re.escape(text) + self._ignore return self + def regex(self, regex): + """ + Add a regex to match but not as an argument. + + :param regex: regex to add + :return: builder + """ + self._reg_ex += regex + self._ignore + return self + def arg(self, arg_regex): """ Add an argument to the command. diff --git a/lewis_emulators/utils/constants.py b/lewis_emulators/utils/constants.py new file mode 100644 index 00000000..3f634cc4 --- /dev/null +++ b/lewis_emulators/utils/constants.py @@ -0,0 +1,10 @@ + + +STX = chr(2) +ACK = chr(6) +EOT = chr(4) + +COMMAND_CHARS = { + "STX": STX, + "ACK": ACK, + "EOT": EOT} From 196d55625190f3c31ef3ad950e71bf645b9b52f5 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Wed, 29 Nov 2017 12:34:59 +0000 Subject: [PATCH 0385/1466] Added support for remaining set commands --- .../fzj_dd_fermi_chopper/device.py | 5 ++- .../interfaces/stream_interface.py | 40 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index c9b5fcd2..fe115d10 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -25,7 +25,7 @@ def _initialize_data(self): self.magnetic_bearing_status = "OK" self.magnetic_bearing_integrator = 0 self.drive = "OFF" - self.drive_status = "START" + self.drive_mode = "START" self.drive_l1_current = 0 self.drive_l2_current = 0 self.drive_l3_current = 0 @@ -48,6 +48,9 @@ def _initialize_data(self): self.interlock_ups_status = "OK" self.error_on_set_frequency = None + self.error_on_set_phase = None + self.error_on_set_magnetic_bearing = None + self.error_on_set_drive_mode = None def _get_state_handlers(self): """ diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 666c9c88..17019394 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -13,7 +13,10 @@ class FZJDDFCHStreamInterface(StreamInterface): commands = { CmdBuilder("get_magnetic_bearing_status").escape("MBON?").build(), CmdBuilder("get_all_status").arg(".{3}").escape(";ASTA?").build(), - CmdBuilder("set_frequency", arg_sep="").arg(".{3}").escape("!;FACT!;").int().build() + CmdBuilder("set_frequency", arg_sep="").arg(".{3}").escape("!;FACT!;").int().build(), + CmdBuilder("set_phase", arg_sep="").arg(".{3}").escape("!;PHAS!;").float().build(), + CmdBuilder("set_magnetic_bearing", arg_sep="").arg(".{3}").escape("!;MAGB!;").any().build(), + CmdBuilder("set_drive_mode", arg_sep="").arg(".{3}").escape("!;DRIV!;").any().build() } in_terminator = "\r\n" @@ -41,6 +44,39 @@ def set_frequency(self, chopper_name, frequency): self.log.info(reply) return reply + def set_phase(self, chopper_name, phase): + + if self._device.error_on_set_phase is None: + self._device.phase_setpoint = float(phase) + reply = "{chopper_name}OK".format(chopper_name=chopper_name) + else: + reply = self._device.error_on_set_phase + + self.log.info(reply) + return reply + + def set_magnetic_bearing(self, chopper_name, magnetic_bearing): + + if self._device.error_on_set_magnetic_bearing is None: + self._device.magnetic_bearing = magnetic_bearing + reply = "{chopper_name}OK".format(chopper_name=chopper_name) + else: + reply = self._device.error_on_set_magnetic_bearing + + self.log.info(reply) + return reply + + def set_drive_mode(self, chopper_name, drive_mode): + + if self._device.error_on_set_drive_mode is None: + self._device.drive_mode = drive_mode + reply = "{chopper_name}OK".format(chopper_name=chopper_name) + else: + reply = self._device.error_on_set_drive_mode + + self.log.info(reply) + return reply + def get_magnetic_bearing_status(self): """ @@ -80,7 +116,7 @@ def get_all_status(self, chopper_name): "{0:s}".format(device.magnetic_bearing_status), "{0:.1f}".format(device.magnetic_bearing_integrator), "{0:s}".format(device.drive), - "{0:s}".format(device.drive_status), + "{0:s}".format(device.drive_mode), "{0:.2f}".format(device.drive_l1_current), "{0:.2f}".format(device.drive_l2_current), "{0:.2f}".format(device.drive_l3_current), From 0a920bb13b4c87ecf86e79ff4d11f7c8e6c4dcb6 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Thu, 30 Nov 2017 09:39:13 +0000 Subject: [PATCH 0386/1466] blank error successfully --- .../fzj_dd_fermi_chopper/interfaces/stream_interface.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 17019394..39defdfd 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -39,7 +39,7 @@ def set_frequency(self, chopper_name, frequency): self._device.frequency_setpoint = int(frequency) * self._device.frequency_reference reply = "{chopper_name}OK".format(chopper_name=chopper_name) else: - reply = self._device.error_on_set_frequency + reply = "ERROR;{}".format(self._device.error_on_set_frequency) self.log.info(reply) return reply @@ -50,7 +50,7 @@ def set_phase(self, chopper_name, phase): self._device.phase_setpoint = float(phase) reply = "{chopper_name}OK".format(chopper_name=chopper_name) else: - reply = self._device.error_on_set_phase + reply = "ERROR;{}".format(self._device.error_on_set_phase) self.log.info(reply) return reply @@ -61,7 +61,7 @@ def set_magnetic_bearing(self, chopper_name, magnetic_bearing): self._device.magnetic_bearing = magnetic_bearing reply = "{chopper_name}OK".format(chopper_name=chopper_name) else: - reply = self._device.error_on_set_magnetic_bearing + reply = "ERROR;{}".format(self._device.error_on_set_magnetic_bearing) self.log.info(reply) return reply @@ -72,7 +72,7 @@ def set_drive_mode(self, chopper_name, drive_mode): self._device.drive_mode = drive_mode reply = "{chopper_name}OK".format(chopper_name=chopper_name) else: - reply = self._device.error_on_set_drive_mode + reply = "ERROR;{}".format(self._device.error_on_set_drive_mode) self.log.info(reply) return reply From 0f2e940ac6021ebcf926f57d75856d2b49c566b8 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 10:00:36 +0000 Subject: [PATCH 0387/1466] Add template emulator --- lewis_emulators/gemorc/__init__.py | 5 ++++ lewis_emulators/gemorc/device.py | 25 +++++++++++++++++++ lewis_emulators/gemorc/interfaces/__init__.py | 3 +++ .../gemorc/interfaces/stream_interface.py | 12 +++++++++ lewis_emulators/gemorc/states.py | 5 ++++ 5 files changed, 50 insertions(+) create mode 100644 lewis_emulators/gemorc/__init__.py create mode 100644 lewis_emulators/gemorc/device.py create mode 100644 lewis_emulators/gemorc/interfaces/__init__.py create mode 100644 lewis_emulators/gemorc/interfaces/stream_interface.py create mode 100644 lewis_emulators/gemorc/states.py diff --git a/lewis_emulators/gemorc/__init__.py b/lewis_emulators/gemorc/__init__.py new file mode 100644 index 00000000..dc5682c4 --- /dev/null +++ b/lewis_emulators/gemorc/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedGemorc +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedGemorc'] diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py new file mode 100644 index 00000000..653e4246 --- /dev/null +++ b/lewis_emulators/gemorc/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedGemorc(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + pass + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + ]) + diff --git a/lewis_emulators/gemorc/interfaces/__init__.py b/lewis_emulators/gemorc/interfaces/__init__.py new file mode 100644 index 00000000..132bd198 --- /dev/null +++ b/lewis_emulators/gemorc/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import GemorcStreamInterface + +__all__ = ['GemorcStreamInterface'] diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py new file mode 100644 index 00000000..44d95e5f --- /dev/null +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -0,0 +1,12 @@ +from lewis.adapters.stream import StreamInterface, Cmd + + +class GemorcStreamInterface(StreamInterface): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + } + + def catch_all(self): + pass diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py new file mode 100644 index 00000000..e4ca48e8 --- /dev/null +++ b/lewis_emulators/gemorc/states.py @@ -0,0 +1,5 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + pass From 9b6e443ccf7a7e61ab411ddc79ee01a82bbfcf29 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 11:25:15 +0000 Subject: [PATCH 0388/1466] Add basic commands --- .../gemorc/interfaces/stream_interface.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index 44d95e5f..c040ec5b 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -1,12 +1,32 @@ from lewis.adapters.stream import StreamInterface, Cmd +from lewis.core.logging import has_log +from lewis_emulators.utils.command_builder import CmdBuilder - +@has_log class GemorcStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + CmdBuilder("get_id").escape("id").build(), + CmdBuilder("initialise").escape("in").build(), + CmdBuilder("rezero_to_datum").escape("da").build(), + CmdBuilder("start").escape("st").build(), + CmdBuilder("stop").escape("sp").build(), + CmdBuilder("stop_next_initialisation").escape("si").build(), + CmdBuilder("set_window_width").escape("ww").int().build(), + CmdBuilder("set_offset_from_datum").escape("of").int().build(), + CmdBuilder("set_radial_speed").escape("ds").int().build(), + CmdBuilder("set_radial_acceleration").escape("ac").int().build(), + CmdBuilder("get_status").escape("rq"), } - def catch_all(self): - pass + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) \ No newline at end of file From 94a8813ff3f5a6bfa9e8095c30f078575319463b Mon Sep 17 00:00:00 2001 From: Eilidh Date: Thu, 30 Nov 2017 12:06:28 +0000 Subject: [PATCH 0389/1466] post demo, pre rewrite --- lewis_emulators/HFMAGPSU/__init__.pyc | Bin 369 -> 369 bytes lewis_emulators/HFMAGPSU/device.pyc | Bin 9586 -> 9586 bytes .../HFMAGPSU/interfaces/__init__.pyc | Bin 297 -> 297 bytes .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 11572 -> 11572 bytes lewis_emulators/HFMAGPSU/states.pyc | Bin 2123 -> 2123 bytes 5 files changed, 0 insertions(+), 0 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/__init__.pyc b/lewis_emulators/HFMAGPSU/__init__.pyc index ae43f25087362963db7b4958fcd969c36379fef2..ac06ed3abc1185a218419d92975879c0421d732c 100644 GIT binary patch delta 16 Xcmey!^pT03`7{*NeGu{P_ delta 16 Xcmey!^pT03`7VM&l?AfXSI&20E diff --git a/lewis_emulators/HFMAGPSU/interfaces/__init__.pyc b/lewis_emulators/HFMAGPSU/interfaces/__init__.pyc index 10742605e1c554f51d4479ba1086fe15821746c5..922844642bb83e12e7491b7c71283bff70652e47 100644 GIT binary patch delta 17 YcmZ3ta9V(!`7@FMtFti0f delta 16 XcmX>ta9V(!`7@FMtE;|J0 From c0a6731c0a6b4afe81812e4678d006820d8124c7 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 13:45:30 +0000 Subject: [PATCH 0390/1466] Add basic interface --- .../gemorc/interfaces/stream_interface.py | 111 +++++++++++++++++- 1 file changed, 106 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index c040ec5b..4b0c6cae 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -1,4 +1,4 @@ -from lewis.adapters.stream import StreamInterface, Cmd +from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder @@ -9,14 +9,14 @@ class GemorcStreamInterface(StreamInterface): commands = { CmdBuilder("get_id").escape("id").build(), CmdBuilder("initialise").escape("in").build(), - CmdBuilder("rezero_to_datum").escape("da").build(), + CmdBuilder("re_zero_to_datum").escape("da").build(), CmdBuilder("start").escape("st").build(), CmdBuilder("stop").escape("sp").build(), CmdBuilder("stop_next_initialisation").escape("si").build(), CmdBuilder("set_window_width").escape("ww").int().build(), CmdBuilder("set_offset_from_datum").escape("of").int().build(), - CmdBuilder("set_radial_speed").escape("ds").int().build(), - CmdBuilder("set_radial_acceleration").escape("ac").int().build(), + CmdBuilder("set_speed").escape("ds").int().build(), + CmdBuilder("set_acceleration").escape("ac").int().build(), CmdBuilder("get_status").escape("rq"), } @@ -29,4 +29,105 @@ def handle_error(self, request, error): error: problem """ - self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) \ No newline at end of file + self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) + + def get_id(self): + """ + Returns: + Device's PnP identity + """ + return "IBEX_GEMORC_DEVICE_EMULATOR" + + def initialise(self): + """ + Initialise the collimator after 1 turn rotation + """ + self.device.initialise() + return "inok" + + def re_zero_to_datum(self): + """ + Re-zero position from datum marker + """ + self.device.initialise() + return "daok" + + def start(self): + """ + Start motion + """ + self.device.start() + return "stok" + + def stop(self): + """ + Stop motion + """ + self.device.stop() + return "spok" + + def stop_next_initialisation(self): + """ + Stop motion after next auto-initialisation + """ + self.device.stop_next_initialisation() + return "siok" + + def set_window_width(self, width): + """ + Sets the window width + + Args: + width: Width in centi-degrees + """ + self.device.set_window_width(int(width)/100.0) + return "wnok" + + def set_offset_from_datum(self, offset): + """ + Sets the offset position from datum to match axis (signed) + + Args: + offset: Datum offset in centi-degrees + """ + self.device.set_offset_from_datum(int(offset)/100.0) + return "ofok" + + def set_acceleration(self, acceleration): + """ + Sets the acceleration rate for reversals + + Args: + acceleration: Rate of acceleration in centi-degrees per second^2 + """ + self.device.set_acceleration(int(acceleration)/100.0) + return "acok" + + def set_speed(self, speed): + """ + Sets the radial speed + + Args: + speed: Speed in centi-degrees per second + """ + self.device.set_speed(int(speed)/100.0) + return "dsok" + + def get_status(self): + """ + Get the current state of the collimator. This is backwards engineered from the VI. I haven't actually seen + what the real thing returns as the VI code doesn't match the spec + """ + request_format = "{oscillating:1d}....{initialising:1d}....{initialised:1d}.....{width:3d}......{offset:4d}" \ + "......{speed:2d}.....{acceleration:3d}..{cycles:5d}..........{backlash:3d}" + return request_format.format( + oscillating=int(self.device.is_oscillating()), + initialising=int(self.device.is_initialising()), + initialised=int(self.device.has_been_initialised()), + width=self.device.get_window_width(), + offset=self.device.get_offset(), + speed=self.device.get_speed(), + acceleration=self.device.get_acceleration(), + cycles=self.device.get_complete_cycles(), + backlash=self.device.get_backlash() + ) From 729ef5b7b223f3831f82bde04aa3dd0abefedbec Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Thu, 30 Nov 2017 13:54:19 +0000 Subject: [PATCH 0391/1466] Error if main pv becomes disconnected --- lewis_emulators/fzj_dd_fermi_chopper/device.py | 2 ++ .../interfaces/stream_interface.py | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/device.py b/lewis_emulators/fzj_dd_fermi_chopper/device.py index fe115d10..41a56762 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/device.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/device.py @@ -52,6 +52,8 @@ def _initialize_data(self): self.error_on_set_magnetic_bearing = None self.error_on_set_drive_mode = None + self.disconnected = False + def _get_state_handlers(self): """ Returns: states and their names diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index 39defdfd..ee9fe4c4 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -34,7 +34,8 @@ def handle_error(self, request, error): self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) def set_frequency(self, chopper_name, frequency): - + if self._device.disconnected: + return None if self._device.error_on_set_frequency is None: self._device.frequency_setpoint = int(frequency) * self._device.frequency_reference reply = "{chopper_name}OK".format(chopper_name=chopper_name) @@ -45,7 +46,8 @@ def set_frequency(self, chopper_name, frequency): return reply def set_phase(self, chopper_name, phase): - + if self._device.disconnected: + return None if self._device.error_on_set_phase is None: self._device.phase_setpoint = float(phase) reply = "{chopper_name}OK".format(chopper_name=chopper_name) @@ -56,7 +58,8 @@ def set_phase(self, chopper_name, phase): return reply def set_magnetic_bearing(self, chopper_name, magnetic_bearing): - + if self._device.disconnected: + return None if self._device.error_on_set_magnetic_bearing is None: self._device.magnetic_bearing = magnetic_bearing reply = "{chopper_name}OK".format(chopper_name=chopper_name) @@ -67,7 +70,8 @@ def set_magnetic_bearing(self, chopper_name, magnetic_bearing): return reply def set_drive_mode(self, chopper_name, drive_mode): - + if self._device.disconnected: + return None if self._device.error_on_set_drive_mode is None: self._device.drive_mode = drive_mode reply = "{chopper_name}OK".format(chopper_name=chopper_name) @@ -86,10 +90,8 @@ def get_magnetic_bearing_status(self): Returns: """ - pass - # return self._device.magnetic_bearing_status - return + return self._device.magnetic_bearing_status def get_all_status(self, chopper_name): @@ -101,7 +103,7 @@ def get_all_status(self, chopper_name): """ device = self._device - if chopper_name != device.chopper_name: + if self._device.disconnected or chopper_name != device.chopper_name: return None drive_direction = "CLOCK" if device.is_drive_direction_clockwise else "ANTICLOCK" values = [ From 39e061db7a2eaad19cc7d74e068d65c41f0e19a7 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 14:57:26 +0000 Subject: [PATCH 0392/1466] Add basic state properties --- lewis_emulators/gemorc/device.py | 96 ++++++++++++++++++- .../gemorc/interfaces/stream_interface.py | 6 +- lewis_emulators/gemorc/states.py | 28 +++++- 3 files changed, 121 insertions(+), 9 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 653e4246..3313e7f0 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import DefaultState +from states import StoppedState, OscillatingState, InitialisingState, IdleState from lewis.devices import StateMachineDevice @@ -9,17 +9,105 @@ def _initialize_data(self): """ Initialize all of the device's attributes. """ - pass + + # State control variables + self.time_to_initialise = 10.0 + self.time_spent_initialising = 0.0 + self.initialisation_requested = False + self.start_requested = True + self.state = None # Controlled by state handler + self.stop_initialisation = False + + self.initialisation_cycle = 20000 + self.optional_initialisation_cycle = 10000 + self.complete_cycles = 0 + + # Device settings + self.window_width = 1.0 + self.acceleration = 1.0 + self.max_speed = 1.0 + self.speed = 0.0 + self.offset = 0.0 + self.backlash = 1.0 def _get_state_handlers(self): return { - 'default': DefaultState(), + 'initialising': InitialisingState(), + 'oscillating': OscillatingState(), + 'stopped': StoppedState(), + 'idle': IdleState(), } def _get_initial_state(self): - return 'default' + return 'idle' def _get_transition_handlers(self): return OrderedDict([ + (('idle', 'initialising'), lambda: self.initialisation_requested), + + (('initialising', 'initialised'), lambda: self.stop_initialisation_requested or + self.time_spent_initialising >= self.time_to_initialise), + + (('initialised', 'oscillating'), lambda: self.start_requested), + (('initialised', 'initialising'), lambda: self.initialisation_requested), + + (('oscillating', 'stopped'), lambda: not self.start_requested), + (('oscillating', 'initialising'), lambda: self.complete_cycles % self.initialisation_cycle == 0), + + (('stopped', 'initialising'), lambda: self.initialisation_requested), + (('stopped', 'oscillating'), lambda: self.start_requested), ]) + def initialise(self): + self.initialisation_requested = True + + def re_zero_to_datum(self): + raise NotImplementedError() + + def start(self): + self.start_requested = True + + def stop(self): + self.start_requested = False + + def stop_next_initialisation(self): + self.stop_initialisation = True + + def get_window_width(self): + return self.window_width + + def set_window_width(self, width): + self.window_width = width + + def get_offset(self): + return self.offset + + def set_offset(self, offset): + self.offset = offset + + def get_acceleration(self): + return self.acceleration + + def set_acceleration(self, acceleration): + self.acceleration = acceleration + + def get_speed(self): + return self.speed + + def set_speed(self, speed): + self.max_speed = speed + + def is_oscillating(self): + return self.state == OscillatingState.__name__ + + def is_initialising(self): + return self.state == InitialisingState.__name__ + + def has_been_initialised(self): + return self.state != IdleState.__name__ + + def get_complete_cycles(self): + return self.complete_cycles + + def get_backlash(self): + return self.backlash \ No newline at end of file diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index 4b0c6cae..6193678e 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -14,7 +14,7 @@ class GemorcStreamInterface(StreamInterface): CmdBuilder("stop").escape("sp").build(), CmdBuilder("stop_next_initialisation").escape("si").build(), CmdBuilder("set_window_width").escape("ww").int().build(), - CmdBuilder("set_offset_from_datum").escape("of").int().build(), + CmdBuilder("set_offset").escape("of").int().build(), CmdBuilder("set_speed").escape("ds").int().build(), CmdBuilder("set_acceleration").escape("ac").int().build(), CmdBuilder("get_status").escape("rq"), @@ -83,14 +83,14 @@ def set_window_width(self, width): self.device.set_window_width(int(width)/100.0) return "wnok" - def set_offset_from_datum(self, offset): + def set_offset(self, offset): """ Sets the offset position from datum to match axis (signed) Args: offset: Datum offset in centi-degrees """ - self.device.set_offset_from_datum(int(offset)/100.0) + self.device.set_offset(int(offset)/100.0) return "ofok" def set_acceleration(self, acceleration): diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index e4ca48e8..d5b38be7 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -1,5 +1,29 @@ from lewis.core.statemachine import State -class DefaultState(State): - pass +class StoppedState(State): + + def on_entry(self, dt): + self._context.state = StoppedState.__name__ + + +class OscillatingState(State): + + def on_entry(self, dt): + self._context.state = OscillatingState.__name__ + + +class IdleState(State): + + def on_entry(self, dt): + self._context.state = OscillatingState.__name__ + + +class InitialisingState(State): + + def on_entry(self, dt): + self._context.state = InitialisingState.__name__ + self._context.initialisation_requested = False + + def on_exit(self, dt): + self._context.stop_initialisation = False From cedf7881187893aea696184c8a30e7ac33a4c141 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 15:27:58 +0000 Subject: [PATCH 0393/1466] Add oscillation logic --- lewis_emulators/gemorc/device.py | 7 +++--- lewis_emulators/gemorc/states.py | 43 +++++++++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 3313e7f0..81b024aa 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -2,6 +2,7 @@ from states import StoppedState, OscillatingState, InitialisingState, IdleState from lewis.devices import StateMachineDevice +SMALL = 1.0e-10 class SimulatedGemorc(StateMachineDevice): @@ -25,7 +26,7 @@ def _initialize_data(self): # Device settings self.window_width = 1.0 self.acceleration = 1.0 - self.max_speed = 1.0 + self.target_speed = 1.0 self.speed = 0.0 self.offset = 0.0 self.backlash = 1.0 @@ -51,7 +52,7 @@ def _get_transition_handlers(self): (('initialised', 'oscillating'), lambda: self.start_requested), (('initialised', 'initialising'), lambda: self.initialisation_requested), - (('oscillating', 'stopped'), lambda: not self.start_requested), + (('oscillating', 'stopped'), lambda: not self.speed < SMALL), (('oscillating', 'initialising'), lambda: self.complete_cycles % self.initialisation_cycle == 0), (('stopped', 'initialising'), lambda: self.initialisation_requested), @@ -95,7 +96,7 @@ def get_speed(self): return self.speed def set_speed(self, speed): - self.max_speed = speed + self.target_speed = speed def is_oscillating(self): return self.state == OscillatingState.__name__ diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index d5b38be7..0f8ef5e6 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -10,7 +10,45 @@ def on_entry(self, dt): class OscillatingState(State): def on_entry(self, dt): - self._context.state = OscillatingState.__name__ + device = self._context + device.state = OscillatingState.__name__ + self.time = 0.0 + + def in_state(self, dt): + self.time += dt + dev = self._context + + def calculate_speed(time, window_width, target_speed, acceleration): + transition_time = target_speed/acceleration + total_cycle_time = 2*transition_time + window_width + cycle_time = time % total_cycle_time + + spinning_up = cycle_time < transition_time + spinning_down = cycle_time > window_width + transition_time + + if spinning_up: + current_speed = target_speed*cycle_time/transition_time + elif spinning_down: + current_speed = target_speed*(total_cycle_time - cycle_time)/transition_time + else: + current_speed = target_speed + + return current_speed + + dev.speed = calculate_speed(self.time, dev.window_width, dev.target_speed, dev.acceleration) + + def cycle_counter(speed, acceleration, dt): + was_in_motion = False + while True: + tolerance = 2*dt*acceleration/speed # 2 normalised speed steps + in_motion = speed > tolerance + if not in_motion and was_in_motion: + was_in_motion = False + yield 1 + else: + yield 0 + + dev.complete_cycles += cycle_counter(dev.target_speed, dev.acceleration, dt) class IdleState(State): @@ -25,5 +63,8 @@ def on_entry(self, dt): self._context.state = InitialisingState.__name__ self._context.initialisation_requested = False + def in_state(self, dt): + self._context.time_spent_initialising += dt + def on_exit(self, dt): self._context.stop_initialisation = False From d91c3b6c90ad8dd53335909732056721d1c9da93 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 16:42:31 +0000 Subject: [PATCH 0394/1466] Correct behaviour up to oscillation --- lewis_emulators/gemorc/device.py | 23 +++--- .../gemorc/interfaces/stream_interface.py | 29 +++---- lewis_emulators/gemorc/states.py | 76 +++++++++++-------- 3 files changed, 73 insertions(+), 55 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 81b024aa..8002a147 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import StoppedState, OscillatingState, InitialisingState, IdleState +from states import StoppedState, OscillatingState, InitialisingState, IdleState, InitialisedState from lewis.devices import StateMachineDevice SMALL = 1.0e-10 @@ -12,10 +12,10 @@ def _initialize_data(self): """ # State control variables - self.time_to_initialise = 10.0 + self.time_to_initialise = 2.0 self.time_spent_initialising = 0.0 self.initialisation_requested = False - self.start_requested = True + self.start_requested = False self.state = None # Controlled by state handler self.stop_initialisation = False @@ -34,6 +34,7 @@ def _initialize_data(self): def _get_state_handlers(self): return { 'initialising': InitialisingState(), + 'initialised': InitialisedState(), 'oscillating': OscillatingState(), 'stopped': StoppedState(), 'idle': IdleState(), @@ -46,17 +47,17 @@ def _get_transition_handlers(self): return OrderedDict([ (('idle', 'initialising'), lambda: self.initialisation_requested), - (('initialising', 'initialised'), lambda: self.stop_initialisation_requested or - self.time_spent_initialising >= self.time_to_initialise), + (('initialising', 'initialised'), lambda: self.time_spent_initialising >= self.time_to_initialise), (('initialised', 'oscillating'), lambda: self.start_requested), (('initialised', 'initialising'), lambda: self.initialisation_requested), - (('oscillating', 'stopped'), lambda: not self.speed < SMALL), - (('oscillating', 'initialising'), lambda: self.complete_cycles % self.initialisation_cycle == 0), - - (('stopped', 'initialising'), lambda: self.initialisation_requested), - (('stopped', 'oscillating'), lambda: self.start_requested), + (('oscillating', 'stopped'), lambda: not self.start_requested), + # (('oscillating', 'initialising'), lambda: self.complete_cycles > 0 and + # self.complete_cycles % self.initialisation_cycle == 0), + # + # (('stopped', 'initialising'), lambda: self.initialisation_requested), + # (('stopped', 'oscillating'), lambda: self.start_requested), ]) def initialise(self): @@ -105,7 +106,7 @@ def is_initialising(self): return self.state == InitialisingState.__name__ def has_been_initialised(self): - return self.state != IdleState.__name__ + return self.state not in (IdleState.__name__, InitialisingState.__name__) def get_complete_cycles(self): return self.complete_cycles diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index 6193678e..aeae6fc3 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -17,9 +17,12 @@ class GemorcStreamInterface(StreamInterface): CmdBuilder("set_offset").escape("of").int().build(), CmdBuilder("set_speed").escape("ds").int().build(), CmdBuilder("set_acceleration").escape("ac").int().build(), - CmdBuilder("get_status").escape("rq"), + CmdBuilder("get_status").escape("rq").build(), } + in_terminator = "\r\n" + out_terminator = "\r\n" + def handle_error(self, request, error): """ If command is not recognised print and error @@ -80,7 +83,7 @@ def set_window_width(self, width): Args: width: Width in centi-degrees """ - self.device.set_window_width(int(width)/100.0) + self.device.set_window_width(int(width)) return "wnok" def set_offset(self, offset): @@ -90,7 +93,7 @@ def set_offset(self, offset): Args: offset: Datum offset in centi-degrees """ - self.device.set_offset(int(offset)/100.0) + self.device.set_offset(int(offset)) return "ofok" def set_acceleration(self, acceleration): @@ -100,7 +103,7 @@ def set_acceleration(self, acceleration): Args: acceleration: Rate of acceleration in centi-degrees per second^2 """ - self.device.set_acceleration(int(acceleration)/100.0) + self.device.set_acceleration(int(acceleration)) return "acok" def set_speed(self, speed): @@ -110,7 +113,7 @@ def set_speed(self, speed): Args: speed: Speed in centi-degrees per second """ - self.device.set_speed(int(speed)/100.0) + self.device.set_speed(int(speed)) return "dsok" def get_status(self): @@ -118,16 +121,16 @@ def get_status(self): Get the current state of the collimator. This is backwards engineered from the VI. I haven't actually seen what the real thing returns as the VI code doesn't match the spec """ - request_format = "{oscillating:1d}....{initialising:1d}....{initialised:1d}.....{width:3d}......{offset:4d}" \ - "......{speed:2d}.....{acceleration:3d}..{cycles:5d}..........{backlash:3d}" + request_format = "{oscillating:01d}....{initialising:01d}....{initialised:01d}.....{width:03d}......" \ + "{offset:04d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" return request_format.format( oscillating=int(self.device.is_oscillating()), initialising=int(self.device.is_initialising()), initialised=int(self.device.has_been_initialised()), - width=self.device.get_window_width(), - offset=self.device.get_offset(), - speed=self.device.get_speed(), - acceleration=self.device.get_acceleration(), - cycles=self.device.get_complete_cycles(), - backlash=self.device.get_backlash() + width=int(self.device.get_window_width()), + offset=int(self.device.get_offset()), + speed=int(self.device.get_speed()), + acceleration=int(self.device.get_acceleration()), + cycles=int(self.device.get_complete_cycles()), + backlash=int(self.device.get_backlash()) ) diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index 0f8ef5e6..6212108a 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -7,54 +7,68 @@ def on_entry(self, dt): self._context.state = StoppedState.__name__ +class IdleState(State): + + def on_entry(self, dt): + self._context.state = IdleState.__name__ + + +class InitialisedState(State): + + def on_entry(self, dt): + self._context.state = InitialisedState.__name__ + + class OscillatingState(State): def on_entry(self, dt): - device = self._context - device.state = OscillatingState.__name__ + dev = self._context + dev.state = OscillatingState.__name__ self.time = 0.0 + self.target_reached = False + + @staticmethod + def calculate_speed(time, window_width, target_speed, acceleration): + transition_time = target_speed/acceleration + total_cycle_time = 2*transition_time + window_width + cycle_time = time % total_cycle_time + + spinning_up = cycle_time < transition_time + spinning_down = cycle_time > window_width + transition_time + + if spinning_up: + current_speed = target_speed * cycle_time / transition_time + elif spinning_down: + current_speed = target_speed * (total_cycle_time - cycle_time) / transition_time + else: + current_speed = target_speed + return current_speed def in_state(self, dt): self.time += dt dev = self._context - def calculate_speed(time, window_width, target_speed, acceleration): - transition_time = target_speed/acceleration - total_cycle_time = 2*transition_time + window_width - cycle_time = time % total_cycle_time - - spinning_up = cycle_time < transition_time - spinning_down = cycle_time > window_width + transition_time + dev.speed = OscillatingState.calculate_speed(self.time, dev.window_width, dev.target_speed, dev.acceleration) - if spinning_up: - current_speed = target_speed*cycle_time/transition_time - elif spinning_down: - current_speed = target_speed*(total_cycle_time - cycle_time)/transition_time - else: - current_speed = target_speed + # Increment the cycle counter if needed. Important to only do this once per cycle + # Has the cycle hit its target speed this cycle + self.target_reached = self.target_reached or dev.speed == dev.target_speed + in_motion = dev.speed/(dev.acceleration*dt) > 2 # 2 dimensionless speed increments + if not in_motion and self.target_reached: + dev.complete_cycles += 1 + self.target_reached = False - return current_speed - dev.speed = calculate_speed(self.time, dev.window_width, dev.target_speed, dev.acceleration) - - def cycle_counter(speed, acceleration, dt): - was_in_motion = False - while True: - tolerance = 2*dt*acceleration/speed # 2 normalised speed steps - in_motion = speed > tolerance - if not in_motion and was_in_motion: - was_in_motion = False - yield 1 - else: - yield 0 +class IdleState(State): - dev.complete_cycles += cycle_counter(dev.target_speed, dev.acceleration, dt) + def on_entry(self, dt): + self._context.state = IdleState.__name__ -class IdleState(State): +class InitialisedState(State): def on_entry(self, dt): - self._context.state = OscillatingState.__name__ + self._context.state = InitialisedState.__name__ class InitialisingState(State): From 69194a0341f44bad8521db2f5be345e5c397c303 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 16:43:33 +0000 Subject: [PATCH 0395/1466] Set some default parameters as in VI --- lewis_emulators/gemorc/device.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 8002a147..9ad94ccc 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -24,12 +24,12 @@ def _initialize_data(self): self.complete_cycles = 0 # Device settings - self.window_width = 1.0 - self.acceleration = 1.0 - self.target_speed = 1.0 - self.speed = 0.0 - self.offset = 0.0 - self.backlash = 1.0 + self.window_width = 100 + self.acceleration = 500 + self.target_speed = 20 + self.speed = 0 + self.offset = 0 + self.backlash = 10 def _get_state_handlers(self): return { From a2c434cd17e91a4e42c03605539b4404eb71bbe7 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 16:49:11 +0000 Subject: [PATCH 0396/1466] Reset initialisation time once initialisation complete --- lewis_emulators/gemorc/device.py | 14 +++++++------- lewis_emulators/gemorc/states.py | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 9ad94ccc..11a18f56 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -19,8 +19,8 @@ def _initialize_data(self): self.state = None # Controlled by state handler self.stop_initialisation = False - self.initialisation_cycle = 20000 - self.optional_initialisation_cycle = 10000 + self.initialisation_cycle = 100 + self.optional_initialisation_cycle = 50 self.complete_cycles = 0 # Device settings @@ -53,11 +53,11 @@ def _get_transition_handlers(self): (('initialised', 'initialising'), lambda: self.initialisation_requested), (('oscillating', 'stopped'), lambda: not self.start_requested), - # (('oscillating', 'initialising'), lambda: self.complete_cycles > 0 and - # self.complete_cycles % self.initialisation_cycle == 0), - # - # (('stopped', 'initialising'), lambda: self.initialisation_requested), - # (('stopped', 'oscillating'), lambda: self.start_requested), + (('oscillating', 'initialising'), lambda: self.complete_cycles > 0 and + self.complete_cycles % self.initialisation_cycle == 0), + + (('stopped', 'initialising'), lambda: self.initialisation_requested), + (('stopped', 'oscillating'), lambda: self.start_requested), ]) def initialise(self): diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index 6212108a..5cf83945 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -82,3 +82,4 @@ def in_state(self, dt): def on_exit(self, dt): self._context.stop_initialisation = False + self._context.time_spent_initialising = 0.0 From 2e28de4d68dffca4fdee33b493a6f96986d48ec0 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 16:58:53 +0000 Subject: [PATCH 0397/1466] Honour request to skip initialisation --- lewis_emulators/gemorc/device.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 11a18f56..6b8ef912 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -47,7 +47,8 @@ def _get_transition_handlers(self): return OrderedDict([ (('idle', 'initialising'), lambda: self.initialisation_requested), - (('initialising', 'initialised'), lambda: self.time_spent_initialising >= self.time_to_initialise), + (('initialising', 'initialised'), lambda: self.stop_initialisation or + self.time_spent_initialising >= self.time_to_initialise), (('initialised', 'oscillating'), lambda: self.start_requested), (('initialised', 'initialising'), lambda: self.initialisation_requested), From 66f7faf30c8588377211800f8c7406722649de73 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Thu, 30 Nov 2017 17:03:10 +0000 Subject: [PATCH 0398/1466] No action at the moment on datum re zero --- lewis_emulators/gemorc/interfaces/stream_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index aeae6fc3..cd64456b 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -52,7 +52,6 @@ def re_zero_to_datum(self): """ Re-zero position from datum marker """ - self.device.initialise() return "daok" def start(self): From 71b621a2c55b7b3d5122047aa03561190b3ca8b7 Mon Sep 17 00:00:00 2001 From: esouthren Date: Fri, 1 Dec 2017 09:29:44 +0000 Subject: [PATCH 0399/1466] stage1 pre testing - stripped back fsm --- lewis_emulators/HFMAGPSU/device.py | 22 ++----------- lewis_emulators/HFMAGPSU/device.pyc | Bin 9586 -> 8670 bytes .../HFMAGPSU/interfaces/stream_interface.py | 30 +++++++++--------- .../HFMAGPSU/interfaces/stream_interface.pyc | Bin 11572 -> 11563 bytes 4 files changed, 18 insertions(+), 34 deletions(-) diff --git a/lewis_emulators/HFMAGPSU/device.py b/lewis_emulators/HFMAGPSU/device.py index 74ca95b3..b79dd2d3 100644 --- a/lewis_emulators/HFMAGPSU/device.py +++ b/lewis_emulators/HFMAGPSU/device.py @@ -6,6 +6,7 @@ class SimulatedHFMAGPSU(StateMachineDevice): def _initialize_data(self): + self._is_output_mode_tesla = True self._is_heater_on = True self._is_paused = True @@ -13,13 +14,12 @@ def _initialize_data(self): self._direction = '+' self._ramp_target = 'MID' self._heater_value = 0.0 - self._max_target = 34.92 + self._max_target = 10 self._mid_target = 0.0 self._ramp_rate = 0.5 self._limit = 5.0 self._log_message = "this is the initial log message" - self._error_message = "this is the initial error message" - self._constant = 0.00029 + self._constant = 0.029 self._zero_target = 0.0 self._mid_final_target = 0 @@ -91,18 +91,10 @@ def limit(self): def log_message(self): return self._log_message - @property - def error_message(self): - return self._error_message - @property def constant(self): return float(self._constant) - @property - def zero_value(self): - return self._zero_target - @property def mid_final_target(self): return self._mid_final_target @@ -151,10 +143,6 @@ def limit(self, lim): def log_message(self, lm): self._log_message = lm - @error_message.setter - def error_message(self, em): - self._error_message = em - @output.setter def output(self, op): self._output = op @@ -163,10 +151,6 @@ def output(self, op): def constant(self, cons): self._constant = cons - @zero_value.setter - def zero_value(self, zv): - self._zero_value = zv - @mid_final_target.setter def mid_final_target(self, mft): self._mid_final_target = mft diff --git a/lewis_emulators/HFMAGPSU/device.pyc b/lewis_emulators/HFMAGPSU/device.pyc index bfd11392559e6336bcf04ce2a6f4c4d13afd9295..01ffb54f0ba3ffa6328d073b9c56625da1009995 100644 GIT binary patch delta 1218 zcmZ9J%}W$<7{{MkcXoGX_GR{UoZWGEZC70_!(K2H%)mop8Zm;00yEw5CAM7LHE^jF ztS%iYdJ2N@+#!MtymazXhoqoegarKsM%^N)-}BoUwGO|B@AG-yXMT))9r8B)49GKm z*$r<}aQzZ6C;VCU!qqqwBX9@cwg4BsVEH(SI>ZN-%t_24GFS>Hafc{i`8i2A#1B@0 z6U`w3u!5X4J0u8JXq6+~K_ReIPLd8$!3uMda!44g2q%U^B4A0Jv^YcpE6PdQAyKem zoMapl19QBfWdUGDV1}NHy&?80k8!1GB`d&9fXhKBN?e4X3S6jA1Q;*Km~yAY1(_A# zpu1O{)7=xn?$_&s3yeS^j4#}u@PYWzlO^AUmgkwEqu||CQxqpyps6Oo5+3)}Jrpnv zEK70R_GxM}rLeb`_dQ?2Nhyq*z69QtI#|0`+MJwa8C~pEz^TM_Dbxt+6g

%oONC z)1I;|x8x0idh(0(>kRcbhYgJ!wR4t%H&DC&L!UPx8MPUhFBROO$eu>oUdFNTeh zNWZksr)`Br+%iNq@tv_O(%)Po9#fdI7iWqeS8fbEf^}m6d!nMpzH2aD6}wr|Jt|aU z`-S*BT@jBm|2{?H{{0A3{D?)~(_`itc*OU4nW5`@T97%g5Xa}m-q_yt$NF=fjX%~&DGnc^U?~u_fkr_hAhn`fi36Zo2ERHE`7qvf zL?cA_C_TUd(m5hHaNwf{q^O62#D%I7w?YpU;!-t2;?PqB5)y|}<~{EtiI8@m{_~r8 zGxO%XTl0TB7YY5D(C@pK-HzC7UIol_(N zJSs_EktlFol7b>Sa6=MH5d(N!lAWuM3p^%?1M|VrW59C~)l^UpcwUkTMQA()NhTF3fP1=r2y3A{ z`xGA!otuimn*hxuw3!qsXzNm#;DZ!tXak(C8~Af*Jl6TE{_;mx2Xn`9H#9x%)d0X> z?0OD;{vvm}t6jg_T6TE9zt(qFxaYMxyklF*^KI66s!P#O&){_UhMpwZlwtT!_!n(L zxbU`C#9i$IvvDrHjK7UOYnH?g;j1%R+@a&E$Xl$0J5d|Ij#=i||3V@i{3%*s75qJV zjB;(~JFJEu#-1K(T{Qh5{T|@+dbv>%`$uSTg>uWnl*Pmnlog78W#X28RW{kfYsND{ zyKlr_2-MNULZEgNCjxaTc`{HubtX_p%ms6bvbyPgsl~~Um>T|V4ijII!9DC|guR`) z9BkfAgz;v!ir15lkw3UUlOS8pwi#T(8QjUv3~Ln*#wMaS`K?v|72Jw94C`P7 zbrUu=cz>qAHe13|#Too0uj@8>d@;HyJ*JYyXmQKg~mDv5nHi*p|GO@_9xGpo-anqhP%zZgdjd|Jw zWWOtNm~yDYn#9~0dQ-2By5iz5$Qs|K#^LykdvjN}(0Q$gOze+TwM zgd0ZcMf|qvIOC+cS_N$qNftbq zBBg?$c#t{vte_r5qImG)AE4k_>`5;Q9z^g^6`Xk+QiO)=XLo03-uupc-^+z(3ohqR z&z`Rb_zTyg-eXQhQ4GAANkmIguHm04OH64oMO()whoZIcVx5rv4qLr()qxcj)vwgA zTD$PR+-TInbiZPu_HXw|XHdhKQ53?=75Hpgm_{LeCO6G*t#aNLo@vHTz=M4p$+R^% z(sw>Fjs@Z{qUl2fHV66`%gV1=`Sq@UEooBmETS1gbQ_pAQw3A-d?8gRb{dgwig7qT7*R>2tsqSG zWQUMFz>mSlez}|nWB;upOn4rM1Wr*dC*%lGLYk0ie-HGqz1xC<@Fy4|;r@`z!DfLE z`fOOOYBVm=y zr?t`ii;*GQeE1ly!S%5X_JExY)%M{3Mr!a$-GJRuLuxBU zHJV*Dil&#Oj*`@&=?06y1$}G><#VWf3lAF|Ap6KgV|T)N+{x-iRO;1gxwh1_=Ncmc QwiEC!HpEW1zr`|+f6PY3tN;K2 delta 950 zcmZ{iO=uHQ5XW~mEt^==rcIhQ{YbX?>KdE$&?p$Jm?mqImTcRX4V5DOuq}wtq?p`n z527bQJq&p1O{fSDaZef634D2-Q*ugn;e3c-Jd6t zJd{mvH2OGFt)E&oN@j&_XbuWc2`PNs^9us_z%!y~o#dIs7oIfC@I-g48@(UElOivb z$y6I=xDwmP6@Lyw_{1N!-uin03|x&3f?*vE z$k26#(&!wGA^bBqstBnpiQz!BpB}U2g()ge5L)nS;OVfq(fvNjJKZ;aPLWX7P8ff-pVN2+Bu@ge(_=#-OiR5PTs?C za5Xha-tz>NU=TtC(JP^1kxW|v9tQo%IkLw%G@(?K$jCQCILBd6e!aQHa(Em(1{HsV zBE5D;L~@lP^Z43#9}m|yW{V}HAsv6X_5b9gh3F;qhm@d2Tp`X`KHvPM|dH!kH2F%u6H^fv98DY z%$LN~C2{q%mcxs2H(t{M{}fXY#q^!o;Y&qZVz1U{;@)MG3u-z!HtO5k>$S~>-OID2 Yv)z4B4=p+q`zt!@C9`aOOU%lD0edLK9{>OV From a76fbe40ddc7285613e5f617a772bc46d790b015 Mon Sep 17 00:00:00 2001 From: David Keymer Date: Fri, 1 Dec 2017 14:16:37 +0000 Subject: [PATCH 0400/1466] Removed redundant command. Altered get_all_status to reflect protocol change. --- .../interfaces/stream_interface.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py index ee9fe4c4..8a654f5e 100644 --- a/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py +++ b/lewis_emulators/fzj_dd_fermi_chopper/interfaces/stream_interface.py @@ -11,8 +11,8 @@ class FZJDDFCHStreamInterface(StreamInterface): """ commands = { - CmdBuilder("get_magnetic_bearing_status").escape("MBON?").build(), - CmdBuilder("get_all_status").arg(".{3}").escape(";ASTA?").build(), + CmdBuilder("get_magnetic_bearing_status").arg(".{3}").escape("?;MBON?").build(), + CmdBuilder("get_all_status").arg(".{3}").escape("?;ASTA?").build(), CmdBuilder("set_frequency", arg_sep="").arg(".{3}").escape("!;FACT!;").int().build(), CmdBuilder("set_phase", arg_sep="").arg(".{3}").escape("!;PHAS!;").float().build(), CmdBuilder("set_magnetic_bearing", arg_sep="").arg(".{3}").escape("!;MAGB!;").any().build(), @@ -81,7 +81,7 @@ def set_drive_mode(self, chopper_name, drive_mode): self.log.info(reply) return reply - def get_magnetic_bearing_status(self): + def get_magnetic_bearing_status(self, chopper_name): """ Gets the magnetic bearing status for the FZJ Digital Drive Fermi Chopper Controller @@ -90,8 +90,10 @@ def get_magnetic_bearing_status(self): Returns: """ - - return self._device.magnetic_bearing_status + if self._device.disconnected: + return None + device = self._device + return "{0:3s};MBON?;{}".format(device.chopper_name, self._device.magnetic_bearing_status) def get_all_status(self, chopper_name): From e6085f4311d94b1a883556a81dc651368b404abe Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Fri, 1 Dec 2017 14:19:24 +0000 Subject: [PATCH 0401/1466] Fix return string and time calculation --- lewis_emulators/gemorc/interfaces/stream_interface.py | 2 +- lewis_emulators/gemorc/states.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index cd64456b..c10869ab 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -83,7 +83,7 @@ def set_window_width(self, width): width: Width in centi-degrees """ self.device.set_window_width(int(width)) - return "wnok" + return "wwok" def set_offset(self, offset): """ diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index 5cf83945..f0887607 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -30,11 +30,12 @@ def on_entry(self, dt): @staticmethod def calculate_speed(time, window_width, target_speed, acceleration): transition_time = target_speed/acceleration - total_cycle_time = 2*transition_time + window_width + window_time = window_width/target_speed + total_cycle_time = 2*transition_time + window_time cycle_time = time % total_cycle_time spinning_up = cycle_time < transition_time - spinning_down = cycle_time > window_width + transition_time + spinning_down = cycle_time > window_time + transition_time if spinning_up: current_speed = target_speed * cycle_time / transition_time From ac921f782873e03db0e0e8d0ab39701f1a840ea6 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Fri, 1 Dec 2017 17:07:38 +0000 Subject: [PATCH 0402/1466] Add reset --- lewis_emulators/gemorc/device.py | 35 +++++++---- lewis_emulators/gemorc/states.py | 104 +++++++++++++++++++++++-------- 2 files changed, 103 insertions(+), 36 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 6b8ef912..b4e3f9bd 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from states import StoppedState, OscillatingState, InitialisingState, IdleState, InitialisedState +from states import StoppedState, OscillatingState, InitialisingState, IdleState, InitialisedState, ResetState from lewis.devices import StateMachineDevice SMALL = 1.0e-10 @@ -19,17 +19,22 @@ def _initialize_data(self): self.state = None # Controlled by state handler self.stop_initialisation = False - self.initialisation_cycle = 100 - self.optional_initialisation_cycle = 50 - self.complete_cycles = 0 - - # Device settings - self.window_width = 100 - self.acceleration = 500 - self.target_speed = 20 + # Device settings, set by the reset state + self.window_width = 0 + self.acceleration = 0 self.speed = 0 self.offset = 0 + + # Instantaneous properties self.backlash = 10 + self.complete_cycles = 0 + + # Emulator control + self.reset = False + + # TODO: These are not part of the hardware. Their logic should be removed before the code is accepted + self.initialisation_cycle = 100 + self.optional_initialisation_cycle = 50 def _get_state_handlers(self): return { @@ -38,10 +43,11 @@ def _get_state_handlers(self): 'oscillating': OscillatingState(), 'stopped': StoppedState(), 'idle': IdleState(), + 'reset': ResetState(), } def _get_initial_state(self): - return 'idle' + return 'reset' def _get_transition_handlers(self): return OrderedDict([ @@ -59,6 +65,13 @@ def _get_transition_handlers(self): (('stopped', 'initialising'), lambda: self.initialisation_requested), (('stopped', 'oscillating'), lambda: self.start_requested), + + (('idle', 'reset'), lambda: self.reset), + (('initialising', 'reset'), lambda: self.reset), + (('initialised', 'reset'), lambda: self.reset), + (('oscillating', 'reset'), lambda: self.reset), + (('stopped', 'reset'), lambda: self.reset), + (('reset', 'idle'), lambda: not self.reset), ]) def initialise(self): @@ -98,7 +111,7 @@ def get_speed(self): return self.speed def set_speed(self, speed): - self.target_speed = speed + self.speed = speed def is_oscillating(self): return self.state == OscillatingState.__name__ diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index f0887607..907aa106 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -25,39 +25,84 @@ def on_entry(self, dt): dev = self._context dev.state = OscillatingState.__name__ self.time = 0.0 - self.target_reached = False + self.new_cycle = False @staticmethod - def calculate_speed(time, window_width, target_speed, acceleration): - transition_time = target_speed/acceleration - window_time = window_width/target_speed - total_cycle_time = 2*transition_time + window_time - cycle_time = time % total_cycle_time - - spinning_up = cycle_time < transition_time - spinning_down = cycle_time > window_time + transition_time - - if spinning_up: - current_speed = target_speed * cycle_time / transition_time - elif spinning_down: - current_speed = target_speed * (total_cycle_time - cycle_time) / transition_time + def transition_time(speed, acceleration): + return speed/acceleration + + @staticmethod + def window_time(width, speed): + return width/speed + + @staticmethod + def total_cycle_time(width, speed, acceleration): + transition_time = OscillatingState.transition_time(speed, acceleration) + window_time = OscillatingState.window_time(width, speed) + return 2*transition_time + window_time + + @staticmethod + def cycle_time(actual_time, width, speed, acceleration): + return actual_time % OscillatingState.total_cycle_time(width, speed, acceleration) + + @staticmethod + def spinning_up(actual_time, width, speed, acceleration): + cycle_time = OscillatingState.cycle_time(actual_time, width, speed, acceleration) + transition_time = OscillatingState.transition_time(speed, acceleration) + return cycle_time < transition_time + + # TODO: The following methods static methods are only needed if the ORC reports its instantaneous speed/acceleration + # At the moment we don't know if it reports only the requested speed/acceleration rather than the instantaneous + # values. + + @staticmethod + def spinning_down(actual_time, width, speed, acceleration): + cycle_time = OscillatingState.cycle_time(actual_time, width, speed, acceleration) + window_time = OscillatingState.window_time(width, speed) + transition_time = OscillatingState.transition_time(speed, acceleration) + return cycle_time > window_time + transition_time + + @staticmethod + def calculate_speed(time, window_width, speed, acceleration): + cycle_time = OscillatingState.cycle_time(time, window_width, speed, acceleration) + transition_time = OscillatingState.transition_time(speed, acceleration) + total_cycle_time = OscillatingState.total_cycle_time(window_width, speed, acceleration) + + if OscillatingState.spinning_up(time, window_width, speed, acceleration): + current_speed = speed*cycle_time/transition_time + elif OscillatingState.spinning_down(time, window_width, speed, acceleration): + current_speed = speed*(total_cycle_time-cycle_time)/transition_time else: - current_speed = target_speed + current_speed = speed + return current_speed + @staticmethod + def calculate_acceleration(time, window_width, speed, acceleration): + if OscillatingState.spinning_up(time, window_width, speed, acceleration): + current_acceleration = acceleration + elif OscillatingState.spinning_down(time, window_width, speed, acceleration): + current_acceleration = 0 + else: + current_acceleration = -acceleration + return current_acceleration + def in_state(self, dt): self.time += dt dev = self._context - - dev.speed = OscillatingState.calculate_speed(self.time, dev.window_width, dev.target_speed, dev.acceleration) - - # Increment the cycle counter if needed. Important to only do this once per cycle - # Has the cycle hit its target speed this cycle - self.target_reached = self.target_reached or dev.speed == dev.target_speed - in_motion = dev.speed/(dev.acceleration*dt) > 2 # 2 dimensionless speed increments - if not in_motion and self.target_reached: - dev.complete_cycles += 1 - self.target_reached = False + spinning_up = OscillatingState.spinning_up(self.time, dev.window_width, dev.speed, dev.acceleration) + + if spinning_up and self.new_cycle: + dev.complete_cycles += 1 # Increment the complete cycles during spin up once per cycle + self.new_cycle = False + elif spinning_up and not self.new_cycle: + pass # We've already incremented the cycles + elif not spinning_up and self.new_cycle: + pass # We've already set the new cycle flag ready for the next cycle + elif not spinning_up and not self.new_cycle: + self.new_cycle = True + else: + raise AssertionError("Should not logically be reached") class IdleState(State): @@ -84,3 +129,12 @@ def in_state(self, dt): def on_exit(self, dt): self._context.stop_initialisation = False self._context.time_spent_initialising = 0.0 + +class ResetState(State): + + def on_entry(self, dt): + dev = self._context + dev.window_width = 100 + dev.acceleration = 500 + dev.speed = 20 + dev.offset = 0 \ No newline at end of file From 7076b8abae958b6b98db1516e01f53bb4a9004be Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Fri, 1 Dec 2017 17:48:32 +0000 Subject: [PATCH 0403/1466] Better way to calculate cycles that can't be aliased --- lewis_emulators/gemorc/device.py | 7 +------ lewis_emulators/gemorc/states.py | 15 ++------------- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index b4e3f9bd..da9c8369 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -32,10 +32,6 @@ def _initialize_data(self): # Emulator control self.reset = False - # TODO: These are not part of the hardware. Their logic should be removed before the code is accepted - self.initialisation_cycle = 100 - self.optional_initialisation_cycle = 50 - def _get_state_handlers(self): return { 'initialising': InitialisingState(), @@ -60,8 +56,7 @@ def _get_transition_handlers(self): (('initialised', 'initialising'), lambda: self.initialisation_requested), (('oscillating', 'stopped'), lambda: not self.start_requested), - (('oscillating', 'initialising'), lambda: self.complete_cycles > 0 and - self.complete_cycles % self.initialisation_cycle == 0), + (('oscillating', 'initialising'), lambda: self.initialisation_requested), (('stopped', 'initialising'), lambda: self.initialisation_requested), (('stopped', 'oscillating'), lambda: self.start_requested), diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index 907aa106..fa8ae0f1 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -1,4 +1,5 @@ from lewis.core.statemachine import State +from math import floor class StoppedState(State): @@ -90,19 +91,7 @@ def calculate_acceleration(time, window_width, speed, acceleration): def in_state(self, dt): self.time += dt dev = self._context - spinning_up = OscillatingState.spinning_up(self.time, dev.window_width, dev.speed, dev.acceleration) - - if spinning_up and self.new_cycle: - dev.complete_cycles += 1 # Increment the complete cycles during spin up once per cycle - self.new_cycle = False - elif spinning_up and not self.new_cycle: - pass # We've already incremented the cycles - elif not spinning_up and self.new_cycle: - pass # We've already set the new cycle flag ready for the next cycle - elif not spinning_up and not self.new_cycle: - self.new_cycle = True - else: - raise AssertionError("Should not logically be reached") + dev.complete_cycles = floor(self.time/self.total_cycle_time(dev.window_width, dev.speed, dev.acceleration)) class IdleState(State): From 23d67956323792f37c8f3acc15f3212eba5efb49 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Fri, 1 Dec 2017 17:49:52 +0000 Subject: [PATCH 0404/1466] Refactor now these methods not required --- lewis_emulators/gemorc/states.py | 58 ++------------------------------ 1 file changed, 2 insertions(+), 56 deletions(-) diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index fa8ae0f1..d619756c 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -28,66 +28,12 @@ def on_entry(self, dt): self.time = 0.0 self.new_cycle = False - @staticmethod - def transition_time(speed, acceleration): - return speed/acceleration - - @staticmethod - def window_time(width, speed): - return width/speed - @staticmethod def total_cycle_time(width, speed, acceleration): - transition_time = OscillatingState.transition_time(speed, acceleration) - window_time = OscillatingState.window_time(width, speed) + transition_time = speed/acceleration + window_time = width/speed return 2*transition_time + window_time - @staticmethod - def cycle_time(actual_time, width, speed, acceleration): - return actual_time % OscillatingState.total_cycle_time(width, speed, acceleration) - - @staticmethod - def spinning_up(actual_time, width, speed, acceleration): - cycle_time = OscillatingState.cycle_time(actual_time, width, speed, acceleration) - transition_time = OscillatingState.transition_time(speed, acceleration) - return cycle_time < transition_time - - # TODO: The following methods static methods are only needed if the ORC reports its instantaneous speed/acceleration - # At the moment we don't know if it reports only the requested speed/acceleration rather than the instantaneous - # values. - - @staticmethod - def spinning_down(actual_time, width, speed, acceleration): - cycle_time = OscillatingState.cycle_time(actual_time, width, speed, acceleration) - window_time = OscillatingState.window_time(width, speed) - transition_time = OscillatingState.transition_time(speed, acceleration) - return cycle_time > window_time + transition_time - - @staticmethod - def calculate_speed(time, window_width, speed, acceleration): - cycle_time = OscillatingState.cycle_time(time, window_width, speed, acceleration) - transition_time = OscillatingState.transition_time(speed, acceleration) - total_cycle_time = OscillatingState.total_cycle_time(window_width, speed, acceleration) - - if OscillatingState.spinning_up(time, window_width, speed, acceleration): - current_speed = speed*cycle_time/transition_time - elif OscillatingState.spinning_down(time, window_width, speed, acceleration): - current_speed = speed*(total_cycle_time-cycle_time)/transition_time - else: - current_speed = speed - - return current_speed - - @staticmethod - def calculate_acceleration(time, window_width, speed, acceleration): - if OscillatingState.spinning_up(time, window_width, speed, acceleration): - current_acceleration = acceleration - elif OscillatingState.spinning_down(time, window_width, speed, acceleration): - current_acceleration = 0 - else: - current_acceleration = -acceleration - return current_acceleration - def in_state(self, dt): self.time += dt dev = self._context From 12eec82c14e68f1dcc09dfac9746555d7c8f291a Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Mon, 4 Dec 2017 14:24:44 +0000 Subject: [PATCH 0405/1466] Improve reset logic --- lewis_emulators/gemorc/device.py | 10 +++++----- lewis_emulators/gemorc/interfaces/stream_interface.py | 2 +- lewis_emulators/gemorc/states.py | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index da9c8369..7525f72f 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -12,18 +12,18 @@ def _initialize_data(self): """ # State control variables - self.time_to_initialise = 2.0 + self.time_to_initialise = 300.0 # 3s in real time with speed up factor of 100 self.time_spent_initialising = 0.0 - self.initialisation_requested = False - self.start_requested = False self.state = None # Controlled by state handler - self.stop_initialisation = False # Device settings, set by the reset state self.window_width = 0 self.acceleration = 0 self.speed = 0 self.offset = 0 + self.initialisation_requested = False + self.start_requested = False + self.stop_initialisation = False # Instantaneous properties self.backlash = 10 @@ -115,7 +115,7 @@ def is_initialising(self): return self.state == InitialisingState.__name__ def has_been_initialised(self): - return self.state not in (IdleState.__name__, InitialisingState.__name__) + return self.state in (s.__name__ for s in (InitialisedState, OscillatingState, StoppedState)) def get_complete_cycles(self): return self.complete_cycles diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index c10869ab..a77f2274 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -121,7 +121,7 @@ def get_status(self): what the real thing returns as the VI code doesn't match the spec """ request_format = "{oscillating:01d}....{initialising:01d}....{initialised:01d}.....{width:03d}......" \ - "{offset:04d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" + "{offset:03d}......{speed:02d}.....{acceleration:04d}..{cycles:05d}..........{backlash:03d}" return request_format.format( oscillating=int(self.device.is_oscillating()), initialising=int(self.device.is_initialising()), diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index d619756c..981ddf5c 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -65,6 +65,7 @@ def on_exit(self, dt): self._context.stop_initialisation = False self._context.time_spent_initialising = 0.0 + class ResetState(State): def on_entry(self, dt): @@ -72,4 +73,8 @@ def on_entry(self, dt): dev.window_width = 100 dev.acceleration = 500 dev.speed = 20 - dev.offset = 0 \ No newline at end of file + dev.offset = 0 + dev.initialisation_requested = False + dev.start_requested = False + dev.stop_initialisation = False + dev.reset = False From adadd44a91a07231b2c56e29dc09cf7cade0fe72 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Tue, 5 Dec 2017 10:39:43 +0000 Subject: [PATCH 0406/1466] Switch protocol back to match VI rather than spec --- lewis_emulators/gemorc/device.py | 3 +-- lewis_emulators/gemorc/interfaces/stream_interface.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 7525f72f..ff3e8568 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -26,7 +26,6 @@ def _initialize_data(self): self.stop_initialisation = False # Instantaneous properties - self.backlash = 10 self.complete_cycles = 0 # Emulator control @@ -121,4 +120,4 @@ def get_complete_cycles(self): return self.complete_cycles def get_backlash(self): - return self.backlash \ No newline at end of file + return 0.5*float(self.speed**2)/float(max(self.acceleration,1)) \ No newline at end of file diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index a77f2274..da5a9e9a 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -120,8 +120,8 @@ def get_status(self): Get the current state of the collimator. This is backwards engineered from the VI. I haven't actually seen what the real thing returns as the VI code doesn't match the spec """ - request_format = "{oscillating:01d}....{initialising:01d}....{initialised:01d}.....{width:03d}......" \ - "{offset:03d}......{speed:02d}.....{acceleration:04d}..{cycles:05d}..........{backlash:03d}" + request_format = "{oscillating:01d}....{initialising:01d}....{initialised:01d}.....{width:03d}....." \ + "{offset:04d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" return request_format.format( oscillating=int(self.device.is_oscillating()), initialising=int(self.device.is_initialising()), From 5e3d1e3dac742075ac5fd3ff4305429e259c0665 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Tue, 5 Dec 2017 11:15:43 +0000 Subject: [PATCH 0407/1466] Make re zero to datum do something. May not reflect reality --- lewis_emulators/gemorc/device.py | 2 +- lewis_emulators/gemorc/interfaces/stream_interface.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index ff3e8568..ed1875e4 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -72,7 +72,7 @@ def initialise(self): self.initialisation_requested = True def re_zero_to_datum(self): - raise NotImplementedError() + self.offset = 0.0 def start(self): self.start_requested = True diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index da5a9e9a..1b292e0b 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -52,6 +52,7 @@ def re_zero_to_datum(self): """ Re-zero position from datum marker """ + self.device.re_zero_to_datum() return "daok" def start(self): From cd546936fcaa8dbf4dfe5d62be0f2a27fa61b907 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Tue, 5 Dec 2017 11:16:11 +0000 Subject: [PATCH 0408/1466] Make re zero to datum do something. May not reflect reality --- lewis_emulators/gemorc/device.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index ed1875e4..4618f4b0 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -72,6 +72,7 @@ def initialise(self): self.initialisation_requested = True def re_zero_to_datum(self): + # Not clear whether this is what the real device does self.offset = 0.0 def start(self): From fe00e6a4eb5760cb644346fc3e6df3553bc4daca Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Tue, 5 Dec 2017 12:49:10 +0000 Subject: [PATCH 0409/1466] Reset cycle count on reset --- lewis_emulators/gemorc/states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index 981ddf5c..17055c16 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -77,4 +77,5 @@ def on_entry(self, dt): dev.initialisation_requested = False dev.start_requested = False dev.stop_initialisation = False + dev.complete_cycles = 0 dev.reset = False From b6a78b3161cece32efbe11f00fcef7affb7c9d63 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Tue, 5 Dec 2017 14:09:09 +0000 Subject: [PATCH 0410/1466] Reset cycles when reinitialising --- lewis_emulators/gemorc/states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lewis_emulators/gemorc/states.py b/lewis_emulators/gemorc/states.py index 17055c16..b26e4fa2 100644 --- a/lewis_emulators/gemorc/states.py +++ b/lewis_emulators/gemorc/states.py @@ -57,6 +57,7 @@ class InitialisingState(State): def on_entry(self, dt): self._context.state = InitialisingState.__name__ self._context.initialisation_requested = False + self._context.complete_cycles = 0 def in_state(self, dt): self._context.time_spent_initialising += dt From 01d5214a39294934d3bc1a81d74be1b73d2c0bd4 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Wed, 6 Dec 2017 15:38:18 +0000 Subject: [PATCH 0411/1466] Update stream_interface.py --- lewis_emulators/gemorc/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index 1b292e0b..bbd8403b 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -20,8 +20,8 @@ class GemorcStreamInterface(StreamInterface): CmdBuilder("get_status").escape("rq").build(), } - in_terminator = "\r\n" - out_terminator = "\r\n" + in_terminator = "\n" + out_terminator = "\n" def handle_error(self, request, error): """ From e62d08932ab1d4f8831aea58ba593caf12f79136 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Mon, 11 Dec 2017 09:52:37 +0000 Subject: [PATCH 0412/1466] Remove unused variable --- lewis_emulators/gemorc/device.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lewis_emulators/gemorc/device.py b/lewis_emulators/gemorc/device.py index 4618f4b0..8e67ccf0 100644 --- a/lewis_emulators/gemorc/device.py +++ b/lewis_emulators/gemorc/device.py @@ -2,7 +2,6 @@ from states import StoppedState, OscillatingState, InitialisingState, IdleState, InitialisedState, ResetState from lewis.devices import StateMachineDevice -SMALL = 1.0e-10 class SimulatedGemorc(StateMachineDevice): From 69ae52bcd1058e696a22c3038ea21887985b3bc5 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Mon, 11 Dec 2017 09:53:28 +0000 Subject: [PATCH 0413/1466] Use CR not LF --- lewis_emulators/gemorc/interfaces/stream_interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index bbd8403b..c087a07c 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -20,8 +20,8 @@ class GemorcStreamInterface(StreamInterface): CmdBuilder("get_status").escape("rq").build(), } - in_terminator = "\n" - out_terminator = "\n" + in_terminator = "\r" + out_terminator = "\r" def handle_error(self, request, error): """ From 595eb2b5d5b3ab740e9457a5a6607e1855b7d46f Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Mon, 11 Dec 2017 10:25:19 +0000 Subject: [PATCH 0414/1466] Add optional sign to integer arg --- lewis_emulators/utils/command_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index b321730b..b6437dff 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -92,7 +92,7 @@ def int(self): :return: builder """ - return self.arg(r"\d+") + return self.arg(r"[+-]?\d+") def any(self): """ From bc486a239c8832d87f8b3ec709e3ed581db5f7ad Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 12 Dec 2017 17:06:27 +0000 Subject: [PATCH 0415/1466] Changes need to read position --- lewis_emulators/sm300/device.py | 14 +++++++++++--- .../sm300/interfaces/stream_interface.py | 8 ++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py index d8e3e29a..ffb8fc63 100644 --- a/lewis_emulators/sm300/device.py +++ b/lewis_emulators/sm300/device.py @@ -6,13 +6,13 @@ class Axis(): def __init__(self): - self.rbv = 0 - self.sp = 0 + self.rbv = 10.0 + self.sp = self.rbv self.moving = False self.speed = 0.1 def home(self): - self.sp = 0 + self.sp = 0.0 self.moving = True def simulate(self, dt): @@ -59,3 +59,11 @@ def y_axis_rbv(self): @y_axis_rbv.setter def y_axis_rbv(self, position): self.y_axis.rbv = position + + @property + def x_axis_sp(self): + return self.x_axis.rbv + + @x_axis_sp.setter + def x_axis_sp(self, position): + self.x_axis.sp = position diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index 774d2796..8f011f9d 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -8,6 +8,7 @@ @has_log class Sm300StreamInterface(StreamInterface): + in_terminator = EOT out_terminator = EOT def __init__(self): @@ -18,7 +19,7 @@ def __init__(self): self.commands = { CmdBuilder(self.get_position).escape("LQ").build(), # Catch-all command for debugging CmdBuilder(self.home_axis).escape("BR").char().build(), # Catch-all command for debugging - CmdBuilder(self.get_status).escape("LM").char().build(), # Catch-all command for debugging + CmdBuilder(self.get_status).escape("LM").build(), # Catch-all command for debugging } self.device = self._device @@ -35,7 +36,10 @@ def handle_error(self, request, error): self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) def get_position(self): - return "{ACK}{STX}X{0:d.0},Y{1:d.0}".format(self.device.x_axis.rbv, self.device.y_axis.rbv, **COMMAND_CHARS) + self.device.x_axis.rbv += 0.1 + self.device.x_axis.sp += 0.1 + self.log.info("Send position {} {}".format(self.device.x_axis.rbv, self.device.y_axis.rbv)) + return "{ACK}{STX}X{0:.0f},Y{1:.0f}".format(self.device.x_axis.rbv, self.device.y_axis.rbv, **COMMAND_CHARS) def home_axis(self, axis): if axis == "X": From 815a1069c722b39a6ec87dfa89413bf1ad6b34aa Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Wed, 13 Dec 2017 11:49:56 +0000 Subject: [PATCH 0416/1466] Sign the offset response --- lewis_emulators/gemorc/interfaces/stream_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index c087a07c..586d75f4 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -122,7 +122,7 @@ def get_status(self): what the real thing returns as the VI code doesn't match the spec """ request_format = "{oscillating:01d}....{initialising:01d}....{initialised:01d}.....{width:03d}....." \ - "{offset:04d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" + "{offset:+03d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" return request_format.format( oscillating=int(self.device.is_oscillating()), initialising=int(self.device.is_initialising()), From 0ece17104dd6a2e9be7d66e557b6de976c492b89 Mon Sep 17 00:00:00 2001 From: Adrian Potter Date: Wed, 13 Dec 2017 12:36:44 +0000 Subject: [PATCH 0417/1466] Format for +999 is +04d --- lewis_emulators/gemorc/interfaces/stream_interface.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/gemorc/interfaces/stream_interface.py b/lewis_emulators/gemorc/interfaces/stream_interface.py index 586d75f4..7235ecb5 100644 --- a/lewis_emulators/gemorc/interfaces/stream_interface.py +++ b/lewis_emulators/gemorc/interfaces/stream_interface.py @@ -122,8 +122,8 @@ def get_status(self): what the real thing returns as the VI code doesn't match the spec """ request_format = "{oscillating:01d}....{initialising:01d}....{initialised:01d}.....{width:03d}....." \ - "{offset:+03d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" - return request_format.format( + "{offset:+04d}......{speed:02d}.....{acceleration:03d}..{cycles:05d}..........{backlash:03d}" + status_string = request_format.format( oscillating=int(self.device.is_oscillating()), initialising=int(self.device.is_initialising()), initialised=int(self.device.has_been_initialised()), @@ -134,3 +134,4 @@ def get_status(self): cycles=int(self.device.get_complete_cycles()), backlash=int(self.device.get_backlash()) ) + return status_string From 0bef371e6dce38b7d30d34fa69e99e82a81ce0ef Mon Sep 17 00:00:00 2001 From: esouthren Date: Mon, 18 Dec 2017 11:36:36 +0000 Subject: [PATCH 0418/1466] updated emulator for lewis update --- .idea/.name | 1 + .idea/master.iml | 13 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/workspace.xml | 694 +++++++++++++++ .../.idea/dictionaries/nht51319.xml | 7 + lewis_emulators/.idea/lewis_emulators.iml | 14 + lewis_emulators/.idea/misc.xml | 4 + lewis_emulators/.idea/modules.xml | 8 + lewis_emulators/.idea/workspace.xml | 799 ++++++++++++++++++ lewis_emulators/lakeshore460/device.py | 345 +------- .../interfaces/stream_interface.py | 198 +---- 13 files changed, 1610 insertions(+), 491 deletions(-) create mode 100644 .idea/.name create mode 100644 .idea/master.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 lewis_emulators/.idea/dictionaries/nht51319.xml create mode 100644 lewis_emulators/.idea/lewis_emulators.iml create mode 100644 lewis_emulators/.idea/misc.xml create mode 100644 lewis_emulators/.idea/modules.xml create mode 100644 lewis_emulators/.idea/workspace.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 00000000..4deae2ba --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +TDKLambdaGenesys \ No newline at end of file diff --git a/.idea/master.iml b/.idea/master.iml new file mode 100644 index 00000000..cfb25219 --- /dev/null +++ b/.idea/master.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..e2f5a1e6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..32163c3a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..01ef32c2 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,694 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SIM + SIM: + powe + power + set + _set_powerstate + out + outputmode + heater + read_heater_value + output + "P" + outpu + outputmod + MAX + TESLA + pause + heaterstatus + 33.4 + 33.3 + setmid 2 + limit + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1501576077938 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/../../IocTestFramework/master/tests/tdklambdagenesys.py + + + file://$PROJECT_DIR$/../../IocTestFramework/master/tests/tdklambdagenesys.py + 53 + + + file://$PROJECT_DIR$/lewis_emulators/HFMAGPSU/interfaces/stream_interface.py + 30 + + + file://$PROJECT_DIR$/../../IocTestFramework/master/tests/hfmagpsu.py + 16 + + + file://$PROJECT_DIR$/../../IocTestFramework/master/tests/hfmagpsu.py + 73 + + + file://$PROJECT_DIR$/../../IocTestFramework/master/tests/hfmagpsu.py + 43 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/.idea/dictionaries/nht51319.xml b/lewis_emulators/.idea/dictionaries/nht51319.xml new file mode 100644 index 00000000..c73bc589 --- /dev/null +++ b/lewis_emulators/.idea/dictionaries/nht51319.xml @@ -0,0 +1,7 @@ + + + + ramping + + + \ No newline at end of file diff --git a/lewis_emulators/.idea/lewis_emulators.iml b/lewis_emulators/.idea/lewis_emulators.iml new file mode 100644 index 00000000..1b0e5caf --- /dev/null +++ b/lewis_emulators/.idea/lewis_emulators.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/.idea/misc.xml b/lewis_emulators/.idea/misc.xml new file mode 100644 index 00000000..e2f5a1e6 --- /dev/null +++ b/lewis_emulators/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/lewis_emulators/.idea/modules.xml b/lewis_emulators/.idea/modules.xml new file mode 100644 index 00000000..1c31bc17 --- /dev/null +++ b/lewis_emulators/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/.idea/workspace.xml b/lewis_emulators/.idea/workspace.xml new file mode 100644 index 00000000..d4812aad --- /dev/null +++ b/lewis_emulators/.idea/workspace.xml @@ -0,0 +1,799 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + def get_block + user_input + puw + set_pv + mid + max + _string + heater + match_en + pause + OUTPUT + ready + device + state + context + get_ramp + started + in_ + in_state + output + ramp_rate + linear + _get_ramp + zero_ + log + ramp status + read_output + 10.5 + write_pause + GET_OUTPUT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1503389649496 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file://$PROJECT_DIR$/../../../IocTestFramework/master/tests/hfmagpsu.py + + + file://$PROJECT_DIR$/utils/command_builder.py + + + file://$PROJECT_DIR$/mk2_chopper/device.py + + + file://$PROJECT_DIR$/lakeshore460/device.py + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lewis_emulators/lakeshore460/device.py b/lewis_emulators/lakeshore460/device.py index 5cf01f97..264ac691 100644 --- a/lewis_emulators/lakeshore460/device.py +++ b/lewis_emulators/lakeshore460/device.py @@ -4,43 +4,17 @@ from .states import DefaultState -class Channel(object): - def __init__(self): - self.display_filter_window = 0 - self.filter_points = 0 - self.manual_range = 0 - self.max_multiplier = "u" - self.rel_multiplier = "u" - self.magnetic_field_multiplier = "u" - self.rel_mode_multiplier = "u" - self.unit = "G" - self.status = 0 - self.mode = 0 - self.prms = 0 - self.filter = 0 - self.max_hold = 0 - self.rel_mode = 0 - self.auto_range = 0 - self.rel_setpoint = 0 - - class SimulatedLakeshore460(StateMachineDevice): """ - Simulated Lakeshore 460 + Simulated AM Int2-L pressure transducer. """ def _initialize_data(self): """ Sets the initial state of the device. """ - self._idn = "000000000000000000000000000000000000000" - self._rel_mode_reading = 4906 - self._source = 1 - self._channel = "X" - self._rel_set_point = 500 - self._max_reading = 100 - self._magnetic_field_reading = 400 - self.channels = {"X": Channel(), "Y":Channel(), "Z": Channel(), "V": Channel()} + self._pressure = 2.0 + self.address = "AB" def _get_state_handlers(self): """ @@ -60,314 +34,17 @@ def _get_transition_handlers(self): """ return OrderedDict() - - @property - def idn(self): - """ - Returns: the IDN of device - """ - return self._idn - - @idn.setter - def idn(self, idn): - """ - :param: idn:sets the IDN of device - """ - self._idn = idn - - @property - def unit(self): - """ - :return: unit for the device - """ - return self.channels[self._channel].unit - - @unit.setter - def unit(self, unit): - """ - :param unit: set unit for the device - """ - self.channels[self._channel].unit = unit - - @property - def status(self): - """ - :return: status of device. If its on/off - """ - return self.channels[self._channel].status - - @status.setter - def status(self, status): - """ - :param status: sets the device status, if device is on/off - """ - self.channels[self._channel].status = status - - @property - def mode(self): - """ - :return: AC or DC unit of measurement - """ - return self.channels[self._channel].mode - - @mode.setter - def mode(self, mode): - """ - :param mode: sets the device mode, if device is set to AC or DC measurement - """ - self.channels[self._channel].mode = mode - - @property - def prms(self): - """ - :return: Peak /RMS Field reading - """ - return self.channels[self._channel].prms - - @prms.setter - def prms(self, prms): - """ - :param prms: Sets the value to Peak or RMS - """ - self.channels[self._channel].prms = prms - - @property - def filter(self): - """ - :return: returns filter - """ - return self.channels[self._channel].filter - - @filter.setter - def filter(self, state): - """ - :param state: sets filter state (binary) - """ - self.channels[self._channel].filter = state - - @property - def max_hold(self): - """ - :return: returns binary max hold - """ - return self.channels[self._channel].max_hold - - @max_hold.setter - def max_hold(self, max_hold): - """ - :param max_hold: sets the max hold value - """ - self.channels[self._channel].max_hold = max_hold - - @property - def rel_mode(self): - """ - :return: Return relative mode reading - """ - return self.channels[self._channel].rel_mode - - @rel_mode.setter - def rel_mode(self, rel_mode): - """ - :param rel_mode: Sets the relative mode reading - """ - self.channels[self._channel].rel_mode = rel_mode - - @property - def auto_range(self): - """ - :return: Returns the auto range - """ - return self.channels[self._channel].auto_range - - @auto_range.setter - def auto_range(self, range): - """ - :param range: sets the auto range - """ - self.channels[self._channel].auto_range = range - - @property - def manual_range(self): - """ - :return: the value that has been set for the manual range - """ - return self.channels[self._channel].manual_range - - @manual_range.setter - def manual_range(self, range): - """ - :param range: sets the manual range - """ - self.channels[self._channel].manual_range = range - - @property - def source(self): - """ - :return: Returns magnetic field source XYZ, XYZ - """ - return self._source - - @source.setter - def source(self, source): - """ - :param source: Sets magnetic field source XYZ, XYZ - """ - self._source = source - - @property - def channel(self): - """ - :return: returns the channel value i.e Channel X, Channel Y - """ - return self._channel - - @channel.setter - def channel(self, channel): - """ - :param channel: sets channel i.e Channel X, Channel Y - """ - self._channel = channel - - - @property - def display_filter(self): - """ - :return: returns the percentage for display filter opening - """ - return self.channels[self._channel].display_filter_window - - @display_filter.setter - def display_filter(self, percentage): - """ - :param percentage: sets the percentage for display filter opening - """ - self.channels[self._channel].display_filter_window = percentage - - @property - def filter_points(self): - """ - :return: returns filter points value should be between 1 to 10 - """ - return self.channels[self._channel].filter_points - - @filter_points.setter - def filter_points(self, points): - """ - :param points: sets filter points value should be between 1 to 10 - """ - self.channels[self._channel].filter_points = points - - - @property - def rel_multiplier(self): - """ - :return: Returns relative multiplier for device - """ - return self.channels[self._channel].rel_multiplier - - @rel_multiplier.setter - def rel_multiplier(self, multiplier): - """ - :param multiplier: sets the relative multiplier for the device - """ - self.channels[self._channel].rel_multiplier = multiplier - - @property - def max_reading_multiplier(self): - """ - :return: return max reading multiplier - """ - return self.channels[self._channel].max_multiplier - - - @max_reading_multiplier.setter - def max_reading_multiplier(self, multiplier): - """ - :param multiplier: sets the max reading multiplier for the device - """ - self.channels[self._channel].max_multiplier = multiplier - - @property - def rel_set_point(self): - """ - :return: return relative mode setpoint for Lakeshore 460 - """ - return self.channels[self._channel].rel_setpoint - - @rel_set_point.setter - def rel_set_point(self, setpoint): - """ - :param setpoint: sets the relative mode set point - """ - self.channels[self._channel].rel_setpoint = setpoint - - @property - def max_reading(self): - """ - :return: returns max reading for Lakeshore 460 - """ - return self._max_reading - - @max_reading.setter - def max_reading(self, reading): - """ - :param reading: sets max_reading for device - """ - self._max_reading = reading - - @property - def rel_mode_reading(self): - """ - :return: Return relative mode reading - """ - return self._rel_mode_reading - - @rel_mode_reading.setter - def rel_mode_reading(self, rel_mode): - """ - :param rel_mode: sets relative mode reading - """ - self._rel_mode_reading = rel_mode - - @property - def rel_mode_reading_multiplier(self): - """ - :return: Return relative mode multiplier - """ - return self.channels[self._channel].rel_mode_multiplier - - @rel_mode_reading_multiplier.setter - def rel_mode_reading_multiplier(self, multiplier): - """ - :param multiplier: sets relative mode multiplier - """ - self.channels[self._channel].rel_mode_multiplier = multiplier - - @property - def magnetic_field_multiplier(self): - """ - :return: Returns Magnetic Field Multiplier - """ - return self.channels[self._channel].magnetic_field_multiplier - - @magnetic_field_multiplier.setter - def magnetic_field_multiplier(self, multiplier): - """ - :param multiplier: Sets Magnetic field multiplier i.e micro, kilo, etc. - """ - self.channels[self._channel].magnetic_field_multiplier = multiplier - @property - def magnetic_field_reading(self): + def pressure(self): """ - :return: Magnetic field Reading + Returns: the pressure """ - return self._magnetic_field_reading + return self._pressure - @magnetic_field_reading.setter - def magnetic_field_reading(self, field): + @pressure.setter + def pressure(self, pressure): """ - :param field: sets Magnetic Field Reading + :param pressure: set the pressure + :return: """ - self._magnetic_field_reading = field + self._pressure = pressure diff --git a/lewis_emulators/lakeshore460/interfaces/stream_interface.py b/lewis_emulators/lakeshore460/interfaces/stream_interface.py index 51bd8f1f..cce22683 100644 --- a/lewis_emulators/lakeshore460/interfaces/stream_interface.py +++ b/lewis_emulators/lakeshore460/interfaces/stream_interface.py @@ -1,164 +1,48 @@ -from lewis.adapters.stream import StreamAdapter +from lewis.adapters.stream import StreamInterface from lewis_emulators.utils.command_builder import CmdBuilder -from lewis.core.logging import has_log -@has_log -class Lakeshore460StreamInterface(StreamAdapter): - in_terminator="\r\n" - out_terminator="\r\n" +class Lakeshore460StreamInterface(StreamInterface): + """ + Stream interface for the serial port + """ commands = { - CmdBuilder("get_IDN").escape("*IDN?").build(), - CmdBuilder("get_unit").escape("UNIT?").build(), - CmdBuilder("set_unit").escape("UNIT ").arg("G|T").build(), - CmdBuilder("get_on_off").escape("ONOFF?").build(), - CmdBuilder("set_on_off").escape("ONOFF ").arg("0|1").build(), - CmdBuilder("get_ac_dc_field_reading").escape("ACDC?").build(), - CmdBuilder("set_ac_dc_field_reading").escape("ACDC ").arg("0|1").build(), - CmdBuilder("get_prms_reading").escape("PRMS?").build(), - CmdBuilder("set_prms_reading").escape("PRMS ").arg("0|1").build(), - CmdBuilder("set_display_filter").escape("FILT ").arg("0|1").build(), - CmdBuilder("get_display_filter").escape("FILT?").build(), - CmdBuilder("set_max_hold").escape("MAX ").arg("0|1").build(), - CmdBuilder("get_max_hold").escape("MAX?").build(), - CmdBuilder("set_auto_range").escape("AUTO ").arg("0|1").build(), - CmdBuilder("get_auto_range").escape("AUTO?").build(), - CmdBuilder("set_relative_mode").escape("REL ").arg("0|1").build(), - CmdBuilder("get_relative_mode").escape("REL?").build(), - CmdBuilder("get_all_fields").escape("ALLF?").build(), - CmdBuilder("get_source").escape("VSRC?").build(), - CmdBuilder("set_source").escape("VSRC ").digit().build(), - CmdBuilder("get_channel").escape("CHNL?").build(), - CmdBuilder("set_channel").escape("CHNL ").arg("X|Y|Z|V").build(), - CmdBuilder("get_display_filter_window").escape("FWIN?").build(), - CmdBuilder("set_display_filter_window").escape("FWIN ").int().build(), - CmdBuilder("set_filter_points").escape("FNUM ").int().build(), - CmdBuilder("get_filter_points").escape("FNUM?").build(), - CmdBuilder("get_manual_range").escape("RANGE?").build(), - CmdBuilder("set_manual_range").escape("RANGE ").int().build(), - CmdBuilder("read_relative_mode_set_point_multiplier").escape("RELSM?").build(), - CmdBuilder("read_relative_mode_set_point").escape("RELS?").build(), - CmdBuilder("set_relative_mode_set_point").escape("RELS ").float().build(), - CmdBuilder("read_max_reading").escape("MAXR?").build(), - CmdBuilder("read_max_reading_multiplier").escape("MAXRM?").build(), - CmdBuilder("get_relative_mode_reading_multiplier").escape("RELRM?").build(), - CmdBuilder("get_relative_mode_reading").escape("RELR").build(), - CmdBuilder("get_magnetic_field_reading_multiplier").escape("FIELDM?").build(), - CmdBuilder("get_magnetic_field_reading").escape("FIELD?").build() + CmdBuilder("get_pressure").stx().arg("[A-Fa-f0-9]+").escape("r").build() } - def handle_error(self,request, error): - self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) - print("An error occurred at request" + repr(request) + ": " + repr(error)) - - def get_IDN(self): - return "{0}".format(self._device.idn) - - def get_unit(self): - return "{0}".format(self._device.unit) - - def set_unit(self, unit): - self._device.unit = unit - - def get_on_off(self): - return self._device.status - - def set_on_off(self, status): - self._device.status = status - - def get_ac_dc_field_reading(self): - return "{0}".format(self._device.mode) - - def set_ac_dc_field_reading(self, mode): - self._device.mode = mode - - def get_prms_reading(self): - return "{0}".format(self._device.prms) - - def set_prms_reading(self, prms): - self._device.prms = prms - - def get_display_filter(self): - return "{0}".format(self._device.filter) - - def set_display_filter(self, filter): - self._device.filter = filter - - def set_max_hold(self, max_hold): - self._device.max_hold = max_hold - - def get_max_hold(self): - return "{0}".format(self._device.max_hold) - - def set_relative_mode(self, rel_mode): - self._device.rel_mode = rel_mode - - def get_relative_mode(self): - return "{0}".format(self._device.rel_mode) - - def set_auto_range(self, auto_range): - self._device.auto_range = auto_range - - def get_auto_range(self): - return "{0}".format(self._device.auto_range) - - def set_manual_range(self, range): - self._device.manual_range = range - - def get_manual_range(self): - return "{0}".format(self._device.manual_range) - - def get_all_fields(self): - return "{0}".format(self._device.total_fields) - - def set_source(self, source): - self._device.source = source - - def get_source(self): - return "{0}".format(self._device.source) - - def set_channel(self, channel): - self._device.channel = channel - - def get_channel(self): - return "{0}".format(self._device.channel) - - def get_display_filter_window(self): - return "{0}".format(self._device.display_filter) - - def set_display_filter_window(self, percentage): - self._device.display_filter = percentage - - def set_filter_points(self, points): - self._device.filter_points = points - - def get_filter_points(self): - return "{0}".format(self._device.filter_points) - - def read_relative_mode_set_point_multiplier(self): - return "{0}".format(self._device.rel_multiplier) - - def read_relative_mode_set_point(self): - return "{0}".format(self._device.rel_set_point) - - def set_relative_mode_set_point(self, setpoint): - self._device.rel_set_point = setpoint - - def read_max_reading(self): - return "{0}".format( self._device.max_reading) - - def read_max_reading_multiplier(self): - return "{0}".format( self._device.max_reading_multiplier) - - def get_relative_mode_reading(self): - return "{0}".format(self._device.rel_mode_reading) - - def get_relative_mode_reading_multiplier(self): - return "{0}".format(self._device.rel_mode_reading_multiplier) - - def get_magnetic_field_reading(self): - return "{0}".format(self._device.magnetic_field_reading) - - def get_magnetic_field_reading_multiplier(self): - return "{0}".format(self._device.magnetic_field_reading_multiplier) + in_terminator = chr(3) + out_terminator = chr(3) + + def handle_error(self, request, error): + """ + If command is not recognised print and error + + Args: + request: requested string + error: problem + + """ + print "An error occurred at request " + repr(request) + ": " + repr(error) + + def get_pressure(self, address): + + """ + Gets the current pressure + + :param address: address of request + Returns: pressure in correct format if pressure has a value; if None returns None as if it is disconnected + + """ + if address.upper() != self._device.address.upper(): + print "unknown address {0}".format(address) + return None + print str(self._device.pressure) + if self._device.pressure is None: + return None + else: + try: + return "{stx}{pressure:+8.3f}".format(stx=chr(2), pressure=self._device.pressure) + except ValueError: + # pressure contains string probably OR (over range) or UR (under range) + return "{stx}{pressure:8s}".format(stx=chr(2), pressure=self._device.pressure) From 01ae1866e9f6f1225de3487cd9ecf6e2e5881feb Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Mon, 18 Dec 2017 16:46:13 +0000 Subject: [PATCH 0419/1466] Movement position --- lewis_emulators/sm300/device.py | 35 +++++++++++++++++-- .../sm300/interfaces/stream_interface.py | 33 +++++++++++++---- 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py index ffb8fc63..7bf66535 100644 --- a/lewis_emulators/sm300/device.py +++ b/lewis_emulators/sm300/device.py @@ -5,11 +5,13 @@ class Axis(): - def __init__(self): + def __init__(self, axis_label): + self.rbv_error = None self.rbv = 10.0 self.sp = self.rbv self.moving = False self.speed = 0.1 + self.axis_label = axis_label def home(self): self.sp = 0.0 @@ -19,6 +21,12 @@ def simulate(self, dt): self.rbv = approaches.linear(self.rbv, self.sp, self.speed, dt) self.moving = self.rbv == self.sp + def get_label_and_position(self): + if self.rbv_error is not None: + return self.rbv_error + else: + return "{label}{0:.0f}".format(self.rbv, label=self.axis_label) + class SimulatedSm300(StateMachineDevice): @@ -29,8 +37,10 @@ def _initialize_data(self): # Is the device initialised, if not it won't talk to me self.initialised = False - self.x_axis = Axis() - self.y_axis = Axis() + self.x_axis = Axis("X") + self.y_axis = Axis("Y") + self.is_moving = None # let the axis report its motion + self.is_moving_error = False def _get_state_handlers(self): return { @@ -44,6 +54,9 @@ def _get_transition_handlers(self): return OrderedDict([ ]) + def reset(self): + self._initialize_data() + @property def x_axis_rbv(self): return self.x_axis.rbv @@ -67,3 +80,19 @@ def x_axis_sp(self): @x_axis_sp.setter def x_axis_sp(self, position): self.x_axis.sp = position + + @property + def y_axis_sp(self): + return self.y_axis.rbv + + @y_axis_sp.setter + def y_axis_sp(self, position): + self.y_axis.sp = position + + @property + def x_axis_rbv_error(self): + return self.x_axis.rbv_error + + @x_axis_rbv_error.setter + def x_axis_rbv_error(self, error_return): + self.x_axis.rbv_error = error_return diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index 8f011f9d..a82e2e31 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -36,10 +36,16 @@ def handle_error(self, request, error): self.log.error("An error occurred at request " + repr(request) + ": " + repr(error)) def get_position(self): - self.device.x_axis.rbv += 0.1 - self.device.x_axis.sp += 0.1 - self.log.info("Send position {} {}".format(self.device.x_axis.rbv, self.device.y_axis.rbv)) - return "{ACK}{STX}X{0:.0f},Y{1:.0f}".format(self.device.x_axis.rbv, self.device.y_axis.rbv, **COMMAND_CHARS) + """ + Get position of the motor axes + Returns: string indicating postion of motors + + """ + + x_axis_return = self.device.x_axis.get_label_and_position() + y_axis_return = self.device.y_axis.get_label_and_position() + self.log.info("Send position {} {}".format(x_axis_return, y_axis_return)) + return "{ACK}{STX}{0},{1}".format(x_axis_return, y_axis_return, **COMMAND_CHARS) def home_axis(self, axis): if axis == "X": @@ -49,8 +55,21 @@ def home_axis(self, axis): return "{ACK}" def get_status(self): - if self.device.x_axis.moving or self.device.y_axis.moving: - status = "N" + """ + + Returns: the moving status of the motor, N not at position, P at position, E error + + """ + if self.device.is_moving_error: + status = "E" else: - status = "P" + is_moving = self.device.x_axis.moving or self.device.y_axis.moving + if self.device.is_moving is not None: + is_moving = self.device.is_moving + + if is_moving: + status = "N" + else: + status = "P" + self.log.info("Send motor status {}".format(status)) return "{ACK}{STX}{status}".format(status=status, **COMMAND_CHARS) From 25136e0bbfb6a6aa70ea9f0a4bdc02e6a3611504 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 09:21:40 +0000 Subject: [PATCH 0420/1466] Add template emulator --- lewis_emulators/triton/__init__.py | 5 ++++ lewis_emulators/triton/device.py | 25 +++++++++++++++++++ lewis_emulators/triton/interfaces/__init__.py | 3 +++ .../triton/interfaces/stream_interface.py | 12 +++++++++ lewis_emulators/triton/states.py | 5 ++++ 5 files changed, 50 insertions(+) create mode 100644 lewis_emulators/triton/__init__.py create mode 100644 lewis_emulators/triton/device.py create mode 100644 lewis_emulators/triton/interfaces/__init__.py create mode 100644 lewis_emulators/triton/interfaces/stream_interface.py create mode 100644 lewis_emulators/triton/states.py diff --git a/lewis_emulators/triton/__init__.py b/lewis_emulators/triton/__init__.py new file mode 100644 index 00000000..c68adebe --- /dev/null +++ b/lewis_emulators/triton/__init__.py @@ -0,0 +1,5 @@ +from .device import SimulatedTriton +from ..lewis_versions import LEWIS_LATEST + +framework_version = LEWIS_LATEST +__all__ = ['SimulatedTriton'] diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py new file mode 100644 index 00000000..1c3765d9 --- /dev/null +++ b/lewis_emulators/triton/device.py @@ -0,0 +1,25 @@ +from collections import OrderedDict +from states import DefaultState +from lewis.devices import StateMachineDevice + + +class SimulatedTriton(StateMachineDevice): + + def _initialize_data(self): + """ + Initialize all of the device's attributes. + """ + pass + + def _get_state_handlers(self): + return { + 'default': DefaultState(), + } + + def _get_initial_state(self): + return 'default' + + def _get_transition_handlers(self): + return OrderedDict([ + ]) + diff --git a/lewis_emulators/triton/interfaces/__init__.py b/lewis_emulators/triton/interfaces/__init__.py new file mode 100644 index 00000000..605c5739 --- /dev/null +++ b/lewis_emulators/triton/interfaces/__init__.py @@ -0,0 +1,3 @@ +from .stream_interface import TritonStreamInterface + +__all__ = ['TritonStreamInterface'] diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py new file mode 100644 index 00000000..e08e7ba9 --- /dev/null +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -0,0 +1,12 @@ +from lewis.adapters.stream import StreamInterface, Cmd + + +class TritonStreamInterface(StreamInterface): + + # Commands that we expect via serial during normal operation + commands = { + Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + } + + def catch_all(self): + pass diff --git a/lewis_emulators/triton/states.py b/lewis_emulators/triton/states.py new file mode 100644 index 00000000..e4ca48e8 --- /dev/null +++ b/lewis_emulators/triton/states.py @@ -0,0 +1,5 @@ +from lewis.core.statemachine import State + + +class DefaultState(State): + pass From 249bd0c5d60e6491942b8f3e630891c8ba0abba0 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 19 Dec 2017 09:36:53 +0000 Subject: [PATCH 0421/1466] Add set the setp points --- .../sm300/interfaces/stream_interface.py | 6 ++++++ lewis_emulators/utils/command_builder.py | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index a82e2e31..00243b8e 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -20,6 +20,7 @@ def __init__(self): CmdBuilder(self.get_position).escape("LQ").build(), # Catch-all command for debugging CmdBuilder(self.home_axis).escape("BR").char().build(), # Catch-all command for debugging CmdBuilder(self.get_status).escape("LM").build(), # Catch-all command for debugging + CmdBuilder(self.set_position).escape("B/").spaces().escape("X").int().spaces().escape("Y").int() } self.device = self._device @@ -73,3 +74,8 @@ def get_status(self): status = "P" self.log.info("Send motor status {}".format(status)) return "{ACK}{STX}{status}".format(status=status, **COMMAND_CHARS) + + def set_position(self, x_position, y_position): + self.device.x_axis.sp = x_position + self.device.y_axis.sp = y_position + return "{ACK}".format(**COMMAND_CHARS) \ No newline at end of file diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index e4781a47..14d95b05 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -61,6 +61,19 @@ def regex(self, regex): self._reg_ex += regex + self._ignore return self + def spaces(self, at_least_one=False): + """ + Add a regex for any number of spaces + Args: + at_least_one: true there must be at least one space; false there can be any nnumber including zero + + Returns: builder + + """ + wildchard = "*" if at_least_one else "+" + + self._reg_ex += "\w" + wildchard + self._ignore + def arg(self, arg_regex): """ Add an argument to the command. From f6f1eb5aa9922e67bfc46088ecb8cac4b2f7438a Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 11:09:57 +0000 Subject: [PATCH 0422/1466] Add correct terminators and one command to stream interface --- lewis_emulators/triton/interfaces/stream_interface.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index e08e7ba9..7c07b2dc 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -5,8 +5,11 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - Cmd("catch_all", "^#9.*$"), # Catch-all command for debugging + Cmd("get_mc_uid", "^READ:SYS:DR:CHAN:MC$"), # Catch-all command for debugging } - def catch_all(self): - pass + in_terminator = "\r\n" + out_terminator = "\r\n" + + def get_mc_uid(self): + return "STAT:SYS:DR:CHAN:MC:{}".format("mix_chamber_name") From 100a2abc1e08500bc96d0efa6213143e5021eecf Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 12:40:56 +0000 Subject: [PATCH 0423/1466] PID controls working --- lewis_emulators/triton/device.py | 26 ++++++++++++- .../triton/interfaces/stream_interface.py | 37 ++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 1c3765d9..5280cdb5 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -3,13 +3,20 @@ from lewis.devices import StateMachineDevice +SUBSYSTEM_NAMES = { + "mixing chamber": "mix_chamber_name", + } + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): """ Initialize all of the device's attributes. """ - pass + self.p = 0 + self.i = 0 + self.d = 0 def _get_state_handlers(self): return { @@ -23,3 +30,20 @@ def _get_transition_handlers(self): return OrderedDict([ ]) + def set_p(self, value): + self.p = value + + def set_i(self, value): + self.i = value + + def set_d(self, value): + self.d = value + + def get_p(self): + return self.p + + def get_i(self): + return self.i + + def get_d(self): + return self.d diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 7c07b2dc..96d7048e 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,15 +1,48 @@ from lewis.adapters.stream import StreamInterface, Cmd +from lewis_emulators.utils.command_builder import CmdBuilder +from lewis_emulators.triton.device import SUBSYSTEM_NAMES class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - Cmd("get_mc_uid", "^READ:SYS:DR:CHAN:MC$"), # Catch-all command for debugging + CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), + + # PID setpoints + CmdBuilder("set_p").escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_i").escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_d").escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + + # PID readbacks + CmdBuilder("get_p").escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_i").escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_d").escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), } in_terminator = "\r\n" out_terminator = "\r\n" def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}".format("mix_chamber_name") + return "STAT:SYS:DR:CHAN:MC:{}".format(SUBSYSTEM_NAMES["mixing chamber"]) + + def set_p(self, value): + self.device.set_p(value) + return "ok" + + def set_i(self, value): + self.device.set_i(value) + return "ok" + + def set_d(self, value): + self.device.set_d(value) + return "ok" + + def get_p(self): + return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) + + def get_i(self): + return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) + + def get_d(self): + return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) From aa80eefcdfa4ce3efd1b91f8535cfe298510f3ad Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 14:16:31 +0000 Subject: [PATCH 0424/1466] Add heater range/temp setpoint --- lewis_emulators/triton/device.py | 15 +++++++++++ .../triton/interfaces/stream_interface.py | 26 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 5280cdb5..04863226 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -14,6 +14,9 @@ def _initialize_data(self): """ Initialize all of the device's attributes. """ + self.temperature_setpoint = 0 + self.heater_range = 0 + self.p = 0 self.i = 0 self.d = 0 @@ -47,3 +50,15 @@ def get_i(self): def get_d(self): return self.d + + def get_temperature_setpoint(self): + return self.temperature_setpoint + + def set_temperature_setpoint(self, value): + self.temperature_setpoint = value + + def get_heater_range(self): + return self.heater_range + + def set_heater_range(self, value): + self.heater_range = value diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 96d7048e..db94a1b5 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -18,6 +18,18 @@ class TritonStreamInterface(StreamInterface): CmdBuilder("get_p").escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), CmdBuilder("get_i").escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), CmdBuilder("get_d").escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Setpoint temperature + CmdBuilder("set_temperature_setpoint") + .escape("SET:DEV:{}:TEMP:LOOP:TSET:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("get_temperature_setpoint") + .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Heater range + CmdBuilder("set_heater_range") + .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("get_heater_range") + .escape("READ:DEV:{}:TEMP:LOOP:RANGE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), } in_terminator = "\r\n" @@ -46,3 +58,17 @@ def get_i(self): def get_d(self): return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) + + def set_temperature_setpoint(self, value): + self.device.set_temperature_setpoint(value) + + def get_temperature_setpoint(self): + return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], + self.device.get_temperature_setpoint()) + + def set_heater_range(self, value): + self.device.set_heater_range(value) + + def get_heater_range(self): + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(SUBSYSTEM_NAMES["mixing chamber"], + self.device.get_heater_range()) From 630a511438c0a3fb721cca5d2c449c09028e4ca8 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 16:40:22 +0000 Subject: [PATCH 0425/1466] Get heater power --- lewis_emulators/triton/device.py | 8 ++- .../triton/interfaces/stream_interface.py | 64 ++++++++++++++----- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 04863226..4d1815e2 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -4,8 +4,9 @@ SUBSYSTEM_NAMES = { - "mixing chamber": "mix_chamber_name", - } + "mixing chamber": "mix_chamber_name", + "heater": "H5" +} class SimulatedTriton(StateMachineDevice): @@ -17,6 +18,9 @@ def _initialize_data(self): self.temperature_setpoint = 0 self.heater_range = 0 + self.heater_power = 1 + self.heater_power_units = "mA" + self.p = 0 self.i = 0 self.d = 0 diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index db94a1b5..c2a93c54 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,23 +1,32 @@ from lewis.adapters.stream import StreamInterface, Cmd +from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import SUBSYSTEM_NAMES +@has_log class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { - CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), + CmdBuilder("get_mc_uid") + .escape("READ:SYS:DR:CHAN:MC").build(), # PID setpoints - CmdBuilder("set_p").escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("set_i").escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("set_d").escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_p") + .escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_i") + .escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + CmdBuilder("set_d") + .escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), # PID readbacks - CmdBuilder("get_p").escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("get_i").escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("get_d").escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_p") + .escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_i") + .escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_d") + .escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), # Setpoint temperature CmdBuilder("set_temperature_setpoint") @@ -30,13 +39,27 @@ class TritonStreamInterface(StreamInterface): .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), CmdBuilder("get_heater_range") .escape("READ:DEV:{}:TEMP:LOOP:RANGE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Heater type + CmdBuilder("get_heater_type") + .escape("READ:DEV:{}:TEMP:LOOP:HTR".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Get heater power + CmdBuilder("get_heater_power") + .escape("READ:DEV:{}:HTR:SIG:POWR".format(SUBSYSTEM_NAMES["heater"])).build(), } in_terminator = "\r\n" out_terminator = "\r\n" + def handle_error(self, request, error): + err = "Request: {}, error: {}".format(request, error) + print(err) + return err + def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}".format(SUBSYSTEM_NAMES["mixing chamber"]) + return "STAT:SYS:DR:CHAN:MC:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"]) def set_p(self, value): self.device.set_p(value) @@ -51,24 +74,35 @@ def set_d(self, value): return "ok" def get_p(self): - return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) + return "STAT:DEV:{}:TEMP:LOOP:P:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) def get_i(self): - return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) + return "STAT:DEV:{}:TEMP:LOOP:I:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) def get_d(self): - return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) + return "STAT:DEV:{}:TEMP:LOOP:D:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) def set_temperature_setpoint(self, value): self.device.set_temperature_setpoint(value) def get_temperature_setpoint(self): - return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], - self.device.get_temperature_setpoint()) + return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_temperature_setpoint()) def set_heater_range(self, value): self.device.set_heater_range(value) def get_heater_range(self): - return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(SUBSYSTEM_NAMES["mixing chamber"], - self.device.get_heater_range()) + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_heater_range()) + + def get_heater_type(self): + return "STAT:DEV:{}:TEMP:LOOP:HTR:{}" \ + .format(SUBSYSTEM_NAMES["mixing chamber"], SUBSYSTEM_NAMES["heater"]) + + def get_heater_power(self): + return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ + .format(SUBSYSTEM_NAMES["heater"], self.device.heater_power, self.device.heater_power_units) From 86d0f73fb27f3b2abaec2c87cae78747707c2083 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 19 Dec 2017 17:22:32 +0000 Subject: [PATCH 0426/1466] Add commands --- lewis_emulators/triton/device.py | 2 ++ lewis_emulators/triton/interfaces/stream_interface.py | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 4d1815e2..619d1d19 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -25,6 +25,8 @@ def _initialize_data(self): self.i = 0 self.d = 0 + self.closed_loop = False + def _get_state_handlers(self): return { 'default': DefaultState(), diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index c2a93c54..d2aa48f4 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -47,6 +47,10 @@ class TritonStreamInterface(StreamInterface): # Get heater power CmdBuilder("get_heater_power") .escape("READ:DEV:{}:HTR:SIG:POWR".format(SUBSYSTEM_NAMES["heater"])).build(), + + # Loop mode + CmdBuilder("get_closed_loop_mode") + .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), } in_terminator = "\r\n" @@ -87,6 +91,7 @@ def get_d(self): def set_temperature_setpoint(self, value): self.device.set_temperature_setpoint(value) + return "ok" def get_temperature_setpoint(self): return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ @@ -94,6 +99,7 @@ def get_temperature_setpoint(self): def set_heater_range(self, value): self.device.set_heater_range(value) + return "ok" def get_heater_range(self): return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ @@ -106,3 +112,7 @@ def get_heater_type(self): def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ .format(SUBSYSTEM_NAMES["heater"], self.device.heater_power, self.device.heater_power_units) + + def get_closed_loop_mode(self): + return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ + .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.closed_loop else "OFF") From 524f8ad7a206456497e6e01ab18bc86c6056a078 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 09:54:40 +0000 Subject: [PATCH 0427/1466] Add emulation for valve states --- lewis_emulators/triton/device.py | 17 +++++++++++ .../triton/interfaces/stream_interface.py | 29 +++++++++++++++---- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 619d1d19..5cf9277e 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -9,6 +9,15 @@ } +class ValveStates(object): + """ + Enum representing the possible states of a valve. + """ + OPEN = 0 + CLOSED = 1 + NOT_FOUND = 2 + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): @@ -27,6 +36,11 @@ def _initialize_data(self): self.closed_loop = False + self.valves = [ValveStates.CLOSED] * 10 + + def set_valve_state_backdoor(self, valve, newstate): + self.valves[int(valve) - 1] = int(newstate) + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -68,3 +82,6 @@ def get_heater_range(self): def set_heater_range(self, value): self.heater_range = value + + def get_valve_state(self, valve): + return self.valves[valve-1] diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index d2aa48f4..7860751a 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -2,6 +2,7 @@ from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder from lewis_emulators.triton.device import SUBSYSTEM_NAMES +from lewis_emulators.triton.device import ValveStates @has_log @@ -51,6 +52,10 @@ class TritonStreamInterface(StreamInterface): # Loop mode CmdBuilder("get_closed_loop_mode") .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + + # Valve state + CmdBuilder("get_valve_state") + .escape("READ:DEV:V").int().escape(":VALV:SIG:STATE").build(), } in_terminator = "\r\n" @@ -59,6 +64,7 @@ class TritonStreamInterface(StreamInterface): def handle_error(self, request, error): err = "Request: {}, error: {}".format(request, error) print(err) + self.log.error(err) return err def get_mc_uid(self): @@ -66,15 +72,15 @@ def get_mc_uid(self): .format(SUBSYSTEM_NAMES["mixing chamber"]) def set_p(self, value): - self.device.set_p(value) + self.device.set_p(float(value)) return "ok" def set_i(self, value): - self.device.set_i(value) + self.device.set_i(float(value)) return "ok" def set_d(self, value): - self.device.set_d(value) + self.device.set_d(float(value)) return "ok" def get_p(self): @@ -90,7 +96,7 @@ def get_d(self): .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) def set_temperature_setpoint(self, value): - self.device.set_temperature_setpoint(value) + self.device.set_temperature_setpoint(float(value)) return "ok" def get_temperature_setpoint(self): @@ -98,7 +104,7 @@ def get_temperature_setpoint(self): .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_temperature_setpoint()) def set_heater_range(self, value): - self.device.set_heater_range(value) + self.device.set_heater_range(float(value)) return "ok" def get_heater_range(self): @@ -116,3 +122,16 @@ def get_heater_power(self): def get_closed_loop_mode(self): return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.closed_loop else "OFF") + + def get_valve_state(self, valve): + + state = self.device.get_valve_state(int(valve)) + + if state == ValveStates.CLOSED: + response = "CLOSE" + elif state == ValveStates.OPEN: + response = "OPEN" + else: + response = "NOT_FOUND" + + return "STAT:DEV:V{}:VALV:SIG:STATE:{}".format(valve, response) From 8f471a27b50329e17cdb28c690161ab4597685de Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 11:31:57 +0000 Subject: [PATCH 0428/1466] Channel enablement --- lewis_emulators/triton/device.py | 8 +++++++ .../triton/interfaces/stream_interface.py | 24 +++++++++++++++++-- lewis_emulators/utils/command_builder.py | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 5cf9277e..7e9ea462 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -38,6 +38,8 @@ def _initialize_data(self): self.valves = [ValveStates.CLOSED] * 10 + self.channels_enabled = [True] * 6 + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -85,3 +87,9 @@ def set_heater_range(self, value): def get_valve_state(self, valve): return self.valves[valve-1] + + def is_channel_enabled(self, chan): + return self.channels_enabled[chan-1] + + def set_channel_enabled(self, chan, newstate): + self.channels_enabled[chan-1] = newstate diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 7860751a..b3a3ff21 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -56,13 +56,21 @@ class TritonStreamInterface(StreamInterface): # Valve state CmdBuilder("get_valve_state") .escape("READ:DEV:V").int().escape(":VALV:SIG:STATE").build(), + + # Channel enablement + CmdBuilder("get_channel_enabled") + .escape("READ:DEV:T").int().escape(":TEMP:MEAS:ENAB").build(), + CmdBuilder("set_channel_enabled") + .escape("SET:DEV:T").int().escape(":TEMP:MEAS:ENAB:").any().build(), } in_terminator = "\r\n" out_terminator = "\r\n" def handle_error(self, request, error): - err = "Request: {}, error: {}".format(request, error) + err = "Request: {}, error: {}. \n\nAvailable commands: {}"\ + .format(request, error, [c.pattern for c in self.commands]) + print(err) self.log.error(err) return err @@ -131,7 +139,19 @@ def get_valve_state(self, valve): response = "CLOSE" elif state == ValveStates.OPEN: response = "OPEN" - else: + elif state == ValveStates.NOT_FOUND: response = "NOT_FOUND" + else: + raise ValueError("Invalid valve state: {}".format(state)) return "STAT:DEV:V{}:VALV:SIG:STATE:{}".format(valve, response) + + def get_channel_enabled(self, channel): + return "STAT:DEV:T{}:TEMP:MEAS:ENAB:{}"\ + .format(channel, "ON" if self.device.is_channel_enabled(int(channel)) else "OFF") + + def set_channel_enabled(self, channel, newstate): + if newstate not in ["ON", "OFF"]: + raise ValueError("New state '{}' not valid.".format(newstate)) + self.device.set_channel_enabled(int(channel), str(newstate) == "ON") + return "ok" diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index b6437dff..e3fc624f 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -24,7 +24,7 @@ class CmdBuilder(object): >>> CmdBuilder("set_pres").escape("pres?").enq().build() """ - def __init__(self, target_method, arg_sep=",", ignore=""): + def __init__(self, target_method, arg_sep="", ignore=""): """ Create a builder. Use build to create the final object From e525943e785e37672cf3952fb55787e82fcfd9d3 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 13:45:48 +0000 Subject: [PATCH 0429/1466] Add device status --- lewis_emulators/triton/device.py | 5 +++++ lewis_emulators/triton/interfaces/stream_interface.py | 11 ++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 7e9ea462..1eb0e25d 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -40,6 +40,8 @@ def _initialize_data(self): self.channels_enabled = [True] * 6 + self.status = "This is a device status message." + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -93,3 +95,6 @@ def is_channel_enabled(self, chan): def set_channel_enabled(self, chan, newstate): self.channels_enabled[chan-1] = newstate + + def get_status(self): + return self.status diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index b3a3ff21..16daf756 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -62,15 +62,17 @@ class TritonStreamInterface(StreamInterface): .escape("READ:DEV:T").int().escape(":TEMP:MEAS:ENAB").build(), CmdBuilder("set_channel_enabled") .escape("SET:DEV:T").int().escape(":TEMP:MEAS:ENAB:").any().build(), + + # Status + CmdBuilder("get_status") + .escape("READ:SYS:DR:STATUS").build(), } in_terminator = "\r\n" out_terminator = "\r\n" def handle_error(self, request, error): - err = "Request: {}, error: {}. \n\nAvailable commands: {}"\ - .format(request, error, [c.pattern for c in self.commands]) - + err = "Request: {}, error: {}." print(err) self.log.error(err) return err @@ -155,3 +157,6 @@ def set_channel_enabled(self, channel, newstate): raise ValueError("New state '{}' not valid.".format(newstate)) self.device.set_channel_enabled(int(channel), str(newstate) == "ON") return "ok" + + def get_status(self): + return "STAT:SYS:DR:STATUS:{}".format(self.device.get_status()) From 7ff73844e5deb5c7a679c625073c400cc987ced6 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 13:59:00 +0000 Subject: [PATCH 0430/1466] Add automation --- lewis_emulators/triton/device.py | 4 ++++ lewis_emulators/triton/interfaces/stream_interface.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 1eb0e25d..cdfad983 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -41,6 +41,7 @@ def _initialize_data(self): self.channels_enabled = [True] * 6 self.status = "This is a device status message." + self.automation = "This is the automation status" def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -98,3 +99,6 @@ def set_channel_enabled(self, chan, newstate): def get_status(self): return self.status + + def get_automation(self): + return self.automation diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 16daf756..632246f6 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -66,6 +66,10 @@ class TritonStreamInterface(StreamInterface): # Status CmdBuilder("get_status") .escape("READ:SYS:DR:STATUS").build(), + + # Automation + CmdBuilder("get_automation") + .escape("READ:SYS:DR:ACTN").build(), } in_terminator = "\r\n" @@ -160,3 +164,6 @@ def set_channel_enabled(self, channel, newstate): def get_status(self): return "STAT:SYS:DR:STATUS:{}".format(self.device.get_status()) + + def get_automation(self): + return "STAT:SYS:DR:ACTN:{}".format(self.device.get_automation()) From 939399c99b9829d1ed1d5f31d2c69df696bee8bc Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 20 Dec 2017 14:21:51 +0000 Subject: [PATCH 0431/1466] Add get and set position as steps --- lewis_emulators/sm300/device.py | 17 +++++-- .../sm300/interfaces/stream_interface.py | 48 ++++++++++++++----- lewis_emulators/sm300/states.py | 3 +- lewis_emulators/utils/command_builder.py | 29 ++++++----- 4 files changed, 66 insertions(+), 31 deletions(-) diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py index 7bf66535..f8f2a8f6 100644 --- a/lewis_emulators/sm300/device.py +++ b/lewis_emulators/sm300/device.py @@ -1,9 +1,12 @@ from collections import OrderedDict + +from lewis.core.logging import has_log + from states import DefaultState from lewis.devices import StateMachineDevice from lewis.core import approaches - +@has_log class Axis(): def __init__(self, axis_label): self.rbv_error = None @@ -18,8 +21,9 @@ def home(self): self.moving = True def simulate(self, dt): + self.rbv = approaches.linear(self.rbv, self.sp, self.speed, dt) - self.moving = self.rbv == self.sp + self.moving = self.rbv != self.sp def get_label_and_position(self): if self.rbv_error is not None: @@ -36,9 +40,12 @@ def _initialize_data(self): """ # Is the device initialised, if not it won't talk to me self.initialised = False - - self.x_axis = Axis("X") - self.y_axis = Axis("Y") + self.axes = { + "X": Axis("X"), + "Y": Axis("Y") + } + self.x_axis = self.axes["X"] + self.y_axis = self.axes["Y"] self.is_moving = None # let the axis report its motion self.is_moving_error = False diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index 00243b8e..e9c49951 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -9,7 +9,7 @@ class Sm300StreamInterface(StreamInterface): in_terminator = EOT - out_terminator = EOT + out_terminator = "" def __init__(self): @@ -17,10 +17,12 @@ def __init__(self): # Commands that we expect via serial during normal operation self.commands = { - CmdBuilder(self.get_position).escape("LQ").build(), # Catch-all command for debugging - CmdBuilder(self.home_axis).escape("BR").char().build(), # Catch-all command for debugging - CmdBuilder(self.get_status).escape("LM").build(), # Catch-all command for debugging - CmdBuilder(self.set_position).escape("B/").spaces().escape("X").int().spaces().escape("Y").int() + CmdBuilder(self.get_position).escape("LQ").build(), + CmdBuilder(self.get_position_as_steps).escape("LI").char().build(), + CmdBuilder(self.home_axis).escape("BR").char().build(), + CmdBuilder(self.get_status).escape("LM").build(), + CmdBuilder(self.set_position).escape("B/").spaces().escape("X").int().spaces().escape("Y").int().build(), + CmdBuilder(self.set_position_as_steps).escape("B").char().int().build() } self.device = self._device @@ -46,13 +48,11 @@ def get_position(self): x_axis_return = self.device.x_axis.get_label_and_position() y_axis_return = self.device.y_axis.get_label_and_position() self.log.info("Send position {} {}".format(x_axis_return, y_axis_return)) - return "{ACK}{STX}{0},{1}".format(x_axis_return, y_axis_return, **COMMAND_CHARS) + return "{ACK}{STX}{0},{1}{EOT}".format(x_axis_return, y_axis_return, **COMMAND_CHARS) def home_axis(self, axis): - if axis == "X": - self.device.x_axis.home() - elif axis == "Y": - self.device.y_axis.home() + axis = self.device.axes[axis] + axis.home() return "{ACK}" def get_status(self): @@ -73,9 +73,33 @@ def get_status(self): else: status = "P" self.log.info("Send motor status {}".format(status)) - return "{ACK}{STX}{status}".format(status=status, **COMMAND_CHARS) + return "{ACK}{STX}{status}{EOT}".format(status=status, **COMMAND_CHARS) def set_position(self, x_position, y_position): self.device.x_axis.sp = x_position self.device.y_axis.sp = y_position - return "{ACK}".format(**COMMAND_CHARS) \ No newline at end of file + return "{ACK}".format(**COMMAND_CHARS) + + def set_position_as_steps(self, axis, steps): + """ + Set the position at using increments (steps) + Args: + axis: the axis to set + steps: the number of steps to set it at + + Returns: acknowledgement + + """ + axis = self.device.axes[axis] + axis.sp = int(steps) + self.log.info("Setting: Position {}, sp {}".format(axis.rbv, axis.sp)) + return "{ACK}".format(**COMMAND_CHARS) + + def get_position_as_steps(self, axis): + + axis = self.device.axes[axis] + self.log.info("Position {}, sp {}".format(axis.rbv, axis.sp)) + if axis.rbv_error is not None: + return "{ACK}{STX}{0}{EOT}".format(self.rbv_error, **COMMAND_CHARS) + else: + return "{ACK}{STX}{steps:.0f}{EOT}".format(steps=axis.rbv, **COMMAND_CHARS) diff --git a/lewis_emulators/sm300/states.py b/lewis_emulators/sm300/states.py index 38aafdfe..d3f76658 100644 --- a/lewis_emulators/sm300/states.py +++ b/lewis_emulators/sm300/states.py @@ -4,5 +4,4 @@ class DefaultState(State): def in_state(self, dt): device = self._context - device.x_axis.simulate(dt) - device.y_axis.simulate(dt) + [axis.simulate(dt) for axis in device.axes.values()] diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 14d95b05..c4bbd136 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -19,17 +19,17 @@ class CmdBuilder(object): >>> CmdBuilder("set_pres").escape("pres ").float().build() this add float as a regularly expression capture group for your argument. It is equivalent to: >>> Cmd("set_pres", r"pres ([+-]?\d+\.?\d*)") - There are various arguments like int and digit. Finally some special characters are included so if your protocol uses - enquirey character ascii 5 you can match is using + There are various arguments like int and digit. Finally some special characters are included so if your protocol + uses enquirey character ascii 5 you can match is using >>> CmdBuilder("set_pres").escape("pres?").enq().build() """ - def __init__(self, target_method, arg_sep=",", ignore=""): + def __init__(self, target_method, arg_sep="", ignore=""): """ Create a builder. Use build to create the final object :param target_method: name of the method target to call when the reg ex matches - :param arg_sep: separators between the arguments + :param arg_sep: separators between arguments which are next to each other :param ignore: set of characters to ignore between text and arguments """ self._target_method = target_method @@ -41,6 +41,11 @@ def __init__(self, target_method, arg_sep=",", ignore=""): self._ignore = "[{0}]*".format(ignore) self._reg_ex = self._ignore + def _add_to_regex(self, regex, is_arg): + self._reg_ex += regex + self._ignore + if not is_arg: + self._current_sep = "" + def escape(self, text): """ Add some text to the regex which is escaped. @@ -48,7 +53,7 @@ def escape(self, text): :param text: text to add :return: builder """ - self._reg_ex += re.escape(text) + self._ignore + self._add_to_regex(re.escape(text), False) return self def regex(self, regex): @@ -58,7 +63,7 @@ def regex(self, regex): :param regex: regex to add :return: builder """ - self._reg_ex += regex + self._ignore + self._add_to_regex(regex, False) return self def spaces(self, at_least_one=False): @@ -72,7 +77,8 @@ def spaces(self, at_least_one=False): """ wildchard = "*" if at_least_one else "+" - self._reg_ex += "\w" + wildchard + self._ignore + self._add_to_regex("\w" + wildchard, False) + return self def arg(self, arg_regex): """ @@ -81,7 +87,7 @@ def arg(self, arg_regex): :param arg_regex: regex for the argument (capture group will be added) :return: builder """ - self._reg_ex += self._current_sep + "(" + arg_regex + ")" + self._ignore + self._add_to_regex(self._current_sep + "(" + arg_regex + ")", True) self._current_sep = self._arg_sep return self @@ -129,8 +135,8 @@ def build(self, *args, **kwargs): """ Builds the CMd object based on the target and regular expression. - :param *args: arguments to pass to Cmd constructor - :param **kwargs: key word arguments to pass to Cmd constructor + :param args: arguments to pass to Cmd constructor + :param kwargs: key word arguments to pass to Cmd constructor :return: Cmd object """ return Cmd(self._target_method, self._reg_ex, *args, **kwargs) @@ -142,8 +148,7 @@ def add_ascii_character(self, char_number): :param char_number: character number :return: self """ - self._reg_ex += chr(char_number) - + self._add_to_regex(chr(char_number), False) return self def stx(self): From 9fdd3d2c9ff0ae106fb191cd158111e2f1fc0a06 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 14:27:45 +0000 Subject: [PATCH 0432/1466] STIL/MC temperatures --- lewis_emulators/triton/device.py | 10 ++++++++++ .../triton/interfaces/stream_interface.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index cdfad983..059d1a4e 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -5,6 +5,7 @@ SUBSYSTEM_NAMES = { "mixing chamber": "mix_chamber_name", + "stil": "T1", "heater": "H5" } @@ -43,6 +44,9 @@ def _initialize_data(self): self.status = "This is a device status message." self.automation = "This is the automation status" + self.stil_temp = 0 + self.mc_temp = 0 + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -102,3 +106,9 @@ def get_status(self): def get_automation(self): return self.automation + + def get_stil_temp(self): + return self.stil_temp + + def get_mc_temp(self): + return self.mc_temp diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 632246f6..8140c456 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -10,8 +10,11 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { + # UIDs CmdBuilder("get_mc_uid") .escape("READ:SYS:DR:CHAN:MC").build(), + CmdBuilder("get_stil_uid") + .escape("READ:SYS:DR:CHAN:STIL").build(), # PID setpoints CmdBuilder("set_p") @@ -35,6 +38,12 @@ class TritonStreamInterface(StreamInterface): CmdBuilder("get_temperature_setpoint") .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + # Temperature + CmdBuilder("get_stil_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["stil"])).build(), + CmdBuilder("get_mc_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + # Heater range CmdBuilder("set_heater_range") .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), @@ -85,6 +94,10 @@ def get_mc_uid(self): return "STAT:SYS:DR:CHAN:MC:{}" \ .format(SUBSYSTEM_NAMES["mixing chamber"]) + def get_stil_uid(self): + return "STAT:SYS:DR:CHAN:STIL:{}" \ + .format(SUBSYSTEM_NAMES["stil"]) + def set_p(self, value): self.device.set_p(float(value)) return "ok" @@ -167,3 +180,9 @@ def get_status(self): def get_automation(self): return "STAT:SYS:DR:ACTN:{}".format(self.device.get_automation()) + + def get_stil_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["stil"], self.device.get_stil_temp()) + + def get_mc_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_mc_temp()) From 90063650564a4631b38677abf7fb6043dfc72828 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 15:39:10 +0000 Subject: [PATCH 0433/1466] Refactor. --- lewis_emulators/triton/device.py | 19 +++++++++++++-- .../triton/interfaces/stream_interface.py | 23 ++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 059d1a4e..63c8f501 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -4,9 +4,12 @@ SUBSYSTEM_NAMES = { - "mixing chamber": "mix_chamber_name", + "mixing chamber": "T0", "stil": "T1", - "heater": "H5" + "sorb": "T9", + "heater": "H5", + "4khx": "T3", + "jthx": "T2" } @@ -46,6 +49,9 @@ def _initialize_data(self): self.stil_temp = 0 self.mc_temp = 0 + self.sorb_temp = 0 + self.fkhx_temp = 0 + self.jthx_temp = 0 def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) @@ -112,3 +118,12 @@ def get_stil_temp(self): def get_mc_temp(self): return self.mc_temp + + def get_sorb_temp(self): + return self.sorb_temp + + def get_4khx_temp(self): + return self.fkhx_temp + + def get_jthx_temp(self): + return self.jthx_temp diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 8140c456..643a1286 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -15,6 +15,8 @@ class TritonStreamInterface(StreamInterface): .escape("READ:SYS:DR:CHAN:MC").build(), CmdBuilder("get_stil_uid") .escape("READ:SYS:DR:CHAN:STIL").build(), + CmdBuilder("get_sorb_uid") + .escape("READ:SYS:DR:CHAN:SORB").build(), # PID setpoints CmdBuilder("set_p") @@ -43,6 +45,12 @@ class TritonStreamInterface(StreamInterface): .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["stil"])).build(), CmdBuilder("get_mc_temp") .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_sorb_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["sorb"])).build(), + CmdBuilder("get_4khx_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["4khx"])).build(), + CmdBuilder("get_jthx_temp") + .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["jthx"])).build(), # Heater range CmdBuilder("set_heater_range") @@ -85,7 +93,7 @@ class TritonStreamInterface(StreamInterface): out_terminator = "\r\n" def handle_error(self, request, error): - err = "Request: {}, error: {}." + err = "Request: {}, error: {}.".format(request, error) print(err) self.log.error(err) return err @@ -98,6 +106,10 @@ def get_stil_uid(self): return "STAT:SYS:DR:CHAN:STIL:{}" \ .format(SUBSYSTEM_NAMES["stil"]) + def get_sorb_uid(self): + return "STAT:SYS:DR:CHAN:SORB:{}" \ + .format(SUBSYSTEM_NAMES["sorb"]) + def set_p(self, value): self.device.set_p(float(value)) return "ok" @@ -186,3 +198,12 @@ def get_stil_temp(self): def get_mc_temp(self): return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_mc_temp()) + + def get_sorb_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["sorb"], self.device.get_sorb_temp()) + + def get_4khx_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["4khx"], self.device.get_4khx_temp()) + + def get_jthx_temp(self): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["jthx"], self.device.get_jthx_temp()) From 6c2bd0333a958a153a36644ee50a653f8186b684 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Wed, 20 Dec 2017 15:49:21 +0000 Subject: [PATCH 0434/1466] Add some doc strings --- lewis_emulators/sm300/device.py | 48 ++++++++++++++++++- .../sm300/interfaces/stream_interface.py | 33 ++++++++++++- 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py index f8f2a8f6..7341ab12 100644 --- a/lewis_emulators/sm300/device.py +++ b/lewis_emulators/sm300/device.py @@ -1,3 +1,7 @@ +""" +Device classes for the SM 300 motor emulator +""" + from collections import OrderedDict from lewis.core.logging import has_log @@ -6,9 +10,19 @@ from lewis.devices import StateMachineDevice from lewis.core import approaches + @has_log class Axis(): + """ + An axis within the SM300 device + """ + def __init__(self, axis_label): + """ + Constructor. + Args: + axis_label: the label for the axis + """ self.rbv_error = None self.rbv = 10.0 self.sp = self.rbv @@ -17,15 +31,25 @@ def __init__(self, axis_label): self.axis_label = axis_label def home(self): + """ + Perform a homing operation. + """ self.sp = 0.0 self.moving = True def simulate(self, dt): - + """ + Simulate movement of the axis. + Args: + dt: time since last simulation + """ self.rbv = approaches.linear(self.rbv, self.sp, self.speed, dt) self.moving = self.rbv != self.sp def get_label_and_position(self): + """ + Returns: axis label and current position in steps + """ if self.rbv_error is not None: return self.rbv_error else: @@ -33,7 +57,9 @@ def get_label_and_position(self): class SimulatedSm300(StateMachineDevice): - + """ + Simulated SM300 Device + """ def _initialize_data(self): """ Initialize all of the device's attributes. @@ -62,10 +88,16 @@ def _get_transition_handlers(self): ]) def reset(self): + """ + Reset device to start up state + """ self._initialize_data() @property def x_axis_rbv(self): + """ + Returns: Read back value for the x axis (useful usage through the back door + """ return self.x_axis.rbv @x_axis_rbv.setter @@ -74,6 +106,9 @@ def x_axis_rbv(self, position): @property def y_axis_rbv(self): + """ + Returns: Read back value for the y axis (useful usage through the back door + """ return self.y_axis.rbv @y_axis_rbv.setter @@ -82,6 +117,9 @@ def y_axis_rbv(self, position): @property def x_axis_sp(self): + """ + Returns: Set point value for the x axis (useful usage through the back door + """ return self.x_axis.rbv @x_axis_sp.setter @@ -90,6 +128,9 @@ def x_axis_sp(self, position): @property def y_axis_sp(self): + """ + Returns: Set point value for the y axis (useful usage through the back door + """ return self.y_axis.rbv @y_axis_sp.setter @@ -98,6 +139,9 @@ def y_axis_sp(self, position): @property def x_axis_rbv_error(self): + """ + Returns: The error to give instead of the read back value + """ return self.x_axis.rbv_error @x_axis_rbv_error.setter diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index e9c49951..d951f6c8 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -1,13 +1,18 @@ +""" +Stream interface for the SM 300 motor emulator +""" from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder -from lewis_emulators.utils.constants import ACK, STX, EOT, COMMAND_CHARS +from lewis_emulators.utils.constants import EOT, COMMAND_CHARS @has_log class Sm300StreamInterface(StreamInterface): - + """ + Stream interface for the SM 300 motor emulator + """ in_terminator = EOT out_terminator = "" @@ -51,6 +56,14 @@ def get_position(self): return "{ACK}{STX}{0},{1}{EOT}".format(x_axis_return, y_axis_return, **COMMAND_CHARS) def home_axis(self, axis): + """ + Home the axis. + Args: + axis: acis to home + + Returns: acknowledge when sent + + """ axis = self.device.axes[axis] axis.home() return "{ACK}" @@ -76,6 +89,15 @@ def get_status(self): return "{ACK}{STX}{status}{EOT}".format(status=status, **COMMAND_CHARS) def set_position(self, x_position, y_position): + """ + Set the position in mm * 10^data default + Args: + x_position: position for x axis + y_position: position for y axis + + Returns: acknowledge when done + + """ self.device.x_axis.sp = x_position self.device.y_axis.sp = y_position return "{ACK}".format(**COMMAND_CHARS) @@ -96,7 +118,14 @@ def set_position_as_steps(self, axis, steps): return "{ACK}".format(**COMMAND_CHARS) def get_position_as_steps(self, axis): + """ + Get the current position in steps + Args: + axis: axis to get the value for + + Returns: the value + """ axis = self.device.axes[axis] self.log.info("Position {}, sp {}".format(axis.rbv, axis.sp)) if axis.rbv_error is not None: From 897db4c0ac88a544f6310aba834be9010b3d9e6d Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 16:07:49 +0000 Subject: [PATCH 0435/1466] Add pressures --- lewis_emulators/triton/device.py | 8 ++++++++ lewis_emulators/triton/interfaces/stream_interface.py | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 63c8f501..8469479b 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -53,9 +53,14 @@ def _initialize_data(self): self.fkhx_temp = 0 self.jthx_temp = 0 + self.pressures = [0] * 5 + def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) + def set_pressure_backdoor(self, valve, newpressure): + self.pressures[int(valve) - 1] = float(newpressure) + def _get_state_handlers(self): return { 'default': DefaultState(), @@ -127,3 +132,6 @@ def get_4khx_temp(self): def get_jthx_temp(self): return self.jthx_temp + + def get_pressure(self, sensor): + return self.pressures[sensor] diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 643a1286..81dd79c9 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -87,6 +87,10 @@ class TritonStreamInterface(StreamInterface): # Automation CmdBuilder("get_automation") .escape("READ:SYS:DR:ACTN").build(), + + # Pressures + CmdBuilder("get_pressure") + .escape("READ:DEV:P").int().escape(":PRES:SIG:PRES").build(), } in_terminator = "\r\n" @@ -207,3 +211,6 @@ def get_4khx_temp(self): def get_jthx_temp(self): return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["jthx"], self.device.get_jthx_temp()) + + def get_pressure(self, sensor): + return "STAT:DEV:P{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(int(sensor)-1)) From 8f6316f6d24344c6ef28976d76628f3bd582624c Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 16:59:54 +0000 Subject: [PATCH 0436/1466] Emulator happy (mostly) to talk to labview --- lewis_emulators/triton/device.py | 2 +- lewis_emulators/triton/interfaces/stream_interface.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 8469479b..c5494a19 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -7,7 +7,7 @@ "mixing chamber": "T0", "stil": "T1", "sorb": "T9", - "heater": "H5", + "heater": "T5", "4khx": "T3", "jthx": "T2" } diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 81dd79c9..4624187f 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -94,13 +94,13 @@ class TritonStreamInterface(StreamInterface): } in_terminator = "\r\n" - out_terminator = "\r\n" + out_terminator = "\n" def handle_error(self, request, error): - err = "Request: {}, error: {}.".format(request, error) - print(err) - self.log.error(err) - return err + err_string = "command was: {}, error was: {}\n".format(request, error) + print(err_string) + # self.log.error(err) + return err_string def get_mc_uid(self): return "STAT:SYS:DR:CHAN:MC:{}" \ From dbd9aab1c266a174c3ec1c9b50a8de804c00bf3e Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Wed, 20 Dec 2017 17:05:17 +0000 Subject: [PATCH 0437/1466] mixing chamber must be T5 --- lewis_emulators/triton/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index c5494a19..4e5dd5f8 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -4,10 +4,10 @@ SUBSYSTEM_NAMES = { - "mixing chamber": "T0", + "mixing chamber": "T5", "stil": "T1", "sorb": "T9", - "heater": "T5", + "heater": "H5", "4khx": "T3", "jthx": "T2" } From ebe97b298d489fa485fd5ff9a3d4caedbb0371fe Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 21 Dec 2017 09:11:22 +0000 Subject: [PATCH 0438/1466] Add support for setting closed loop mode --- lewis_emulators/triton/device.py | 6 ++++++ lewis_emulators/triton/interfaces/stream_interface.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 4e5dd5f8..f9c48acf 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -73,6 +73,12 @@ def _get_transition_handlers(self): return OrderedDict([ ]) + def get_closed_loop_mode(self): + return self.closed_loop + + def set_closed_loop_mode(self, mode): + self.closed_loop = mode + def set_p(self, value): self.p = value diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index 4624187f..c63bcb6a 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -69,6 +69,8 @@ class TritonStreamInterface(StreamInterface): # Loop mode CmdBuilder("get_closed_loop_mode") .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("set_closed_loop_mode") + .escape("SET:DEV:{}:TEMP:LOOP:MODE:".format(SUBSYSTEM_NAMES["mixing chamber"])).any().build(), # Valve state CmdBuilder("get_valve_state") @@ -164,7 +166,14 @@ def get_heater_power(self): def get_closed_loop_mode(self): return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ - .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.closed_loop else "OFF") + .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.get_closed_loop_mode() else "OFF") + + def set_closed_loop_mode(self, mode): + if mode not in ["ON", "OFF"]: + raise ValueError("Invalid mode") + + self.device.set_closed_loop_mode(mode == "ON") + return "ok" def get_valve_state(self, valve): From 7cb0ba0137c7bb50202eaa92ad275a61ae0bd963 Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Thu, 21 Dec 2017 11:59:49 +0000 Subject: [PATCH 0439/1466] Substantial refactor This should allow more flexibility about which channels are present or not --- lewis_emulators/triton/device.py | 96 +++++++++++-------- .../triton/interfaces/stream_interface.py | 85 ++++++---------- 2 files changed, 85 insertions(+), 96 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index f9c48acf..272612a0 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -8,8 +8,8 @@ "stil": "T1", "sorb": "T9", "heater": "H5", - "4khx": "T3", - "jthx": "T2" + "fkhx": "T3", + "jthx": "T2", } @@ -22,6 +22,22 @@ class ValveStates(object): NOT_FOUND = 2 +class TemperatureStage(object): + def __init__(self, name): + self.name = name + self.temperature = 0 + self.enabled = True + + self.p = 0 + self.i = 0 + self.d = 0 + + +class PressureSensor(object): + def __init__(self): + self.pressure = 0 + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): @@ -34,10 +50,6 @@ def _initialize_data(self): self.heater_power = 1 self.heater_power_units = "mA" - self.p = 0 - self.i = 0 - self.d = 0 - self.closed_loop = False self.valves = [ValveStates.CLOSED] * 10 @@ -47,19 +59,31 @@ def _initialize_data(self): self.status = "This is a device status message." self.automation = "This is the automation status" - self.stil_temp = 0 - self.mc_temp = 0 - self.sorb_temp = 0 - self.fkhx_temp = 0 - self.jthx_temp = 0 + self.pressure_sensors = {"P{}".format(idx): PressureSensor() for idx in range(1, 6)} + + self.temperature_stages = { + "T1": TemperatureStage("stil"), + "T2": TemperatureStage("jthx"), + "T3": TemperatureStage("4khx"), + "T4": TemperatureStage("sorb"), + "T5": TemperatureStage("mc"), + } - self.pressures = [0] * 5 + def find_temperature_channel(self, name): + for k, v in self.temperature_stages.items(): + if v.name == name: + return k + else: + raise ValueError("{} not found".format(name)) + + def set_temperature_backdoor(self, stage_name, new_temp): + self.temperature_stages[self.find_temperature_channel(stage_name)].temperature = new_temp def set_valve_state_backdoor(self, valve, newstate): self.valves[int(valve) - 1] = int(newstate) - def set_pressure_backdoor(self, valve, newpressure): - self.pressures[int(valve) - 1] = float(newpressure) + def set_pressure_backdoor(self, sensor, newpressure): + self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) def _get_state_handlers(self): return { @@ -79,23 +103,23 @@ def get_closed_loop_mode(self): def set_closed_loop_mode(self, mode): self.closed_loop = mode - def set_p(self, value): - self.p = value + def set_p(self, stage, value): + self.temperature_stages[stage].p = value - def set_i(self, value): - self.i = value + def set_i(self, stage, value): + self.temperature_stages[stage].i = value - def set_d(self, value): - self.d = value + def set_d(self, stage, value): + self.temperature_stages[stage].d = value - def get_p(self): - return self.p + def get_p(self, stage): + return self.temperature_stages[stage].p - def get_i(self): - return self.i + def get_i(self, stage): + return self.temperature_stages[stage].i - def get_d(self): - return self.d + def get_d(self, stage): + return self.temperature_stages[stage].d def get_temperature_setpoint(self): return self.temperature_setpoint @@ -124,20 +148,8 @@ def get_status(self): def get_automation(self): return self.automation - def get_stil_temp(self): - return self.stil_temp - - def get_mc_temp(self): - return self.mc_temp - - def get_sorb_temp(self): - return self.sorb_temp - - def get_4khx_temp(self): - return self.fkhx_temp - - def get_jthx_temp(self): - return self.jthx_temp - def get_pressure(self, sensor): - return self.pressures[sensor] + return self.pressure_sensors[sensor].pressure + + def get_temp(self, stage): + return self.temperature_stages[stage].temperature diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index c63bcb6a..cc93b034 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -20,19 +20,19 @@ class TritonStreamInterface(StreamInterface): # PID setpoints CmdBuilder("set_p") - .escape("SET:DEV:{}:TEMP:LOOP:P:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P:").float().build(), CmdBuilder("set_i") - .escape("SET:DEV:{}:TEMP:LOOP:I:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I:").float().build(), CmdBuilder("set_d") - .escape("SET:DEV:{}:TEMP:LOOP:D:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), + .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D:").float().build(), # PID readbacks CmdBuilder("get_p") - .escape("READ:DEV:{}:TEMP:LOOP:P".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P").build(), CmdBuilder("get_i") - .escape("READ:DEV:{}:TEMP:LOOP:I".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I").build(), CmdBuilder("get_d") - .escape("READ:DEV:{}:TEMP:LOOP:D".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D").build(), # Setpoint temperature CmdBuilder("set_temperature_setpoint") @@ -41,16 +41,8 @@ class TritonStreamInterface(StreamInterface): .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), # Temperature - CmdBuilder("get_stil_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["stil"])).build(), - CmdBuilder("get_mc_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("get_sorb_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["sorb"])).build(), - CmdBuilder("get_4khx_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["4khx"])).build(), - CmdBuilder("get_jthx_temp") - .escape("READ:DEV:{}:TEMP:SIG:TEMP".format(SUBSYSTEM_NAMES["jthx"])).build(), + CmdBuilder("get_temp") + .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:SIG:TEMP").build(), # Heater range CmdBuilder("set_heater_range") @@ -92,7 +84,7 @@ class TritonStreamInterface(StreamInterface): # Pressures CmdBuilder("get_pressure") - .escape("READ:DEV:P").int().escape(":PRES:SIG:PRES").build(), + .escape("READ:DEV:").arg("P[0-9]").escape(":PRES:SIG:PRES").build(), } in_terminator = "\r\n" @@ -105,40 +97,37 @@ def handle_error(self, request, error): return err_string def get_mc_uid(self): - return "STAT:SYS:DR:CHAN:MC:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"]) + return "STAT:SYS:DR:CHAN:MC:{}".format(self.device.find_temperature_channel("mc")) def get_stil_uid(self): - return "STAT:SYS:DR:CHAN:STIL:{}" \ - .format(SUBSYSTEM_NAMES["stil"]) + return "STAT:SYS:DR:CHAN:STIL:{}".format(self.device.find_temperature_channel("stil")) def get_sorb_uid(self): - return "STAT:SYS:DR:CHAN:SORB:{}" \ - .format(SUBSYSTEM_NAMES["sorb"]) + return "STAT:SYS:DR:CHAN:SORB:{}".format(self.device.find_temperature_channel("sorb")) - def set_p(self, value): - self.device.set_p(float(value)) + def set_p(self, stage, value): + self.device.set_p(stage, float(value)) return "ok" - def set_i(self, value): - self.device.set_i(float(value)) + def set_i(self, stage, value): + self.device.set_i(stage, float(value)) return "ok" - def set_d(self, value): - self.device.set_d(float(value)) + def set_d(self, stage, value): + self.device.set_d(stage, float(value)) return "ok" - def get_p(self): + def get_p(self, stage): return "STAT:DEV:{}:TEMP:LOOP:P:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_p()) + .format(stage, self.device.get_p(stage)) - def get_i(self): + def get_i(self, stage): return "STAT:DEV:{}:TEMP:LOOP:I:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_i()) + .format(stage, self.device.get_i(stage)) - def get_d(self): + def get_d(self, stage): return "STAT:DEV:{}:TEMP:LOOP:D:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_d()) + .format(stage, self.device.get_d(stage)) def set_temperature_setpoint(self, value): self.device.set_temperature_setpoint(float(value)) @@ -146,7 +135,7 @@ def set_temperature_setpoint(self, value): def get_temperature_setpoint(self): return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_temperature_setpoint()) + .format(self.device.find_temperature_channel("mc"), self.device.get_temperature_setpoint()) def set_heater_range(self, value): self.device.set_heater_range(float(value)) @@ -154,11 +143,11 @@ def set_heater_range(self, value): def get_heater_range(self): return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_heater_range()) + .format(self.device.find_temperature_channel("mc"), self.device.get_heater_range()) def get_heater_type(self): return "STAT:DEV:{}:TEMP:LOOP:HTR:{}" \ - .format(SUBSYSTEM_NAMES["mixing chamber"], SUBSYSTEM_NAMES["heater"]) + .format(self.device.find_temperature_channel("mc"), SUBSYSTEM_NAMES["heater"]) def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ @@ -166,7 +155,7 @@ def get_heater_power(self): def get_closed_loop_mode(self): return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ - .format(SUBSYSTEM_NAMES["mixing chamber"], "ON" if self.device.get_closed_loop_mode() else "OFF") + .format(self.device.find_temperature_channel("mc"), "ON" if self.device.get_closed_loop_mode() else "OFF") def set_closed_loop_mode(self, mode): if mode not in ["ON", "OFF"]: @@ -206,20 +195,8 @@ def get_status(self): def get_automation(self): return "STAT:SYS:DR:ACTN:{}".format(self.device.get_automation()) - def get_stil_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["stil"], self.device.get_stil_temp()) - - def get_mc_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["mixing chamber"], self.device.get_mc_temp()) - - def get_sorb_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["sorb"], self.device.get_sorb_temp()) - - def get_4khx_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["4khx"], self.device.get_4khx_temp()) - - def get_jthx_temp(self): - return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(SUBSYSTEM_NAMES["jthx"], self.device.get_jthx_temp()) + def get_temp(self, stage): + return "STAT:DEV:{}:TEMP:SIG:TEMP:{}K".format(stage, self.device.get_temp(str(stage))) def get_pressure(self, sensor): - return "STAT:DEV:P{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(int(sensor)-1)) + return "STAT:DEV:{}:PRES:SIG:PRES:{}mB".format(sensor, self.device.get_pressure(sensor)) From aedaedc2dcec9b228da2683d9e97fe95ad160b01 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Fri, 22 Dec 2017 15:36:52 +0000 Subject: [PATCH 0440/1466] Test the reset command --- lewis_emulators/sm300/device.py | 3 +- .../sm300/interfaces/stream_interface.py | 57 ++++++++++++++++--- lewis_emulators/utils/command_builder.py | 14 +++-- 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py index 7341ab12..130f66d8 100644 --- a/lewis_emulators/sm300/device.py +++ b/lewis_emulators/sm300/device.py @@ -12,7 +12,7 @@ @has_log -class Axis(): +class Axis(object): """ An axis within the SM300 device """ @@ -74,6 +74,7 @@ def _initialize_data(self): self.y_axis = self.axes["Y"] self.is_moving = None # let the axis report its motion self.is_moving_error = False + self.reset_codes = [] def _get_state_handlers(self): return { diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index d951f6c8..7e2ed674 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -22,12 +22,17 @@ def __init__(self): # Commands that we expect via serial during normal operation self.commands = { - CmdBuilder(self.get_position).escape("LQ").build(), - CmdBuilder(self.get_position_as_steps).escape("LI").char().build(), - CmdBuilder(self.home_axis).escape("BR").char().build(), - CmdBuilder(self.get_status).escape("LM").build(), - CmdBuilder(self.set_position).escape("B/").spaces().escape("X").int().spaces().escape("Y").int().build(), - CmdBuilder(self.set_position_as_steps).escape("B").char().int().build() + CmdBuilder(self.get_position).ack().stx().escape("LQ").build(), + CmdBuilder(self.get_position_as_steps).ack().stx().escape("LI").char().build(), + CmdBuilder(self.home_axis).ack().stx().escape("BR").char().build(), + CmdBuilder(self.get_status).ack().stx().escape("LM").build(), + CmdBuilder(self.set_position).ack().stx().escape("B/").spaces().escape("X").int().spaces().escape("Y"). + int().build(), + CmdBuilder(self.setting).ack().stx().escape("B/").spaces().escape("G").any().build(), + CmdBuilder(self.feed_code).ack().stx().escape("BF").int().build(), + CmdBuilder(self.set_position_as_steps).ack().stx().escape("B").char(not_chars=["F"]).int().build(), + # This is cheating the PEL1 command comes after PEL0 which is sent with a cr characters so add it in here + CmdBuilder(self.reset_code).regex(r"\r?").ack().stx().escape("P").any().build() } self.device = self._device @@ -114,7 +119,6 @@ def set_position_as_steps(self, axis, steps): """ axis = self.device.axes[axis] axis.sp = int(steps) - self.log.info("Setting: Position {}, sp {}".format(axis.rbv, axis.sp)) return "{ACK}".format(**COMMAND_CHARS) def get_position_as_steps(self, axis): @@ -132,3 +136,42 @@ def get_position_as_steps(self, axis): return "{ACK}{STX}{0}{EOT}".format(self.rbv_error, **COMMAND_CHARS) else: return "{ACK}{STX}{steps:.0f}{EOT}".format(steps=axis.rbv, **COMMAND_CHARS) + + def reset_code(self, code): + """ + Record reset codes sent from P commands + Args: + code: code from P command + + Returns: acknowledge + + """ + self.device.reset_codes.append("P{}".format(code)) + self.log.info("Reset codes: {}".format(self.device.reset_codes)) + return "{ACK}".format(**COMMAND_CHARS) + + def setting(self, code): + """ + Record reset codes sent from B/ G commands + Args: + code: code from P command + + Returns: acknowledge + + """ + self.device.reset_codes.append("B/ G{}".format(code)) + self.log.info("Reset codes: {}".format(self.device.reset_codes)) + return "{ACK}".format(**COMMAND_CHARS) + + def feed_code(self, code): + """ + Record reset codes sent from BF commands + Args: + code: code from P command + + Returns: acknowledge + + """ + self.device.reset_codes.append("BF{}".format(code)) + self.log.info("Reset codes: {}".format(self.device.reset_codes)) + return "{ACK}".format(**COMMAND_CHARS) diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index c4bbd136..2ec188a3 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -77,7 +77,7 @@ def spaces(self, at_least_one=False): """ wildchard = "*" if at_least_one else "+" - self._add_to_regex("\w" + wildchard, False) + self._add_to_regex(" " + wildchard, False) return self def arg(self, arg_regex): @@ -107,13 +107,19 @@ def digit(self): """ return self.arg(r"\d") - def char(self): + def char(self, not_chars=None): """ Add a single character argument. - :return: builder + Args: + not_chars: characters that the character can not be; None for can be anything + + Returns: builder + """ - return self.arg(r".") + if not_chars is None: + return self.arg(r".") + return self.arg("[^{}]".format("".join(not_chars))) def int(self): """ From 3eb341c2dae7133b35c1aad940a67ddbcd6982fa Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 2 Jan 2018 13:16:31 +0000 Subject: [PATCH 0441/1466] Add stop for motor --- lewis_emulators/sm300/device.py | 33 ++++++++++++++++++- .../sm300/interfaces/stream_interface.py | 14 +++++++- 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/lewis_emulators/sm300/device.py b/lewis_emulators/sm300/device.py index 130f66d8..4ba257f8 100644 --- a/lewis_emulators/sm300/device.py +++ b/lewis_emulators/sm300/device.py @@ -25,10 +25,30 @@ def __init__(self, axis_label): """ self.rbv_error = None self.rbv = 10.0 - self.sp = self.rbv + self._sp = self.rbv self.moving = False self.speed = 0.1 self.axis_label = axis_label + self.stopped = False + + @property + def sp(self): + """ + Returns: the set point of the motor (i.e. where it is moving to) + + """ + return self._sp + + @sp.setter + def sp(self, position): + """ + Set the set point position of the motor. Also clears stopped condition. + Args: + position: the position to move to + + """ + self._sp = position + self.stopped = False def home(self): """ @@ -36,6 +56,7 @@ def home(self): """ self.sp = 0.0 self.moving = True + self.stopped = False def simulate(self, dt): """ @@ -43,6 +64,9 @@ def simulate(self, dt): Args: dt: time since last simulation """ + if self.stopped: + self.moving = False + return self.rbv = approaches.linear(self.rbv, self.sp, self.speed, dt) self.moving = self.rbv != self.sp @@ -55,6 +79,13 @@ def get_label_and_position(self): else: return "{label}{0:.0f}".format(self.rbv, label=self.axis_label) + def stop(self): + """ + Stop the motor moving. + + """ + self.stopped = True + class SimulatedSm300(StateMachineDevice): """ diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index 7e2ed674..36c6eee6 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -25,6 +25,7 @@ def __init__(self): CmdBuilder(self.get_position).ack().stx().escape("LQ").build(), CmdBuilder(self.get_position_as_steps).ack().stx().escape("LI").char().build(), CmdBuilder(self.home_axis).ack().stx().escape("BR").char().build(), + CmdBuilder(self.stop).ack().stx().escape("BSS").build(), CmdBuilder(self.get_status).ack().stx().escape("LM").build(), CmdBuilder(self.set_position).ack().stx().escape("B/").spaces().escape("X").int().spaces().escape("Y"). int().build(), @@ -133,7 +134,7 @@ def get_position_as_steps(self, axis): axis = self.device.axes[axis] self.log.info("Position {}, sp {}".format(axis.rbv, axis.sp)) if axis.rbv_error is not None: - return "{ACK}{STX}{0}{EOT}".format(self.rbv_error, **COMMAND_CHARS) + return "{ACK}{STX}{0}{EOT}".format(axis.rbv_error, **COMMAND_CHARS) else: return "{ACK}{STX}{steps:.0f}{EOT}".format(steps=axis.rbv, **COMMAND_CHARS) @@ -175,3 +176,14 @@ def feed_code(self, code): self.device.reset_codes.append("BF{}".format(code)) self.log.info("Reset codes: {}".format(self.device.reset_codes)) return "{ACK}".format(**COMMAND_CHARS) + + def stop(self): + """ + Stops all axes movement + Returns: acknowledge + + """ + for axis in self.device.axes.values(): + axis.stop() + + return "{ACK}{STX}P{EOT}".format(**COMMAND_CHARS) From b9216e93c1d0ee292147df47c897e94aad72faac Mon Sep 17 00:00:00 2001 From: Tom Willemsen Date: Tue, 2 Jan 2018 13:32:18 +0000 Subject: [PATCH 0442/1466] Refactor emulator --- lewis_emulators/triton/device.py | 91 ++++++----- .../triton/interfaces/stream_interface.py | 148 ++++++++---------- 2 files changed, 123 insertions(+), 116 deletions(-) diff --git a/lewis_emulators/triton/device.py b/lewis_emulators/triton/device.py index 272612a0..0e057862 100644 --- a/lewis_emulators/triton/device.py +++ b/lewis_emulators/triton/device.py @@ -3,14 +3,7 @@ from lewis.devices import StateMachineDevice -SUBSYSTEM_NAMES = { - "mixing chamber": "T5", - "stil": "T1", - "sorb": "T9", - "heater": "H5", - "fkhx": "T3", - "jthx": "T2", -} +HEATER_NAME = "H5" class ValveStates(object): @@ -23,43 +16,63 @@ class ValveStates(object): class TemperatureStage(object): + """ + Class representing a temperature stage. + """ def __init__(self, name): self.name = name - self.temperature = 0 + self.temperature = 1 self.enabled = True - self.p = 0 - self.i = 0 - self.d = 0 - class PressureSensor(object): + """ + Class to represent a pressure sensor. + + Having this as a class makes it more extensible in future, as the triton driver is still in flux. + """ def __init__(self): self.pressure = 0 +class Valve(object): + """ + Class to represent a valve. + + Having this as a class makes it more extensible in future, as the triton driver is still in flux. + """ + def __init__(self): + self.open = False + + +class Heater(object): + def __init__(self): + self.range = 0 + self.power = 0 + self.power_units = 0 + + class SimulatedTriton(StateMachineDevice): def _initialize_data(self): """ Initialize all of the device's attributes. """ - self.temperature_setpoint = 0 self.heater_range = 0 - self.heater_power = 1 self.heater_power_units = "mA" + self.temperature_setpoint = 0 + self.p = 0 + self.i = 0 + self.d = 0 self.closed_loop = False - self.valves = [ValveStates.CLOSED] * 10 - - self.channels_enabled = [True] * 6 - self.status = "This is a device status message." self.automation = "This is the automation status" - self.pressure_sensors = {"P{}".format(idx): PressureSensor() for idx in range(1, 6)} + self.valves = {"V{}".format(i): Valve() for i in range(1, 11)} + self.pressure_sensors = {"P{}".format(i): PressureSensor() for i in range(1, 6)} self.temperature_stages = { "T1": TemperatureStage("stil"), @@ -67,6 +80,7 @@ def _initialize_data(self): "T3": TemperatureStage("4khx"), "T4": TemperatureStage("sorb"), "T5": TemperatureStage("mc"), + "T6": TemperatureStage("unknown"), } def find_temperature_channel(self, name): @@ -74,13 +88,13 @@ def find_temperature_channel(self, name): if v.name == name: return k else: - raise ValueError("{} not found".format(name)) + raise KeyError("{} not found".format(name)) def set_temperature_backdoor(self, stage_name, new_temp): self.temperature_stages[self.find_temperature_channel(stage_name)].temperature = new_temp def set_valve_state_backdoor(self, valve, newstate): - self.valves[int(valve) - 1] = int(newstate) + self.valves["V{}".format(valve)].open = bool(newstate) def set_pressure_backdoor(self, sensor, newpressure): self.pressure_sensors["P{}".format(sensor)].pressure = float(newpressure) @@ -103,23 +117,23 @@ def get_closed_loop_mode(self): def set_closed_loop_mode(self, mode): self.closed_loop = mode - def set_p(self, stage, value): - self.temperature_stages[stage].p = value + def set_p(self, value): + self.p = value - def set_i(self, stage, value): - self.temperature_stages[stage].i = value + def set_i(self, value): + self.i = value - def set_d(self, stage, value): - self.temperature_stages[stage].d = value + def set_d(self, value): + self.d = value - def get_p(self, stage): - return self.temperature_stages[stage].p + def get_p(self): + return self.p - def get_i(self, stage): - return self.temperature_stages[stage].i + def get_i(self): + return self.i - def get_d(self, stage): - return self.temperature_stages[stage].d + def get_d(self): + return self.d def get_temperature_setpoint(self): return self.temperature_setpoint @@ -134,13 +148,16 @@ def set_heater_range(self, value): self.heater_range = value def get_valve_state(self, valve): - return self.valves[valve-1] + try: + return ValveStates.OPEN if self.valves[valve].open else ValveStates.CLOSED + except KeyError: + return ValveStates.NOT_FOUND def is_channel_enabled(self, chan): - return self.channels_enabled[chan-1] + return self.temperature_stages[chan].enabled def set_channel_enabled(self, chan, newstate): - self.channels_enabled[chan-1] = newstate + self.temperature_stages[chan].enabled = newstate def get_status(self): return self.status diff --git a/lewis_emulators/triton/interfaces/stream_interface.py b/lewis_emulators/triton/interfaces/stream_interface.py index cc93b034..605fccae 100644 --- a/lewis_emulators/triton/interfaces/stream_interface.py +++ b/lewis_emulators/triton/interfaces/stream_interface.py @@ -1,7 +1,7 @@ from lewis.adapters.stream import StreamInterface, Cmd from lewis.core.logging import has_log from lewis_emulators.utils.command_builder import CmdBuilder -from lewis_emulators.triton.device import SUBSYSTEM_NAMES +from lewis_emulators.triton.device import HEATER_NAME from lewis_emulators.triton.device import ValveStates @@ -11,80 +11,55 @@ class TritonStreamInterface(StreamInterface): # Commands that we expect via serial during normal operation commands = { # UIDs - CmdBuilder("get_mc_uid") - .escape("READ:SYS:DR:CHAN:MC").build(), - CmdBuilder("get_stil_uid") - .escape("READ:SYS:DR:CHAN:STIL").build(), - CmdBuilder("get_sorb_uid") - .escape("READ:SYS:DR:CHAN:SORB").build(), + CmdBuilder("get_mc_uid").escape("READ:SYS:DR:CHAN:MC").build(), + CmdBuilder("get_stil_uid").escape("READ:SYS:DR:CHAN:STIL").build(), + CmdBuilder("get_sorb_uid").escape("READ:SYS:DR:CHAN:SORB").build(), # PID setpoints - CmdBuilder("set_p") - .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P:").float().build(), - CmdBuilder("set_i") - .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I:").float().build(), - CmdBuilder("set_d") - .escape("SET:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D:").float().build(), + CmdBuilder("set_p").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P:").float().build(), + CmdBuilder("set_i").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I:").float().build(), + CmdBuilder("set_d").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D:").float().build(), # PID readbacks - CmdBuilder("get_p") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:P").build(), - CmdBuilder("get_i") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:I").build(), - CmdBuilder("get_d") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:LOOP:D").build(), + CmdBuilder("get_p").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:P").build(), + CmdBuilder("get_i").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:I").build(), + CmdBuilder("get_d").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:D").build(), # Setpoint temperature CmdBuilder("set_temperature_setpoint") - .escape("SET:DEV:{}:TEMP:LOOP:TSET:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("get_temperature_setpoint") - .escape("READ:DEV:{}:TEMP:LOOP:TSET".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + .escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET:").float().build(), + CmdBuilder("get_temperature_setpoint").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:TSET").build(), # Temperature - CmdBuilder("get_temp") - .escape("READ:DEV:").arg("T[0-9]").escape(":TEMP:SIG:TEMP").build(), + CmdBuilder("get_temp").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:SIG:TEMP").build(), # Heater range - CmdBuilder("set_heater_range") - .escape("SET:DEV:{}:TEMP:LOOP:RANGE:".format(SUBSYSTEM_NAMES["mixing chamber"])).float().build(), - CmdBuilder("get_heater_range") - .escape("READ:DEV:{}:TEMP:LOOP:RANGE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("set_heater_range").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE:").float().build(), + CmdBuilder("get_heater_range").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:RANGE").build(), # Heater type - CmdBuilder("get_heater_type") - .escape("READ:DEV:{}:TEMP:LOOP:HTR".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), + CmdBuilder("get_heater_type").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:HTR").build(), # Get heater power - CmdBuilder("get_heater_power") - .escape("READ:DEV:{}:HTR:SIG:POWR".format(SUBSYSTEM_NAMES["heater"])).build(), + CmdBuilder("get_heater_power").escape("READ:DEV:{}:HTR:SIG:POWR".format(HEATER_NAME)).build(), # Loop mode - CmdBuilder("get_closed_loop_mode") - .escape("READ:DEV:{}:TEMP:LOOP:MODE".format(SUBSYSTEM_NAMES["mixing chamber"])).build(), - CmdBuilder("set_closed_loop_mode") - .escape("SET:DEV:{}:TEMP:LOOP:MODE:".format(SUBSYSTEM_NAMES["mixing chamber"])).any().build(), + CmdBuilder("get_closed_loop_mode").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE").build(), + CmdBuilder("set_closed_loop_mode").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:LOOP:MODE:").any().build(), # Valve state - CmdBuilder("get_valve_state") - .escape("READ:DEV:V").int().escape(":VALV:SIG:STATE").build(), + CmdBuilder("get_valve_state").escape("READ:DEV:").arg("V[0-9]+").escape(":VALV:SIG:STATE").build(), # Channel enablement - CmdBuilder("get_channel_enabled") - .escape("READ:DEV:T").int().escape(":TEMP:MEAS:ENAB").build(), - CmdBuilder("set_channel_enabled") - .escape("SET:DEV:T").int().escape(":TEMP:MEAS:ENAB:").any().build(), + CmdBuilder("get_channel_enabled").escape("READ:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB").build(), + CmdBuilder("set_channel_enabled").escape("SET:DEV:").arg("T[0-9]+").escape(":TEMP:MEAS:ENAB:").any().build(), # Status - CmdBuilder("get_status") - .escape("READ:SYS:DR:STATUS").build(), - - # Automation - CmdBuilder("get_automation") - .escape("READ:SYS:DR:ACTN").build(), + CmdBuilder("get_status").escape("READ:SYS:DR:STATUS").build(), + CmdBuilder("get_automation").escape("READ:SYS:DR:ACTN").build(), # Pressures - CmdBuilder("get_pressure") - .escape("READ:DEV:").arg("P[0-9]").escape(":PRES:SIG:PRES").build(), + CmdBuilder("get_pressure").escape("READ:DEV:").arg("P[0-9]+").escape(":PRES:SIG:PRES").build(), } in_terminator = "\r\n" @@ -93,9 +68,13 @@ class TritonStreamInterface(StreamInterface): def handle_error(self, request, error): err_string = "command was: {}, error was: {}\n".format(request, error) print(err_string) - # self.log.error(err) + self.log.error(err_string) return err_string + def raise_if_channel_is_not_mc_channel(self, chan): + if str(chan) != self.device.find_temperature_channel("mc"): + raise ValueError("Channel should have been MC channel") + def get_mc_uid(self): return "STAT:SYS:DR:CHAN:MC:{}".format(self.device.find_temperature_channel("mc")) @@ -106,58 +85,66 @@ def get_sorb_uid(self): return "STAT:SYS:DR:CHAN:SORB:{}".format(self.device.find_temperature_channel("sorb")) def set_p(self, stage, value): - self.device.set_p(stage, float(value)) + self.raise_if_channel_is_not_mc_channel(stage) + self.device.set_p(float(value)) return "ok" def set_i(self, stage, value): - self.device.set_i(stage, float(value)) + self.raise_if_channel_is_not_mc_channel(stage) + self.device.set_i(float(value)) return "ok" def set_d(self, stage, value): - self.device.set_d(stage, float(value)) + self.raise_if_channel_is_not_mc_channel(stage) + self.device.set_d(float(value)) return "ok" def get_p(self, stage): - return "STAT:DEV:{}:TEMP:LOOP:P:{}" \ - .format(stage, self.device.get_p(stage)) + self.raise_if_channel_is_not_mc_channel(stage) + return "STAT:DEV:{}:TEMP:LOOP:P:{}".format(stage, self.device.get_p()) def get_i(self, stage): - return "STAT:DEV:{}:TEMP:LOOP:I:{}" \ - .format(stage, self.device.get_i(stage)) + self.raise_if_channel_is_not_mc_channel(stage) + return "STAT:DEV:{}:TEMP:LOOP:I:{}".format(stage, self.device.get_i()) def get_d(self, stage): - return "STAT:DEV:{}:TEMP:LOOP:D:{}" \ - .format(stage, self.device.get_d(stage)) + self.raise_if_channel_is_not_mc_channel(stage) + return "STAT:DEV:{}:TEMP:LOOP:D:{}".format(stage, self.device.get_d()) - def set_temperature_setpoint(self, value): + def set_temperature_setpoint(self, chan, value): + self.raise_if_channel_is_not_mc_channel(chan) self.device.set_temperature_setpoint(float(value)) return "ok" - def get_temperature_setpoint(self): + def get_temperature_setpoint(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) return "STAT:DEV:{}:TEMP:LOOP:TSET:{}K" \ .format(self.device.find_temperature_channel("mc"), self.device.get_temperature_setpoint()) - def set_heater_range(self, value): + def set_heater_range(self, chan, value): + self.raise_if_channel_is_not_mc_channel(chan) self.device.set_heater_range(float(value)) return "ok" - def get_heater_range(self): - return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}" \ - .format(self.device.find_temperature_channel("mc"), self.device.get_heater_range()) + def get_heater_range(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:RANGE:{}".format(chan, self.device.get_heater_range()) - def get_heater_type(self): - return "STAT:DEV:{}:TEMP:LOOP:HTR:{}" \ - .format(self.device.find_temperature_channel("mc"), SUBSYSTEM_NAMES["heater"]) + def get_heater_type(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:HTR:{}".format(chan, HEATER_NAME) def get_heater_power(self): return "STAT:DEV:{}:HTR:SIG:POWR:{}{}"\ - .format(SUBSYSTEM_NAMES["heater"], self.device.heater_power, self.device.heater_power_units) + .format(HEATER_NAME, self.device.heater_power, self.device.heater_power_units) + + def get_closed_loop_mode(self, chan): + self.raise_if_channel_is_not_mc_channel(chan) + return "STAT:DEV:{}:TEMP:LOOP:MODE:{}".format(chan, "ON" if self.device.get_closed_loop_mode() else "OFF") - def get_closed_loop_mode(self): - return "STAT:DEV:{}:TEMP:LOOP:MODE:{}"\ - .format(self.device.find_temperature_channel("mc"), "ON" if self.device.get_closed_loop_mode() else "OFF") + def set_closed_loop_mode(self, chan, mode): + self.raise_if_channel_is_not_mc_channel(chan) - def set_closed_loop_mode(self, mode): if mode not in ["ON", "OFF"]: raise ValueError("Invalid mode") @@ -166,7 +153,7 @@ def set_closed_loop_mode(self, mode): def get_valve_state(self, valve): - state = self.device.get_valve_state(int(valve)) + state = self.device.get_valve_state(valve) if state == ValveStates.CLOSED: response = "CLOSE" @@ -177,16 +164,19 @@ def get_valve_state(self, valve): else: raise ValueError("Invalid valve state: {}".format(state)) - return "STAT:DEV:V{}:VALV:SIG:STATE:{}".format(valve, response) + return "STAT:DEV:{}:VALV:SIG:STATE:{}".format(valve, response) def get_channel_enabled(self, channel): - return "STAT:DEV:T{}:TEMP:MEAS:ENAB:{}"\ - .format(channel, "ON" if self.device.is_channel_enabled(int(channel)) else "OFF") + return "STAT:DEV:{}:TEMP:MEAS:ENAB:{}"\ + .format(channel, "ON" if self.device.is_channel_enabled(channel) else "OFF") def set_channel_enabled(self, channel, newstate): + newstate = str(newstate) + if newstate not in ["ON", "OFF"]: raise ValueError("New state '{}' not valid.".format(newstate)) - self.device.set_channel_enabled(int(channel), str(newstate) == "ON") + + self.device.set_channel_enabled(channel, newstate == "ON") return "ok" def get_status(self): From 3f6f4aff51c72450e90dd613179771624e897758 Mon Sep 17 00:00:00 2001 From: John-Holt-Tessella Date: Tue, 2 Jan 2018 15:12:59 +0000 Subject: [PATCH 0443/1466] Add comment for PEP8 --- lewis_emulators/sm300/interfaces/stream_interface.py | 1 + lewis_emulators/utils/command_builder.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lewis_emulators/sm300/interfaces/stream_interface.py b/lewis_emulators/sm300/interfaces/stream_interface.py index 36c6eee6..b1b825d5 100644 --- a/lewis_emulators/sm300/interfaces/stream_interface.py +++ b/lewis_emulators/sm300/interfaces/stream_interface.py @@ -1,6 +1,7 @@ """ Stream interface for the SM 300 motor emulator """ + from lewis.adapters.stream import StreamInterface from lewis.core.logging import has_log diff --git a/lewis_emulators/utils/command_builder.py b/lewis_emulators/utils/command_builder.py index 2ec188a3..9885fc56 100644 --- a/lewis_emulators/utils/command_builder.py +++ b/lewis_emulators/utils/command_builder.py @@ -1,3 +1,7 @@ +""" +A fluent command builder for lewis. +""" + import re from lewis.adapters.stream import Cmd @@ -127,7 +131,7 @@ def int(self): :return: builder """ - return self.arg(r"\d+") + return self.arg(r"[-+]?\d+") def any(self): """ From 566df2df0434253b9be295a0a485dcd16c41894a Mon Sep 17 00:00:00 2001 From: esouthren Date: Wed, 3 Jan 2018 11:18:05 +0000 Subject: [PATCH 0444/1466] emulator functioning --- lewis_emulators/.idea/lewis_emulators.iml | 1 - lewis_emulators/.idea/workspace.xml | 588 +++++++++++------- lewis_emulators/lakeshore460/device.py | 341 +++++++++- .../interfaces/stream_interface.py | 214 +++++-- lewis_emulators/utils/command_builder.py | 4 + 5 files changed, 866 insertions(+), 282 deletions(-) diff --git a/lewis_emulators/.idea/lewis_emulators.iml b/lewis_emulators/.idea/lewis_emulators.iml index 1b0e5caf..d8d1b00f 100644 --- a/lewis_emulators/.idea/lewis_emulators.iml +++ b/lewis_emulators/.idea/lewis_emulators.iml @@ -3,7 +3,6 @@ - diff --git a/lewis_emulators/.idea/workspace.xml b/lewis_emulators/.idea/workspace.xml index d4812aad..a507b5ce 100644 --- a/lewis_emulators/.idea/workspace.xml +++ b/lewis_emulators/.idea/workspace.xml @@ -2,46 +2,49 @@ - - - - - - - - - - + - - - + + + - - - + + + + + - - - + - - - - - - - + + + + + + + - + + + + + + + + - + + + + + + \ No newline at end of file diff --git a/lewis_emulators/lakeshore460/device.py b/lewis_emulators/lakeshore460/device.py index 55336530..34a39a31 100644 --- a/lewis_emulators/lakeshore460/device.py +++ b/lewis_emulators/lakeshore460/device.py @@ -7,12 +7,12 @@ class Channel(object): def __init__(self): - self.field_reading = 0.8884 - self.field_multiplier = "u" - self.max_hold_reading = 0.1234 - self.max_hold_multiplier = "m" + self.field_reading = 1.234 + self.field_multiplier = " " + self.max_hold_reading = 0 + self.max_hold_multiplier = " " self.rel_mode_reading = 0.5645 - self.rel_mode_multiplier = "m" + self.rel_mode_multiplier = " " self.mode = 0 self.prms = 0 self.filter_status = 0 @@ -38,7 +38,7 @@ def _initialize_data(self): """ self.idn = "LSCI,MODEL460,0,22323" self.source = 1 - self.channels = {"X": Channel(), "Y":Channel(), "Z": Channel(), "V": Channel()} + self.channels = {"X": Channel(), "Y": Channel(), "Z": Channel(), "V": Channel()} self.channel = "X" self.unit = "T" diff --git a/lewis_emulators/lakeshore460/interfaces/stream_interface.py b/lewis_emulators/lakeshore460/interfaces/stream_interface.py index 3b82ae25..177c73a8 100644 --- a/lewis_emulators/lakeshore460/interfaces/stream_interface.py +++ b/lewis_emulators/lakeshore460/interfaces/stream_interface.py @@ -56,11 +56,95 @@ class Lakeshore460StreamInterface(StreamInterface): CmdBuilder("set_manual_range").escape("RANGE ").int().build(), } - def get_multiplier_value(self, string_val): - if string_val == "u": return 0 - elif string_val == "m" : return 1 - elif string_val == " " : return 2 - else: return 3 + def update_reading(self, reading, multiplier): + """ + :param reading: A reading from the device + :param multiplier: The current multiplier for the reading + :return: new_reading: updated reading value, based on more appropriate multiplier + new_multiplier: updated multiplier for the value + """ + stripped_reading = self.strip_multiplier(reading, multiplier) + new_multiplier = self.calculate_multiplier(stripped_reading) + new_reading = self.apply_multiplier(stripped_reading, new_multiplier) + return new_reading, new_multiplier + + def strip_multiplier(self, reading, multiplier): + """ + :param reading: A reading from the device with multiplier applied + :param multiplier: The current multiplier for the reading + :return: The raw reading + """ + if multiplier == "u": + return reading * 0.000001 + if multiplier == "m": + return reading * 0.001 + if multiplier == "k": + return reading * 1000 + else: + return reading + + def apply_multiplier(self, reading, multiplier): + """ + :param reading: A raw reading from the device + :param multiplier: The multiplier to be applied + :return: The reading with the multiplier applied + """ + if multiplier == "u": + return reading / 0.000001 + if multiplier == "m": + return reading / 0.001 + if multiplier == "k": + return reading / 1000 + else: + return reading + + def convert_units(self, convert_value): + """ + Converts between Tesla and Gauss (applies conversion of *10000 or *0.0001) + Then updates reading values according to more appropriate multiplier + :param convert_value: 10000 or 0.0001 + """ + channels = ['X', 'Y', 'Z', 'V'] + for c in channels: + self.set_channel(c) + self._device.channels[c].field_reading *= convert_value + self._device.channels[c].field_reading, \ + self._device.channels[c].field_multiplier = self.update_reading(self._device.channels[c].field_reading, + self._device.channels[c].field_multiplier) + self._device.channels[c].max_hold_reading *= convert_value + self._device.channels[c].max_hold_reading, \ + self._device.channels[c].max_hold_multiplier = self.update_reading(self._device.channels[c].max_hold_reading, + self._device.channels[c].max_hold_multiplier) + self._device.channels[c].rel_mode_reading *= convert_value + self._device.channels[c].rel_mode_reading, \ + self._device.channels[c].rel_mode_multiplier = self.update_reading(self._device.channels[c].rel_mode_reading, + self._device.channels[c].rel_mode_multiplier) + self._device.channels[c].relative_setpoint *= convert_value + self._device.channels[c].relative_setpoint, \ + self._device.channels[c].relative_setpoint_multiplier = self.update_reading(self._device.channels[c].relative_setpoint, + self._device.channels[c].relative_setpoint_multiplier) + + + def calculate_multiplier(self, reading): + """ + Calculates the most appropriate multiplier for a given value. + :param reading: A raw reading from the device + :return: The most appropriate multiplier value for the given raw reading + """ + if reading < 0.001: + return "u" + if reading >= 0.001 and reading < 0: + return "m" + if reading > 0 and reading < 1000: + return " " + else: + return "k" + + def update_reading(self, reading, multiplier): + stripped_reading = self.strip_multiplier(reading, multiplier) + new_multiplier = self.calculate_multiplier(stripped_reading) + new_reading = self.apply_multiplier(stripped_reading, new_multiplier) + return new_reading, new_multiplier def handle_error(self, request, error): self.log.error("An error occurred at request" + repr(request) + ": " + repr(error)) @@ -82,6 +166,14 @@ def get_channel(self): return self._device.channel def get_magnetic_field_reading(self): + field_reading = self._device.channels[self.get_channel()].field_reading + multiplier = self._device.channels[self.get_channel()].field_multiplier + + # Update max_hold_reading if field_reading is larger + if field_reading > self._device.channels[self.get_channel()].max_hold_reading: + self._device.channels[self.get_channel()].max_hold_reading = field_reading + self._device.channels[self.get_channel()].max_hold_reading_multiplier = multiplier + return "{0}".format(self._device.channels[self.get_channel()].field_reading) def get_magnetic_field_reading_multiplier(self): @@ -103,6 +195,13 @@ def get_unit(self): return "{0}".format(self._device.unit) def set_unit(self, unit): + # Convert values if required + if self._device.unit == "T": + if unit == "G": + self.convert_units(10000) + if self._device.unit == "G": + if unit == "T": + self.convert_units(0.0001) self._device.unit = unit def get_ac_dc_mode(self): From c084946fc1905f871b5923e1cfbabe04415e0b42 Mon Sep 17 00:00:00 2001 From: esouthren Date: Thu, 4 Jan 2018 14:11:10 +0000 Subject: [PATCH 0446/1466] fixed up multiplier addition --- lewis_emulators/.idea/lewis_emulators.iml | 6 + lewis_emulators/.idea/workspace.xml | 349 +++++++++--------- lewis_emulators/lakeshore460/__init__.py | 1 + lewis_emulators/lakeshore460/device.py | 11 +- .../lakeshore460/interfaces/__init__.py | 1 + .../interfaces/stream_interface.py | 96 +++-- 6 files changed, 248 insertions(+), 216 deletions(-) diff --git a/lewis_emulators/.idea/lewis_emulators.iml b/lewis_emulators/.idea/lewis_emulators.iml index d8d1b00f..d7c48b87 100644 --- a/lewis_emulators/.idea/lewis_emulators.iml +++ b/lewis_emulators/.idea/lewis_emulators.iml @@ -7,6 +7,12 @@ + + + + diff --git a/lewis_emulators/.idea/workspace.xml b/lewis_emulators/.idea/workspace.xml index f99ea0c4..216fbd6c 100644 --- a/lewis_emulators/.idea/workspace.xml +++ b/lewis_emulators/.idea/workspace.xml @@ -59,8 +59,18 @@ - - + + + + + + + + + + + + @@ -69,8 +79,8 @@ - - + + @@ -78,33 +88,21 @@ - - + + - - + + - - + + - - - - - - - - - - - - - - + + @@ -121,17 +119,6 @@ - read - ac_d - filter_window - filter_Sta - init - channel_St - prms - REL_MODE_READING - rel_mode_mul - get_relative_mode_reading_multiplier - soure source unit ulti @@ -151,7 +138,21 @@ UNIT get_mag field_reading + STRAINCHANNEL() + STRAINCHANNEL + set_channe + get_chan_w + positionChan + get_channel_pa + waveform_ty + self._channel + set_chan + set_channel + apply_mul + + channel + @@ -221,52 +223,6 @@

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

yj|ib^2m4&!@k`3qFoXfXQxvV1s8Utn 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