feather-xrol 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.
- feather_xrol-0.1.0/LICENSE +28 -0
- feather_xrol-0.1.0/PKG-INFO +65 -0
- feather_xrol-0.1.0/README.md +51 -0
- feather_xrol-0.1.0/pyproject.toml +20 -0
- feather_xrol-0.1.0/setup.cfg +4 -0
- feather_xrol-0.1.0/src/feather_xrol/__init__.py +2 -0
- feather_xrol-0.1.0/src/feather_xrol/attocube.py +50 -0
- feather_xrol-0.1.0/src/feather_xrol/hexapod.py +27 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/ACS.py +245 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/Examples/IDS_Example.py +57 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/SEN.py +51 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/__init__.py +1 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/about.py +37 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/access.py +62 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/adjustment.py +39 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/axis.py +133 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/displacement.py +168 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/ecu.py +170 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/manual.py +153 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/network.py +364 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/nlc.py +241 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/pilotlaser.py +39 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/realtime.py +522 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/Examples/__init__.py +0 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/Examples/streaming_example_animate_loop.py +160 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/Examples/streaming_example_backgroundStreaming.py +76 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/Examples/streaming_example_while_loop.py +63 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/__init__.py +0 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/dll_wrapper.py +47 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/file_parser.py +130 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/stream.py +262 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/streaming/streaming.py +143 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/system.py +294 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/system_service.py +214 -0
- feather_xrol-0.1.0/src/feather_xrol/ids_api/update.py +83 -0
- feather_xrol-0.1.0/src/feather_xrol.egg-info/PKG-INFO +65 -0
- feather_xrol-0.1.0/src/feather_xrol.egg-info/SOURCES.txt +38 -0
- feather_xrol-0.1.0/src/feather_xrol.egg-info/dependency_links.txt +1 -0
- feather_xrol-0.1.0/src/feather_xrol.egg-info/requires.txt +5 -0
- feather_xrol-0.1.0/src/feather_xrol.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Dream Beam
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: feather-xrol
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bluesky and Ophyd integration for Attocube interferometer and Hexapod
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: bluesky
|
|
9
|
+
Requires-Dist: ophyd
|
|
10
|
+
Requires-Dist: pyepics
|
|
11
|
+
Requires-Dist: tiled
|
|
12
|
+
Requires-Dist: python-dotenv
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# Project Feather
|
|
16
|
+
contact: awojdyla@lbl.gov
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
Project Feather aims at expending the use of [bluesky data collection framework](https://blueskyproject.io/bluesky/main/index.html) beyond synchrotron beamlines
|
|
20
|
+
|
|
21
|
+
We want to use Tiled to collect data from optical experiments, or to store metrology data so that it can easily be accessed.
|
|
22
|
+
|
|
23
|
+
Eventually, you might even be able to start a container that has everything you need.
|
|
24
|
+
|
|
25
|
+
## feather-xrol Package
|
|
26
|
+
|
|
27
|
+
Integration for Attocube interferometer and Hexapod.
|
|
28
|
+
|
|
29
|
+
### Installation
|
|
30
|
+
|
|
31
|
+
#### From Source (Development)
|
|
32
|
+
To install the package locally in editable mode for development:
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Usage
|
|
38
|
+
|
|
39
|
+
The package provides Ophyd device definitions for the ALSHexapod and Attocube interferometer.
|
|
40
|
+
|
|
41
|
+
#### Hexapod Example
|
|
42
|
+
See `examples/hexapod_scan.py` for a complete example of running a scan.
|
|
43
|
+
```python
|
|
44
|
+
from feather_xrol.hexapod import ALSHexapod
|
|
45
|
+
from bluesky import RunEngine
|
|
46
|
+
from bluesky.plans import scan
|
|
47
|
+
|
|
48
|
+
hexapod = ALSHexapod('ALS:XROL-HEX0', name='hexapod')
|
|
49
|
+
RE = RunEngine({})
|
|
50
|
+
RE(scan([hexapod.x], hexapod.x, 0.0, 5.0, 10))
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Attocube Example
|
|
54
|
+
See `examples/test_attocube.py` for Attocube axis integration.
|
|
55
|
+
```python
|
|
56
|
+
from feather_xrol.attocube import AttocubeAxis
|
|
57
|
+
from feather_xrol.ids_api.SEN import Device as AttocubeIDS3010
|
|
58
|
+
|
|
59
|
+
ids3010 = AttocubeIDS3010("192.168.10.81")
|
|
60
|
+
ids3010.connect()
|
|
61
|
+
atto = AttocubeAxis("atto_3", hardware_interface=ids3010, axis=2)
|
|
62
|
+
atto.trigger()
|
|
63
|
+
print(atto.read())
|
|
64
|
+
ids3010.close()
|
|
65
|
+
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Project Feather
|
|
2
|
+
contact: awojdyla@lbl.gov
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Project Feather aims at expending the use of [bluesky data collection framework](https://blueskyproject.io/bluesky/main/index.html) beyond synchrotron beamlines
|
|
6
|
+
|
|
7
|
+
We want to use Tiled to collect data from optical experiments, or to store metrology data so that it can easily be accessed.
|
|
8
|
+
|
|
9
|
+
Eventually, you might even be able to start a container that has everything you need.
|
|
10
|
+
|
|
11
|
+
## feather-xrol Package
|
|
12
|
+
|
|
13
|
+
Integration for Attocube interferometer and Hexapod.
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
|
|
17
|
+
#### From Source (Development)
|
|
18
|
+
To install the package locally in editable mode for development:
|
|
19
|
+
```bash
|
|
20
|
+
pip install -e .
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Usage
|
|
24
|
+
|
|
25
|
+
The package provides Ophyd device definitions for the ALSHexapod and Attocube interferometer.
|
|
26
|
+
|
|
27
|
+
#### Hexapod Example
|
|
28
|
+
See `examples/hexapod_scan.py` for a complete example of running a scan.
|
|
29
|
+
```python
|
|
30
|
+
from feather_xrol.hexapod import ALSHexapod
|
|
31
|
+
from bluesky import RunEngine
|
|
32
|
+
from bluesky.plans import scan
|
|
33
|
+
|
|
34
|
+
hexapod = ALSHexapod('ALS:XROL-HEX0', name='hexapod')
|
|
35
|
+
RE = RunEngine({})
|
|
36
|
+
RE(scan([hexapod.x], hexapod.x, 0.0, 5.0, 10))
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
#### Attocube Example
|
|
40
|
+
See `examples/test_attocube.py` for Attocube axis integration.
|
|
41
|
+
```python
|
|
42
|
+
from feather_xrol.attocube import AttocubeAxis
|
|
43
|
+
from feather_xrol.ids_api.SEN import Device as AttocubeIDS3010
|
|
44
|
+
|
|
45
|
+
ids3010 = AttocubeIDS3010("192.168.10.81")
|
|
46
|
+
ids3010.connect()
|
|
47
|
+
atto = AttocubeAxis("atto_3", hardware_interface=ids3010, axis=2)
|
|
48
|
+
atto.trigger()
|
|
49
|
+
print(atto.read())
|
|
50
|
+
ids3010.close()
|
|
51
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "feather-xrol"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Bluesky and Ophyd integration for Attocube interferometer and Hexapod"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"bluesky",
|
|
13
|
+
"ophyd",
|
|
14
|
+
"pyepics",
|
|
15
|
+
"tiled",
|
|
16
|
+
"python-dotenv"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.setuptools.packages.find]
|
|
20
|
+
where = ["src"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from ophyd import Device
|
|
3
|
+
from ophyd.status import DeviceStatus
|
|
4
|
+
|
|
5
|
+
PM_TO_MM = 1e-9
|
|
6
|
+
|
|
7
|
+
class AttocubeAxis(Device):
|
|
8
|
+
|
|
9
|
+
def __init__(self, *args, hardware_interface, axis: int, **kwargs):
|
|
10
|
+
super().__init__(*args, **kwargs)
|
|
11
|
+
self._hw = hardware_interface
|
|
12
|
+
self._axis = axis
|
|
13
|
+
self._position_mm = None
|
|
14
|
+
self._timestamp = None
|
|
15
|
+
|
|
16
|
+
def trigger(self):
|
|
17
|
+
status = DeviceStatus(self)
|
|
18
|
+
try:
|
|
19
|
+
error, value = self._hw.displacement.getAbsolutePosition(self._axis)
|
|
20
|
+
if not error:
|
|
21
|
+
self._position_mm = value * PM_TO_MM
|
|
22
|
+
self._timestamp = time.time()
|
|
23
|
+
status.set_finished()
|
|
24
|
+
else:
|
|
25
|
+
status.set_exception(Exception(f"Attocube error code: {error}"))
|
|
26
|
+
except Exception as exc:
|
|
27
|
+
status.set_exception(exc)
|
|
28
|
+
return status
|
|
29
|
+
|
|
30
|
+
def read(self):
|
|
31
|
+
return {
|
|
32
|
+
self.name: {
|
|
33
|
+
"value": self._position_mm,
|
|
34
|
+
"timestamp": self._timestamp,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def describe(self):
|
|
39
|
+
return {
|
|
40
|
+
self.name: {
|
|
41
|
+
"source": f"AttocubeIDS3010:axis{self._axis}",
|
|
42
|
+
"dtype": "number",
|
|
43
|
+
"shape": [],
|
|
44
|
+
"units": "mm",
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def hints(self):
|
|
50
|
+
return {"fields": [self.name]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ophyd import Device, PVPositioner, EpicsSignal, EpicsSignalRO
|
|
2
|
+
from ophyd import Component as Cpt, FormattedComponent as FCpt
|
|
3
|
+
|
|
4
|
+
class HexapodAxis(PVPositioner):
|
|
5
|
+
setpoint = Cpt(EpicsSignal, ':TARGET')
|
|
6
|
+
readback = Cpt(EpicsSignalRO, ':POSITION')
|
|
7
|
+
done = FCpt(EpicsSignalRO, '{self.parent.prefix}-alldone')
|
|
8
|
+
done_value = 'done'
|
|
9
|
+
high_limit = Cpt(EpicsSignalRO, ':HLM')
|
|
10
|
+
low_limit = Cpt(EpicsSignalRO, ':LLM')
|
|
11
|
+
servo = Cpt(EpicsSignal, ':SERVO')
|
|
12
|
+
rb_servo = Cpt(EpicsSignalRO, ':RBSERVO')
|
|
13
|
+
|
|
14
|
+
class ALSHexapod(Device):
|
|
15
|
+
"""ALS XROL Hexapod — 6DOF (X, Y, Z, U, V, W)"""
|
|
16
|
+
x = Cpt(HexapodAxis, '-X')
|
|
17
|
+
y = Cpt(HexapodAxis, '-Y')
|
|
18
|
+
z = Cpt(HexapodAxis, '-Z')
|
|
19
|
+
u = Cpt(HexapodAxis, '-U')
|
|
20
|
+
v = Cpt(HexapodAxis, '-V')
|
|
21
|
+
w = Cpt(HexapodAxis, '-W')
|
|
22
|
+
# Shared status / control
|
|
23
|
+
alldone = Cpt(EpicsSignalRO, '-alldone')
|
|
24
|
+
allstop = Cpt(EpicsSignal, '-allstop')
|
|
25
|
+
moving = Cpt(EpicsSignalRO, '-moving')
|
|
26
|
+
clear_err = Cpt(EpicsSignal, '-CLEAR_ERR')
|
|
27
|
+
last_err = Cpt(EpicsSignalRO, '-LAST_ERR')
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import socket
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from time import time, sleep
|
|
8
|
+
|
|
9
|
+
from random import randint
|
|
10
|
+
|
|
11
|
+
from threading import Thread, Lock
|
|
12
|
+
try:
|
|
13
|
+
import netifaces
|
|
14
|
+
except:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
import urllib.request
|
|
18
|
+
import xml.dom.minidom as minidom
|
|
19
|
+
|
|
20
|
+
class AttoException(Exception):
|
|
21
|
+
def __init__(self, errorText = None, errorNumber = 0):
|
|
22
|
+
self.errorText = errorText
|
|
23
|
+
self.errorNumber = errorNumber
|
|
24
|
+
|
|
25
|
+
class AttoResult():
|
|
26
|
+
def __init__(self, resultDict):
|
|
27
|
+
self.resultDict = resultDict
|
|
28
|
+
|
|
29
|
+
def __getitem__(self, index):
|
|
30
|
+
if "error" in self.resultDict:
|
|
31
|
+
raise AttoException("JSON error in %s" % self.resultDict['error'])
|
|
32
|
+
|
|
33
|
+
resultList = self.resultDict.get("result", [])
|
|
34
|
+
if len(resultList) <= index:
|
|
35
|
+
raise AttoException(errorText="Unknown error occured", errorNumber=-1)
|
|
36
|
+
return resultList[index]
|
|
37
|
+
|
|
38
|
+
def __repr__(self):
|
|
39
|
+
return json.dumps(self.resultDict)
|
|
40
|
+
|
|
41
|
+
def __str__(self):
|
|
42
|
+
return self.__repr__()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Device(object):
|
|
46
|
+
TCP_PORT = 9090
|
|
47
|
+
is_open = False
|
|
48
|
+
request_id = randint(0, 1000000)
|
|
49
|
+
request_id_lock = Lock()
|
|
50
|
+
response_buffer = {}
|
|
51
|
+
|
|
52
|
+
def __init__(self, address):
|
|
53
|
+
self.address = address
|
|
54
|
+
self.language = 0
|
|
55
|
+
self.apiversion = 2
|
|
56
|
+
self.response_lock = Lock()
|
|
57
|
+
|
|
58
|
+
def __del__(self):
|
|
59
|
+
self.close()
|
|
60
|
+
|
|
61
|
+
def __enter__(self):
|
|
62
|
+
self.connect()
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
66
|
+
self.close()
|
|
67
|
+
|
|
68
|
+
def connect(self):
|
|
69
|
+
"""
|
|
70
|
+
Initializes and connects the selected AMC device.
|
|
71
|
+
"""
|
|
72
|
+
if not self.is_open:
|
|
73
|
+
tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
74
|
+
tcp.settimeout(10)
|
|
75
|
+
tcp.connect((self.address, self.TCP_PORT))
|
|
76
|
+
self.tcp = tcp
|
|
77
|
+
if sys.version_info[0] > 2:
|
|
78
|
+
self.bufferedSocket = tcp.makefile("rw", newline='\r\n')
|
|
79
|
+
else:
|
|
80
|
+
self.bufferedSocket = tcp.makefile("rw")
|
|
81
|
+
self.is_open = True
|
|
82
|
+
|
|
83
|
+
def close(self):
|
|
84
|
+
"""
|
|
85
|
+
Closes the connection to the device.
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
"""
|
|
89
|
+
if self.is_open:
|
|
90
|
+
self.bufferedSocket.close()
|
|
91
|
+
self.tcp.close()
|
|
92
|
+
self.is_open = False
|
|
93
|
+
|
|
94
|
+
def sendRequest(self, method, params=False):
|
|
95
|
+
req = {
|
|
96
|
+
"jsonrpc": "2.0",
|
|
97
|
+
"method": method,
|
|
98
|
+
"api": self.apiversion,
|
|
99
|
+
"pid": self.pid
|
|
100
|
+
}
|
|
101
|
+
if params:
|
|
102
|
+
req["params"] = params
|
|
103
|
+
with Device.request_id_lock:
|
|
104
|
+
req["id"] = Device.request_id
|
|
105
|
+
self.bufferedSocket.write(json.dumps(req))
|
|
106
|
+
self.bufferedSocket.flush()
|
|
107
|
+
Device.request_id = Device.request_id + 1
|
|
108
|
+
return req["id"]
|
|
109
|
+
|
|
110
|
+
def getResponse(self, request_id):
|
|
111
|
+
start_time = time()
|
|
112
|
+
while True:
|
|
113
|
+
if request_id in self.response_buffer:
|
|
114
|
+
response = self.response_buffer[request_id]
|
|
115
|
+
del self.response_buffer[request_id]
|
|
116
|
+
return response
|
|
117
|
+
if time() - start_time > 10:
|
|
118
|
+
raise TimeoutError("No result")
|
|
119
|
+
|
|
120
|
+
# Only one thread is allowed to read buffer
|
|
121
|
+
# Otherwise, deadlock is possible
|
|
122
|
+
if self.response_lock.acquire(blocking=False):
|
|
123
|
+
try:
|
|
124
|
+
response = self.bufferedSocket.readline()
|
|
125
|
+
parsed = json.loads(response)
|
|
126
|
+
if parsed["id"] == request_id:
|
|
127
|
+
return AttoResult(parsed)
|
|
128
|
+
else:
|
|
129
|
+
self.response_buffer[parsed["id"]] = AttoResult(parsed)
|
|
130
|
+
finally:
|
|
131
|
+
self.response_lock.release()
|
|
132
|
+
else:
|
|
133
|
+
# Sleep to unblock scheduler
|
|
134
|
+
sleep(0.01)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def request(self,method,params=False):
|
|
138
|
+
""" Synchronous request.
|
|
139
|
+
"""
|
|
140
|
+
if not self.is_open:
|
|
141
|
+
raise AttoException("not connected, use connect()");
|
|
142
|
+
request_id = self.sendRequest(method, params)
|
|
143
|
+
return self.getResponse(request_id)
|
|
144
|
+
|
|
145
|
+
def printError(self, errorNumber):
|
|
146
|
+
""" Converts the errorNumber into an error string an prints it to the
|
|
147
|
+
console.
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
errorNumber : int
|
|
151
|
+
"""
|
|
152
|
+
print("Error! " + str(self.system_service.errorNumberToString(self.language, errorNumber)[1]))
|
|
153
|
+
|
|
154
|
+
def handleError(self, response, ignoreFunctionError=False):
|
|
155
|
+
errNo = response[0]
|
|
156
|
+
if (errNo != 0 and errNo != 'null' and not ignoreFunctionError):
|
|
157
|
+
raise AttoException(("Error! " + str(self.system_service.errorNumberToString(self.language ,errNo))), errNo)
|
|
158
|
+
return errNo
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def discover(cls):
|
|
162
|
+
try:
|
|
163
|
+
network_ifaces = netifaces.interfaces()
|
|
164
|
+
except NameError:
|
|
165
|
+
print("Install netifaces for discovery")
|
|
166
|
+
print("Python:")
|
|
167
|
+
print("pip install netifaces")
|
|
168
|
+
print("\nPython3:")
|
|
169
|
+
print("pip3 install netifaces")
|
|
170
|
+
return {}
|
|
171
|
+
|
|
172
|
+
msg = \
|
|
173
|
+
'M-SEARCH * HTTP/1.1\r\n' \
|
|
174
|
+
'HOST:239.255.255.250:1900\r\n' \
|
|
175
|
+
'ST:urn:schemas-attocube-com:device:' + str(cls) + ':1\r\n' \
|
|
176
|
+
'MX:2\r\n' \
|
|
177
|
+
'MAN:"ssdp:discover"\r\n' \
|
|
178
|
+
'\r\n'
|
|
179
|
+
|
|
180
|
+
def send_and_recv(iface, devices, devices_lock):
|
|
181
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
182
|
+
s.bind((iface, 0))
|
|
183
|
+
s.settimeout(2)
|
|
184
|
+
s.sendto(str.encode(msg), ('239.255.255.250', 1900))
|
|
185
|
+
try:
|
|
186
|
+
while True:
|
|
187
|
+
_, addr = s.recvfrom(65507)
|
|
188
|
+
with devices_lock:
|
|
189
|
+
devices.append(addr[0])
|
|
190
|
+
except socket.timeout:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
thread_pool = []
|
|
194
|
+
devices = []
|
|
195
|
+
devices_lock = Lock()
|
|
196
|
+
|
|
197
|
+
for iface in network_ifaces:
|
|
198
|
+
addr = netifaces.ifaddresses(iface)
|
|
199
|
+
if netifaces.AF_INET not in addr:
|
|
200
|
+
continue
|
|
201
|
+
for ip in addr[netifaces.AF_INET]:
|
|
202
|
+
if "addr" not in ip:
|
|
203
|
+
continue
|
|
204
|
+
thread_pool.append(Thread(target=send_and_recv, args=(ip["addr"], devices, devices_lock)))
|
|
205
|
+
thread_pool[-1].start()
|
|
206
|
+
|
|
207
|
+
for thread in thread_pool:
|
|
208
|
+
thread.join()
|
|
209
|
+
|
|
210
|
+
def getElementData(xmlNode, tag):
|
|
211
|
+
tagNodes = xmlNode.getElementsByTagName(tag)
|
|
212
|
+
if len(tagNodes) == 0:
|
|
213
|
+
return None
|
|
214
|
+
childNodes = tagNodes[0].childNodes
|
|
215
|
+
if len(childNodes) == 0:
|
|
216
|
+
return None
|
|
217
|
+
return childNodes[0].data
|
|
218
|
+
|
|
219
|
+
deviceInfos = {}
|
|
220
|
+
for ip in devices:
|
|
221
|
+
try:
|
|
222
|
+
location = "http://" + ip + ":49000/upnp.xml"
|
|
223
|
+
response = urllib.request.urlopen(location)
|
|
224
|
+
response = response.read()
|
|
225
|
+
xmlNode = minidom.parseString(response)
|
|
226
|
+
|
|
227
|
+
serialNumber = getElementData(xmlNode, 'serialNumber')
|
|
228
|
+
ipAddress = getElementData(xmlNode, 'ipAddress')
|
|
229
|
+
macAddress = getElementData(xmlNode, 'macAddress')
|
|
230
|
+
friendlyName = getElementData(xmlNode, 'friendlyName')
|
|
231
|
+
modelName = getElementData(xmlNode, 'modelName')
|
|
232
|
+
lockedStatus = getElementData(xmlNode, 'lockedStatus')
|
|
233
|
+
|
|
234
|
+
deviceInfos[ip] = (
|
|
235
|
+
serialNumber,
|
|
236
|
+
ipAddress,
|
|
237
|
+
macAddress,
|
|
238
|
+
friendlyName,
|
|
239
|
+
modelName,
|
|
240
|
+
lockedStatus
|
|
241
|
+
)
|
|
242
|
+
except:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
return deviceInfos
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from ids_api import SEN # IDS API is the folder, in which the API is located.
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
IP = "192.168.1.1" # IP of the IDS device
|
|
6
|
+
checkAlignment = True
|
|
7
|
+
data_samples = 10
|
|
8
|
+
|
|
9
|
+
# Setup connection to IDS
|
|
10
|
+
ids = SEN.Device(IP)
|
|
11
|
+
ids.connect()
|
|
12
|
+
|
|
13
|
+
axis = 0 # Internally, axes are numbered 0 to 2
|
|
14
|
+
|
|
15
|
+
ids.displacement.setAverageN(14) # this is important: it sets the average of the IDS displacement
|
|
16
|
+
|
|
17
|
+
# check alignment
|
|
18
|
+
if checkAlignment:
|
|
19
|
+
if ids.system.getCurrentMode() == 'measurement running':
|
|
20
|
+
ids.system.stopMeasurement()
|
|
21
|
+
time.sleep(3)
|
|
22
|
+
|
|
23
|
+
if ids.system.getCurrentMode() == "system idle":
|
|
24
|
+
ids.system.startOpticsAlignment()
|
|
25
|
+
while ids.system.getCurrentMode() == 'optics alignment starting':
|
|
26
|
+
time.sleep(1)
|
|
27
|
+
for i in range(5):
|
|
28
|
+
print("Alignment: ", ids.adjustment.getContrastInPermille(axis))
|
|
29
|
+
ids.system.stopOpticsAlignment()
|
|
30
|
+
time.sleep(5)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# check if the IDS measurement is running and start the measurement:
|
|
34
|
+
if ids.system.getCurrentMode() != 'measurement running':
|
|
35
|
+
if ids.system.getCurrentMode() == "optics alignment running":
|
|
36
|
+
ids.system.stopOpticsAlignment()
|
|
37
|
+
time.sleep(3)
|
|
38
|
+
if ids.system.getCurrentMode() == "system idle":
|
|
39
|
+
ids.system.startMeasurement()
|
|
40
|
+
time.sleep(1)
|
|
41
|
+
print('measurement will be started')
|
|
42
|
+
while ids.system.getCurrentMode() == "measurement starting":
|
|
43
|
+
time.sleep(2)
|
|
44
|
+
|
|
45
|
+
if ids.system.getCurrentMode() != 'measurement running':
|
|
46
|
+
raise Exception("IDS not in measurement mode. Please check mode!")
|
|
47
|
+
|
|
48
|
+
# readout data
|
|
49
|
+
for i in range(data_samples):
|
|
50
|
+
error, value = ids.displacement.getAxisDisplacement(axis)
|
|
51
|
+
print("Displacement: ", value)
|
|
52
|
+
time.sleep(0.5)
|
|
53
|
+
|
|
54
|
+
# turn off measurement mode
|
|
55
|
+
ids.system.stopMeasurement()
|
|
56
|
+
|
|
57
|
+
ids.close()
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from . import ACS
|
|
2
|
+
from .pilotlaser import Pilotlaser
|
|
3
|
+
from .update import Update
|
|
4
|
+
from .system import System
|
|
5
|
+
from .nlc import Nlc
|
|
6
|
+
from .realtime import Realtime
|
|
7
|
+
from .access import Access
|
|
8
|
+
from .adjustment import Adjustment
|
|
9
|
+
from .ecu import Ecu
|
|
10
|
+
from .manual import Manual
|
|
11
|
+
from .system_service import System_service
|
|
12
|
+
from .about import About
|
|
13
|
+
from .axis import Axis
|
|
14
|
+
from .network import Network
|
|
15
|
+
from .displacement import Displacement
|
|
16
|
+
try:
|
|
17
|
+
from .streaming.streaming import Streaming
|
|
18
|
+
except:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Device(ACS.Device):
|
|
23
|
+
def __init__(self, address):
|
|
24
|
+
super().__init__(address)
|
|
25
|
+
self.pid = "sen"
|
|
26
|
+
self.pilotlaser = Pilotlaser(self)
|
|
27
|
+
self.update = Update(self)
|
|
28
|
+
self.system = System(self)
|
|
29
|
+
self.nlc = Nlc(self)
|
|
30
|
+
self.realtime = Realtime(self)
|
|
31
|
+
self.access = Access(self)
|
|
32
|
+
self.adjustment = Adjustment(self)
|
|
33
|
+
self.ecu = Ecu(self)
|
|
34
|
+
self.manual = Manual(self)
|
|
35
|
+
self.system_service = System_service(self)
|
|
36
|
+
self.about = About(self)
|
|
37
|
+
self.axis = Axis(self)
|
|
38
|
+
self.network = Network(self)
|
|
39
|
+
self.displacement = Displacement(self)
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
self.streaming = Streaming(self)
|
|
43
|
+
except NameError as e:
|
|
44
|
+
if "Streaming" in str(e):
|
|
45
|
+
print("Warning: Streaming is not supported on your platform")
|
|
46
|
+
else:
|
|
47
|
+
raise e
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def discover():
|
|
51
|
+
return Device.discover("sen")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This marks the directory as a Python package
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
class About:
|
|
2
|
+
def __init__(self, device):
|
|
3
|
+
self.device = device
|
|
4
|
+
self.interface_name = "com.attocube.system.about"
|
|
5
|
+
|
|
6
|
+
def getInstalledPackages(self):
|
|
7
|
+
# type: () -> (str)
|
|
8
|
+
"""
|
|
9
|
+
Gets list of packages installed on the device.
|
|
10
|
+
Returns:
|
|
11
|
+
value_errNo: errNo error code, if there was an error, otherwise 0 for ok
|
|
12
|
+
value_string: string comma separated list of packages
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
response = self.device.request(self.interface_name + ".getInstalledPackages")
|
|
17
|
+
self.device.handleError(response)
|
|
18
|
+
return response[1]
|
|
19
|
+
|
|
20
|
+
def getPackageLicense(self, pckg):
|
|
21
|
+
# type: (str) -> (str)
|
|
22
|
+
"""
|
|
23
|
+
Gets the license for a specific package.
|
|
24
|
+
|
|
25
|
+
Parameters:
|
|
26
|
+
pckg: package name string
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
value_errNo: errNo error code, if there was an error, otherwise 0 for ok
|
|
30
|
+
value_string: string license for this package
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
response = self.device.request(self.interface_name + ".getPackageLicense", [pckg, ])
|
|
35
|
+
self.device.handleError(response)
|
|
36
|
+
return response[1]
|
|
37
|
+
|