ivoryOS 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ivoryOS might be problematic. Click here for more details.
- ivoryOS-0.1.0/LICENSE +21 -0
- ivoryOS-0.1.0/MANIFEST.in +3 -0
- ivoryOS-0.1.0/PKG-INFO +77 -0
- ivoryOS-0.1.0/README.md +55 -0
- ivoryOS-0.1.0/example/__init__.py +0 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/__init__.py +0 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/abclocation.py +6 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/arm.py +22 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/chemical.py +5 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/container.py +15 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/handler.py +13 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/moveable.py +18 -0
- ivoryOS-0.1.0/example/abstract_robot_classes/moveableconfig.py +13 -0
- ivoryOS-0.1.0/example/dummy_ur/__init__.py +0 -0
- ivoryOS-0.1.0/example/dummy_ur/dummy_balance.py +10 -0
- ivoryOS-0.1.0/example/dummy_ur/dummy_deck.py +113 -0
- ivoryOS-0.1.0/example/dummy_ur/dummy_pump.py +8 -0
- ivoryOS-0.1.0/example/kinova_deck/__init__.py +0 -0
- ivoryOS-0.1.0/example/kinova_deck/chemicals.py +15 -0
- ivoryOS-0.1.0/example/kinova_deck/components/__init__.py +0 -0
- ivoryOS-0.1.0/example/kinova_deck/components/balance.py +49 -0
- ivoryOS-0.1.0/example/kinova_deck/components/capper.py +68 -0
- ivoryOS-0.1.0/example/kinova_deck/components/europa_control.py +634 -0
- ivoryOS-0.1.0/example/kinova_deck/components/needlewash.py +17 -0
- ivoryOS-0.1.0/example/kinova_deck/components/shaker.py +28 -0
- ivoryOS-0.1.0/example/kinova_deck/components/solenoid.py +52 -0
- ivoryOS-0.1.0/example/kinova_deck/experiment.py +30 -0
- ivoryOS-0.1.0/example/kinova_deck/kinova_arm.py +124 -0
- ivoryOS-0.1.0/example/kinova_deck/kinova_deck_setup.py +123 -0
- ivoryOS-0.1.0/example/kinova_deck/kinova_location.py +52 -0
- ivoryOS-0.1.0/example/kinova_deck/moveable/__init__.py +0 -0
- ivoryOS-0.1.0/example/kinova_deck/moveable/europa.py +50 -0
- ivoryOS-0.1.0/example/kinova_deck/moveable/vial.py +19 -0
- ivoryOS-0.1.0/example/kinova_deck/moving/__init__.py +0 -0
- ivoryOS-0.1.0/example/kinova_deck/moving/kinova_movable_config.py +21 -0
- ivoryOS-0.1.0/example/kinova_deck/webui_data/__init__.py +0 -0
- ivoryOS-0.1.0/example/kinova_deck/webui_data/scripts/__init__.py +0 -0
- ivoryOS-0.1.0/example/kinova_deck/webui_data/scripts/untitled.py +13 -0
- ivoryOS-0.1.0/example/test.py +90 -0
- ivoryOS-0.1.0/ivoryOS.egg-info/PKG-INFO +77 -0
- ivoryOS-0.1.0/ivoryOS.egg-info/SOURCES.txt +52 -0
- ivoryOS-0.1.0/ivoryOS.egg-info/dependency_links.txt +1 -0
- ivoryOS-0.1.0/ivoryOS.egg-info/requires.txt +11 -0
- ivoryOS-0.1.0/ivoryOS.egg-info/top_level.txt +2 -0
- ivoryOS-0.1.0/ivoryos/__init__.py +0 -0
- ivoryOS-0.1.0/ivoryos/app.py +996 -0
- ivoryOS-0.1.0/ivoryos/utils/__init__.py +0 -0
- ivoryOS-0.1.0/ivoryos/utils/form.py +304 -0
- ivoryOS-0.1.0/ivoryos/utils/llm_agent.py +171 -0
- ivoryOS-0.1.0/ivoryos/utils/model.py +415 -0
- ivoryOS-0.1.0/ivoryos/utils/openai_agent.py +144 -0
- ivoryOS-0.1.0/ivoryos/utils/utils.py +477 -0
- ivoryOS-0.1.0/setup.cfg +4 -0
- ivoryOS-0.1.0/setup.py +28 -0
ivoryOS-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023-2024 Ivory Zhang | Hein Lab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
ivoryOS-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ivoryOS
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: an open-source Python package enabling Self-Driving Labs (SDLs) interoperability
|
|
5
|
+
Home-page: https://gitlab.com/heingroup/ivoryos
|
|
6
|
+
Author: Ivory Zhang
|
|
7
|
+
Author-email: ivoryzhang@chem.ubc.ca
|
|
8
|
+
License: MIT
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Dist: ax-platform
|
|
12
|
+
Requires-Dist: bcrypt
|
|
13
|
+
Requires-Dist: Flask-Login
|
|
14
|
+
Requires-Dist: Flask-Session
|
|
15
|
+
Requires-Dist: Flask-SocketIO
|
|
16
|
+
Requires-Dist: Flask-SQLAlchemy
|
|
17
|
+
Requires-Dist: Flask-WTF
|
|
18
|
+
Requires-Dist: mysqlclient==2.1.1
|
|
19
|
+
Requires-Dist: SQLAlchemy-Utils
|
|
20
|
+
Requires-Dist: openai
|
|
21
|
+
Requires-Dist: python-dotenv
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
# ivoryOS: interoperable Web UI for SDLs
|
|
25
|
+
ivoryOS is an open-source Python package enabling SDL interoperability. Add a web UI to SDLs anytime.
|
|
26
|
+
## Description
|
|
27
|
+
Granting SDLs flexibility and modularity makes it almost impossible to design a UI, yet it's a necessity for allowing more people to interact with it (democratisation).
|
|
28
|
+
This web UI aims to ease up the control of any Python-based SDLs by displaying functions and parameters for initialized modules dynamically.
|
|
29
|
+
The modules can be hardware API, high-level functions, or experiment workflow.
|
|
30
|
+
With the least modification of the current workflow, user can design, save and run their experiment and monitor the process.
|
|
31
|
+
## Design AI-assistant
|
|
32
|
+
To streamline the experimental design on SDLs, we also integrate Large Language Models (LLMs) to interpret the inspected functions and generate code according to task descriptions.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
```
|
|
36
|
+
pip install ivoryos
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
In your SDL script, use `ivoryos(__name__)`. Example in [sample_code.py](example/dummy_ur/dummy_deck.py)
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from ivoryos.app import ivoryos
|
|
44
|
+
ivoryos(__name__)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## Enable LLMs with [OpenAI API](https://github.com/openai/openai-python)
|
|
49
|
+
1. Create a `.env` file in project repo for `OPENAI_API_KEY`
|
|
50
|
+
```
|
|
51
|
+
OPENAI_API_KEY="Your API Key"
|
|
52
|
+
```
|
|
53
|
+
2. In your SDL script, define model, you can use any GPT models.
|
|
54
|
+
```python
|
|
55
|
+
from ivoryos.app import ivoryos
|
|
56
|
+
ivoryos(__name__, model="gpt-3.5-turbo")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Enable local LLMs with [Ollama](https://ollama.com/)
|
|
60
|
+
1. Download Ollama.
|
|
61
|
+
2. pull models from Ollama
|
|
62
|
+
3. In your SDL script, define LLM server and model, you can use any models available on Ollama.
|
|
63
|
+
```python
|
|
64
|
+
from ivoryos.app import ivoryos
|
|
65
|
+
ivoryos(__name__, llm_server="localhost", model="llama3.1")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Developing
|
|
69
|
+
coming soon
|
|
70
|
+
|
|
71
|
+
## Authors and Acknowledgement
|
|
72
|
+
Ivory Zhang, Lucy Hao
|
|
73
|
+
|
|
74
|
+
Authors acknowledge all former and current Hein Lab members for their valuable suggestions.
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
[LICENSE](LICENSE)
|
ivoryOS-0.1.0/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+

|
|
2
|
+
# ivoryOS: interoperable Web UI for SDLs
|
|
3
|
+
ivoryOS is an open-source Python package enabling SDL interoperability. Add a web UI to SDLs anytime.
|
|
4
|
+
## Description
|
|
5
|
+
Granting SDLs flexibility and modularity makes it almost impossible to design a UI, yet it's a necessity for allowing more people to interact with it (democratisation).
|
|
6
|
+
This web UI aims to ease up the control of any Python-based SDLs by displaying functions and parameters for initialized modules dynamically.
|
|
7
|
+
The modules can be hardware API, high-level functions, or experiment workflow.
|
|
8
|
+
With the least modification of the current workflow, user can design, save and run their experiment and monitor the process.
|
|
9
|
+
## Design AI-assistant
|
|
10
|
+
To streamline the experimental design on SDLs, we also integrate Large Language Models (LLMs) to interpret the inspected functions and generate code according to task descriptions.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
```
|
|
14
|
+
pip install ivoryos
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
In your SDL script, use `ivoryos(__name__)`. Example in [sample_code.py](example/dummy_ur/dummy_deck.py)
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from ivoryos.app import ivoryos
|
|
22
|
+
ivoryos(__name__)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Enable LLMs with [OpenAI API](https://github.com/openai/openai-python)
|
|
27
|
+
1. Create a `.env` file in project repo for `OPENAI_API_KEY`
|
|
28
|
+
```
|
|
29
|
+
OPENAI_API_KEY="Your API Key"
|
|
30
|
+
```
|
|
31
|
+
2. In your SDL script, define model, you can use any GPT models.
|
|
32
|
+
```python
|
|
33
|
+
from ivoryos.app import ivoryos
|
|
34
|
+
ivoryos(__name__, model="gpt-3.5-turbo")
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Enable local LLMs with [Ollama](https://ollama.com/)
|
|
38
|
+
1. Download Ollama.
|
|
39
|
+
2. pull models from Ollama
|
|
40
|
+
3. In your SDL script, define LLM server and model, you can use any models available on Ollama.
|
|
41
|
+
```python
|
|
42
|
+
from ivoryos.app import ivoryos
|
|
43
|
+
ivoryos(__name__, llm_server="localhost", model="llama3.1")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Developing
|
|
47
|
+
coming soon
|
|
48
|
+
|
|
49
|
+
## Authors and Acknowledgement
|
|
50
|
+
Ivory Zhang, Lucy Hao
|
|
51
|
+
|
|
52
|
+
Authors acknowledge all former and current Hein Lab members for their valuable suggestions.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
[LICENSE](LICENSE)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from example.abstract_robot_classes.abclocation import ABCLocation
|
|
5
|
+
from example.abstract_robot_classes.moveable import Moveable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Arm(ABC):
|
|
9
|
+
def __init__(self, name: str):
|
|
10
|
+
self.name = name
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def move(self, from_loc: ABCLocation, to_loc: ABCLocation, moveable: Optional[Moveable] = None):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def pick_up(self, moveable: Moveable, location: ABCLocation):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def put_down(self, moveable: Moveable, location: ABCLocation):
|
|
22
|
+
pass
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from example.abstract_robot_classes.chemical import Chemical
|
|
5
|
+
from example.abstract_robot_classes.moveable import Moveable
|
|
6
|
+
from example.kinova_deck.moving.kinova_movable_config import KinovaMoveableConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Container(Moveable, ABC):
|
|
10
|
+
def __init__(self, name: str, config: Optional[KinovaMoveableConfig] = None):
|
|
11
|
+
super().__init__(name, config)
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def update_contents(self, chemical: Chemical, amount_ul: int):
|
|
15
|
+
pass
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
from .arm import Arm
|
|
4
|
+
from .chemical import Chemical
|
|
5
|
+
from .container import Container
|
|
6
|
+
from .abclocation import ABCLocation
|
|
7
|
+
from .moveable import Moveable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Handler(Moveable, ABC):
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def dispense(self, chemical: Chemical, container: Container, location: ABCLocation, arm: Arm):
|
|
13
|
+
pass
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from example.abstract_robot_classes.moveableconfig import MoveableConfig
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Moveable(ABC):
|
|
8
|
+
def __init__(self, name: str,
|
|
9
|
+
config: Optional[MoveableConfig] = None,
|
|
10
|
+
offset_x: float = 0,
|
|
11
|
+
offset_y: float = 0,
|
|
12
|
+
offset_z: float = 0):
|
|
13
|
+
self.name = name
|
|
14
|
+
self.gripped = False
|
|
15
|
+
self.config: Optional[MoveableConfig] = config
|
|
16
|
+
self.offset_x = offset_x
|
|
17
|
+
self.offset_y = offset_y
|
|
18
|
+
self.offset_z = offset_z
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class DummyBalance:
|
|
2
|
+
|
|
3
|
+
def __init__(self, com_port: str):
|
|
4
|
+
self.com_port = com_port
|
|
5
|
+
|
|
6
|
+
def weigh_sample(self):
|
|
7
|
+
print(f"Weighing sample using {self.com_port}")
|
|
8
|
+
|
|
9
|
+
def dose_solid(self, amount_in_mg: float):
|
|
10
|
+
print(f"Dosing {amount_in_mg} mg using {self.com_port}")
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from example.dummy_ur.dummy_balance import DummyBalance
|
|
4
|
+
from example.dummy_ur.dummy_pump import DummyPump
|
|
5
|
+
|
|
6
|
+
# some constant values
|
|
7
|
+
a = {"a": 1, "b": 3}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DummySDLDeck:
|
|
11
|
+
def __init__(self, pump: DummyPump, balance: DummyBalance):
|
|
12
|
+
self.pump = pump
|
|
13
|
+
self.balance = balance
|
|
14
|
+
|
|
15
|
+
def dose_solvent(self, name: str, amount_in_ml: float = 5, rate_ml_per_minute: float = 1):
|
|
16
|
+
self.pump.dose_liquid(amount_in_ml=amount_in_ml, rate_ml_per_minute=rate_ml_per_minute)
|
|
17
|
+
self.balance.weigh_sample()
|
|
18
|
+
print("dosing solvent")
|
|
19
|
+
|
|
20
|
+
def dose_solid(self, amount_in_mg: float = 5, bring_in: bool = False):
|
|
21
|
+
"""dose current chemical"""
|
|
22
|
+
self.balance.dose_solid(amount_in_mg=amount_in_mg)
|
|
23
|
+
self.balance.weigh_sample()
|
|
24
|
+
print("dosing solid")
|
|
25
|
+
|
|
26
|
+
def equilibrate(self, temp: float, duration: float):
|
|
27
|
+
print("equilibrate")
|
|
28
|
+
|
|
29
|
+
def sample(self, sample_volume: float = 10):
|
|
30
|
+
print("sample")
|
|
31
|
+
|
|
32
|
+
def dilute(self, solvent: str, factor: float):
|
|
33
|
+
print("dilute")
|
|
34
|
+
|
|
35
|
+
def analyze(self):
|
|
36
|
+
print("analyze")
|
|
37
|
+
|
|
38
|
+
def filtration(self):
|
|
39
|
+
print("filtration")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
balance = DummyBalance("Fake com port 1")
|
|
43
|
+
pump = DummyPump("Fake com port 2")
|
|
44
|
+
deck = DummySDLDeck(pump, balance)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DummyExperiment:
|
|
48
|
+
def __init__(self, deck):
|
|
49
|
+
self.deck = deck
|
|
50
|
+
|
|
51
|
+
def cooling_crystal(self,
|
|
52
|
+
low_temp: float,
|
|
53
|
+
temp_diff: float,
|
|
54
|
+
cooling_rate: float,
|
|
55
|
+
mass_mg: float,
|
|
56
|
+
solvent_A: str,
|
|
57
|
+
solvent_B: str,
|
|
58
|
+
solvent_ratio: float
|
|
59
|
+
):
|
|
60
|
+
print(type(mass_mg))
|
|
61
|
+
|
|
62
|
+
def reslurry(self):
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
def solubility(self,
|
|
66
|
+
temp: float,
|
|
67
|
+
temp_time_min: float,
|
|
68
|
+
duplicates:int,
|
|
69
|
+
):
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
def calibration_curve(self,
|
|
73
|
+
mass_in_mg: float,
|
|
74
|
+
temp: float,
|
|
75
|
+
temp_time_min: float,
|
|
76
|
+
initial_volume_ml: float,
|
|
77
|
+
sample_volume_ml: float,
|
|
78
|
+
dilution_factor: int,
|
|
79
|
+
number_of_dilutions: int,
|
|
80
|
+
):
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
cryst_co = DummyExperiment(deck)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ShakerDemo:
|
|
88
|
+
def __init__(self, deck):
|
|
89
|
+
self.deck = deck
|
|
90
|
+
|
|
91
|
+
def move_sample_to_shaker(self, location:str="A1"):
|
|
92
|
+
pass
|
|
93
|
+
|
|
94
|
+
def shake_for_duration(self, duration: float):
|
|
95
|
+
time.sleep(duration)
|
|
96
|
+
|
|
97
|
+
def move_vial_to_tray(self, location:str="A1"):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
vial_shaker = ShakerDemo(deck)
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
from ivoryos.app import ivoryos
|
|
104
|
+
ivoryos(__name__, model="llama3.1", llm_server='localhost',)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ivoryos(__name__, model="gpt-3.5-turbo")
|
|
108
|
+
|
|
109
|
+
"""add 10 mg of acetaminophen, dose 1 ml of methanol, equilibrate for 10 minute at 50 degrees, filter the sample and analyze with HPLC"""
|
|
110
|
+
"""i want to run a cooling crystal experiment, with low temperature being 20, and high temperature being 40, cooling rate being 0.1, I want to dose 11 mg of my sample, and use
|
|
111
|
+
water as solvent a and isopropanol for solvent b, the solvent ratio being 0.1"""
|
|
112
|
+
|
|
113
|
+
|
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from example.abstract_robot_classes.chemical import Chemical
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Liquid(Chemical):
|
|
5
|
+
def __init__(self, name: str, formula: str, density: float = -1):
|
|
6
|
+
self.name = name
|
|
7
|
+
self.formula = formula
|
|
8
|
+
self.density: float = density
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Solid(Chemical):
|
|
12
|
+
def __init__(self, name: str, formula: str, density: float = -1):
|
|
13
|
+
self.name = name
|
|
14
|
+
self.formula = formula
|
|
15
|
+
self.density: float = density
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from serial import Serial
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Balance:
|
|
5
|
+
def __init__(self, port: str):
|
|
6
|
+
self.port = port
|
|
7
|
+
self.ser = Serial(port)
|
|
8
|
+
|
|
9
|
+
def stable_weight(self):
|
|
10
|
+
response = self.send_command("S")
|
|
11
|
+
return self.get_reading(response)
|
|
12
|
+
|
|
13
|
+
def weight_immediately(self):
|
|
14
|
+
response = self.send_command("SI")
|
|
15
|
+
return self.get_reading(response)
|
|
16
|
+
|
|
17
|
+
def tare(self):
|
|
18
|
+
response = self.send_command("T")
|
|
19
|
+
return self.get_reading(response)
|
|
20
|
+
|
|
21
|
+
def zero(self):
|
|
22
|
+
self.send_command("Z")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
def tare_immediately(self):
|
|
26
|
+
response = self.ser.write("TI")
|
|
27
|
+
return self.get_reading(response)
|
|
28
|
+
|
|
29
|
+
def zero_immediately(self):
|
|
30
|
+
self.send_command("ZI")
|
|
31
|
+
|
|
32
|
+
def send_command(self, command):
|
|
33
|
+
if not command.endswith("\n\r"):
|
|
34
|
+
command += "\n\r"
|
|
35
|
+
self.ser.write(command.encode())
|
|
36
|
+
response = self.ser.readline()
|
|
37
|
+
return response.decode().strip()
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def get_reading(response):
|
|
41
|
+
_, _, number, unit = response.split()
|
|
42
|
+
return (float(number))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
b = Balance("COM7")
|
|
47
|
+
w = b.stable_weight()
|
|
48
|
+
print(w)
|
|
49
|
+
print()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from ftdi_serial import Serial
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
decapper
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
class Capper:
|
|
8
|
+
def __init__(self, port, baud=115200, bytesize=8, timeout=20):
|
|
9
|
+
# self.somatic = serial_.Serial(port=port, baudrate=baud, bytesize=bytesize,
|
|
10
|
+
# timeout=timeout, stopbits=serial_.STOPBITS_ONE)
|
|
11
|
+
self.capper = Serial(device_port=port, baudrate=baud, data_bits=bytesize, read_timeout=timeout)
|
|
12
|
+
|
|
13
|
+
print(f"Established connection with {self.capper} at {port}")
|
|
14
|
+
|
|
15
|
+
def connect(self):
|
|
16
|
+
self.capper.__init__()
|
|
17
|
+
|
|
18
|
+
def disconnect(self):
|
|
19
|
+
# put capper in its default state of "closed" before closing connection
|
|
20
|
+
# self.home()
|
|
21
|
+
self.capper.close()
|
|
22
|
+
print("Disconnected capper")
|
|
23
|
+
|
|
24
|
+
def open(self):
|
|
25
|
+
cmd = f"open"
|
|
26
|
+
self._execute(cmd)
|
|
27
|
+
|
|
28
|
+
def close(self):
|
|
29
|
+
cmd = "close"
|
|
30
|
+
self._execute(cmd)
|
|
31
|
+
|
|
32
|
+
def tighten(self):
|
|
33
|
+
cmd = "tighten"
|
|
34
|
+
self._execute(cmd)
|
|
35
|
+
|
|
36
|
+
def loosen(self):
|
|
37
|
+
cmd = "loosen"
|
|
38
|
+
self._execute(cmd)
|
|
39
|
+
|
|
40
|
+
def home(self):
|
|
41
|
+
cmd = "home"
|
|
42
|
+
self._execute(cmd)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _execute(self, cmd, wait=True):
|
|
46
|
+
ans = None
|
|
47
|
+
self.capper.write(cmd.encode())
|
|
48
|
+
|
|
49
|
+
# Serial device returns "-4" when action complete
|
|
50
|
+
while True:
|
|
51
|
+
# ans = self.somatic.readline().decode("ascii").strip()
|
|
52
|
+
ans = self.capper.read_line().decode("ascii").strip()
|
|
53
|
+
if ans == "1":
|
|
54
|
+
break
|
|
55
|
+
return ans
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
capper = Capper("COM9")
|
|
60
|
+
capper.home()
|
|
61
|
+
capper.open()
|
|
62
|
+
capper.close()
|
|
63
|
+
# x.loosen()
|
|
64
|
+
# x.tighten()
|
|
65
|
+
# time.sleep(3)
|
|
66
|
+
# x.home()
|
|
67
|
+
|
|
68
|
+
|