ioiocore 0.0.1__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.
Files changed (37) hide show
  1. ioiocore-0.0.1/CHANGELOG.md +11 -0
  2. ioiocore-0.0.1/HOW_TO_BUILD.md +50 -0
  3. ioiocore-0.0.1/HOW_TO_BUILD.pdf +0 -0
  4. ioiocore-0.0.1/LICENSE +20 -0
  5. ioiocore-0.0.1/MANIFEST.in +1 -0
  6. ioiocore-0.0.1/PKG-INFO +37 -0
  7. ioiocore-0.0.1/README.md +22 -0
  8. ioiocore-0.0.1/ioiocore/__init__.py +22 -0
  9. ioiocore-0.0.1/ioiocore/__version__.py +12 -0
  10. ioiocore-0.0.1/ioiocore/pattern/__init__.py +0 -0
  11. ioiocore-0.0.1/ioiocore/pattern/frequency_generator.py +44 -0
  12. ioiocore-0.0.1/ioiocore/pattern/node.py +165 -0
  13. ioiocore-0.0.1/ioiocore/pattern/pipeline.py +96 -0
  14. ioiocore-0.0.1/ioiocore/pattern/sources.py +64 -0
  15. ioiocore-0.0.1/ioiocore/pattern/src/__init__.py +0 -0
  16. ioiocore-0.0.1/ioiocore/pattern/src/i_node.py +82 -0
  17. ioiocore-0.0.1/ioiocore/pattern/src/i_port.py +32 -0
  18. ioiocore-0.0.1/ioiocore/pattern/src/o_node.py +32 -0
  19. ioiocore-0.0.1/ioiocore/pattern/src/o_port.py +35 -0
  20. ioiocore-0.0.1/ioiocore/requirements.txt +1 -0
  21. ioiocore-0.0.1/ioiocore.egg-info/PKG-INFO +37 -0
  22. ioiocore-0.0.1/ioiocore.egg-info/SOURCES.txt +36 -0
  23. ioiocore-0.0.1/ioiocore.egg-info/dependency_links.txt +1 -0
  24. ioiocore-0.0.1/ioiocore.egg-info/top_level.txt +2 -0
  25. ioiocore-0.0.1/setup.cfg +7 -0
  26. ioiocore-0.0.1/setup.py +43 -0
  27. ioiocore-0.0.1/test/__init__.py +0 -0
  28. ioiocore-0.0.1/test/node_serialization.py +76 -0
  29. ioiocore-0.0.1/test/node_tests.py +89 -0
  30. ioiocore-0.0.1/test/node_tests_connect_by_id.py +116 -0
  31. ioiocore-0.0.1/test/node_tests_connect_by_name.py +140 -0
  32. ioiocore-0.0.1/test/node_tests_propagation.py +251 -0
  33. ioiocore-0.0.1/test/pipeline_tests.py +142 -0
  34. ioiocore-0.0.1/test/run_all.py +46 -0
  35. ioiocore-0.0.1/test/sources_test.py +157 -0
  36. ioiocore-0.0.1/test/template_nodes.py +103 -0
  37. ioiocore-0.0.1/test/utilities.py +12 -0
@@ -0,0 +1,11 @@
1
+ # Change Log
2
+
3
+ ## 0.0.1
4
+ ### Added
5
+ - Initial release
6
+
7
+ ### Changed
8
+ - Nothing
9
+
10
+ ### Removed
11
+ - Nothing
@@ -0,0 +1,50 @@
1
+ # Buld instructions
2
+ ## Prerequisites
3
+ - Visual Studio Code<br>
4
+ &nbsp;&nbsp;&nbsp; - Python (Microsoft) v2024.16.1 Extension<br>
5
+ &nbsp;&nbsp;&nbsp; - Python Debugger (Microsoft) v2024.10.00<br>
6
+ &nbsp;&nbsp;&nbsp; - Pylance (Microsoft) v2024.10.01 Extension<br>
7
+ &nbsp;&nbsp;&nbsp; - Markdown Preview Github Styling (Matt Bierner) v2.1.0 Extension<br>
8
+ - Python >= 3.11<br>
9
+ &nbsp;&nbsp;&nbsp; - pip (must be accessible from commandline)<br>
10
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Note**: ensure that the pip package is installed ```python -m ensurepip --upgrade```<br>
11
+ &nbsp;&nbsp;&nbsp; - pipreqs (must be accessible from commandline)<br>
12
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Note**: ensure that the pipreqs package is installed ```pip install pipreqs```<br>
13
+ &nbsp;&nbsp;&nbsp; - build (must be accessible from commandline)<br>
14
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Note**: ensure that the build package is installed ```pip install build```<br>
15
+ &nbsp;&nbsp;&nbsp; - setuptools<br>
16
+ &nbsp;&nbsp;&nbsp; - numpy (for tests only)<br>
17
+
18
+ ## Create requirements file
19
+
20
+ Create or update requirements.txt<br>
21
+ ```pipreqs ./ioiocore --force```
22
+
23
+ ## Build
24
+ Update version number:<br>
25
+ ```./ioiocore/___version__.py```
26
+
27
+ build wheel:<br>
28
+ ```python -m build```<br>
29
+ distributables are built to the ```./dist``` folder
30
+
31
+ ### Upload to pypi
32
+ Upload to pypi using the following command: <br>
33
+ ```python -m twine upload --repository pypi dist/*```
34
+
35
+ ## Installation
36
+ ### Manual installation
37
+ Copy ```.whl``` file from ```./dist``` folder and install from command line: <br>
38
+ ```pip install ./dist/ioiocore-0.0.1-py3-none-any.whl```
39
+
40
+ ### Installation using PyPi
41
+ Package can be downloaded, installed and updated via pypi if uploaded ([Upload to pypi](#upload-to-pypi)).<br>
42
+ ```pip install ioiocore```<br>
43
+ ```pip install ioiocore --upgrade```
44
+
45
+ ### Uninstall
46
+ ```pip uninstall ioiocore```
47
+
48
+ ## Run tests
49
+ Go to project base folder and execute:<br>
50
+ ```python ./test/run_all.py```
Binary file
ioiocore-0.0.1/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2024 g.tec medical engineering GmbH
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ include *.md *.pdf LICENSE .\\ioiocore\\requirements.txt CHANGELOG
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.1
2
+ Name: ioiocore
3
+ Version: 0.0.1
4
+ Summary: A realtime processing framework for python
5
+ Home-page: https://www.gtec.at
6
+ Author: g.tec medical engineering GmbH
7
+ Author-email: support@gtec.at
8
+ License: MIT
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+
16
+ # IOIOCore
17
+
18
+ IOIOCore is general purpose data propagation framework for realtime applications written in python.
19
+
20
+ # Node
21
+ A node is a individual unit or component that performs a specific task in a larger system. Each node typically has inputs, outputs, and processing logic that transforms data as it moves through the system. Nodes are interconnected to form a pipeline where the output of one node becomes the input of another, enabling complex workflows or data transformations.
22
+
23
+ ## Input and Output Ports
24
+ Nodes receive data through input ports and send processed data out through output ports. The number and type of ports may vary depending on the node's purpose.
25
+
26
+ ## Processing Logic
27
+ Each node contains specific logic or algorithms to process the data it receives. This could range from basic transformations (like filtering or mapping) to more complex operations (like aggregation or machine learning inference).
28
+
29
+ Create specific nodes by deriving from the Node parent class. Define input and output ports and implement the processing logic for child objects.
30
+
31
+ # Sources
32
+
33
+ TBD
34
+
35
+ # Pipeline
36
+
37
+ TBD
@@ -0,0 +1,22 @@
1
+ # IOIOCore
2
+
3
+ IOIOCore is general purpose data propagation framework for realtime applications written in python.
4
+
5
+ # Node
6
+ A node is a individual unit or component that performs a specific task in a larger system. Each node typically has inputs, outputs, and processing logic that transforms data as it moves through the system. Nodes are interconnected to form a pipeline where the output of one node becomes the input of another, enabling complex workflows or data transformations.
7
+
8
+ ## Input and Output Ports
9
+ Nodes receive data through input ports and send processed data out through output ports. The number and type of ports may vary depending on the node's purpose.
10
+
11
+ ## Processing Logic
12
+ Each node contains specific logic or algorithms to process the data it receives. This could range from basic transformations (like filtering or mapping) to more complex operations (like aggregation or machine learning inference).
13
+
14
+ Create specific nodes by deriving from the Node parent class. Define input and output ports and implement the processing logic for child objects.
15
+
16
+ # Sources
17
+
18
+ TBD
19
+
20
+ # Pipeline
21
+
22
+ TBD
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ioiocore
4
+ -------
5
+
6
+ A realtime processing framework for python
7
+
8
+ :copyright: (c) 2024 g.tec medical engineering GmbH
9
+
10
+ """
11
+
12
+ # compatibility
13
+ from __future__ import absolute_import, division, print_function
14
+
15
+ # get version
16
+ from .__version__ import __version__
17
+
18
+ # allow lazy loading
19
+ #pattern
20
+ from .pattern.node import Node
21
+ from .pattern.sources import ManualSource, FixedFrequencySource, DeviceSource, FrequencyGenerator
22
+ from .pattern.pipeline import Pipeline
@@ -0,0 +1,12 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ioiocore.version
4
+ ---------------
5
+
6
+ Version tracker.
7
+
8
+ :copyright: (c) 2024 g.tec medical engineering
9
+ """
10
+
11
+ VERSION = (0, 0, 1)
12
+ __version__ = ".".join(map(str, VERSION))
File without changes
@@ -0,0 +1,44 @@
1
+ import time
2
+ import threading
3
+ from abc import ABC, abstractmethod
4
+
5
+ class FrequencyGenerator(ABC):
6
+ def __init__(self, frequencyHz : float):
7
+ self.__realtimeClockRunning = False
8
+ self.__realtimeClockThread = None
9
+ self.__targetDt = 1000/frequencyHz
10
+ self.__timestampStart = None
11
+ self.__timestamp = 0
12
+ self.__samplesSent = 0
13
+
14
+ def __del__(self):
15
+ self.stop()
16
+
17
+ def start(self):
18
+ if not self.__realtimeClockRunning:
19
+ self.__realtimeClockRunning = True
20
+ self.__realtimeClockThread = threading.Thread(target=self.__updateThread_DoWork, daemon=True)
21
+ self.__realtimeClockThread.start()
22
+
23
+ def stop(self):
24
+ if self.__realtimeClockRunning:
25
+ self.__realtimeClockRunning = False
26
+ self.__realtimeClockThread.join(500)
27
+
28
+ def __updateThread_DoWork(self):
29
+ if self.__timestampStart is None:
30
+ self.__timestampStart = time.time()*1000
31
+ while self.__realtimeClockRunning:
32
+ self.__timestamp = time.time()*1000 - self.__timestampStart
33
+
34
+ if self.__samplesSent >= round(self.__timestamp / self.__targetDt):
35
+ time.sleep(self.__targetDt /1000)
36
+ else:
37
+ samplesToSend = round(self.__timestamp / self.__targetDt) - self.__samplesSent
38
+ for i in range(0, samplesToSend):
39
+ self.update()
40
+ self.__samplesSent += 1
41
+
42
+ @abstractmethod
43
+ def update(self):
44
+ pass
@@ -0,0 +1,165 @@
1
+ from .src.i_node import INode
2
+ from .src.o_node import ONode
3
+ from abc import abstractmethod
4
+ import json
5
+
6
+ class Node():
7
+ class __INode(INode):
8
+ def __init__(self, updateFcn, inputPorts = None ):
9
+ super().__init__(inputPorts)
10
+ self.__updateFcn = updateFcn
11
+
12
+ def __del__(self):
13
+ self.__updateFcn = None
14
+ super().__del__()
15
+
16
+ def update(self):
17
+ data = []
18
+ for i in range(0,len(self.InputPorts)):
19
+ if(self.InputPorts[i].DataCount > 0):
20
+ data.append(self.InputPorts[i].read())
21
+ else:
22
+ data.append(None)
23
+ self.__updateFcn(data)
24
+
25
+ class __ONode(ONode):
26
+ def __init__(self, outputPorts = None ):
27
+ super().__init__(outputPorts)
28
+
29
+ def __del__(self):
30
+ super().__del__()
31
+
32
+ def write_internal(self, data):
33
+ if len(data) is not len(self.OutputPorts):
34
+ raise ValueError('Number of OutputPorts do not match received data.')
35
+
36
+ for i in range(0,len(data)):
37
+ self.write(i, data[i])
38
+
39
+ def __init__(self, inputPorts : list[str] = None , outputPorts : list[str] = None):
40
+ if inputPorts is None and outputPorts is None:
41
+ raise ValueError('Node without inputs and outputs is not allowed. \'inputPorts\' and/or \'outputPorts\' must be defined.')
42
+ if inputPorts is not None:
43
+ duplicates = self.__checkForDuplicates(inputPorts)
44
+ if duplicates:
45
+ raise ValueError(f'Duplicate input port names found {duplicates}. Every port must feature a unique name')
46
+ if outputPorts is not None:
47
+ duplicates = self.__checkForDuplicates(outputPorts)
48
+ if duplicates:
49
+ raise ValueError(f'Duplicate input port names found {duplicates}. Every port must feature a unique name')
50
+ if inputPorts is not None and outputPorts is not None:
51
+ duplicates = self.__checkForDuplicates(outputPorts + inputPorts)
52
+ if len(duplicates) > 0:
53
+ raise ValueError(f'Duplicate port names found {duplicates}. Every port must feature a unique name')
54
+ self.__inode = None
55
+ self.__onode = None
56
+ if inputPorts is not None:
57
+ self.__inode = self.__INode(self.__update, inputPorts)
58
+ if outputPorts is not None:
59
+ self.__onode = self.__ONode(outputPorts)
60
+
61
+ def __checkForDuplicates(self, l):
62
+ seen = set()
63
+ duplicates = set()
64
+ for item in l:
65
+ if item in seen:
66
+ duplicates.add(item)
67
+ else:
68
+ seen.add(item)
69
+ return duplicates
70
+
71
+ def __del__(self):
72
+ pass
73
+
74
+ def __dict__(self):
75
+ oPorts = {}
76
+ iPorts = {}
77
+ if self.__onode is not None:
78
+ cnt = 0
79
+ for op in self.__onode.OutputPorts:
80
+ oPorts[cnt] = op.PortName
81
+ cnt+=1
82
+ if self.__inode is not None:
83
+ cnt = 0
84
+ for ip in self.__inode.InputPorts:
85
+ iPorts[cnt] = ip.PortName
86
+ cnt+=1
87
+
88
+ return {
89
+ "name" : self.__class__.__name__,
90
+ "outputs" : oPorts,
91
+ "inputs" : iPorts
92
+ }
93
+
94
+ def __repr__(self):
95
+ return json.dumps(self.__dict__(), indent=4)
96
+
97
+ def __get_output_port_index_by_name(self, node, outputPortName):
98
+ port = None
99
+ for p in node.__onode.OutputPorts:
100
+ if p.PortName == outputPortName:
101
+ port = p
102
+ break
103
+ if port is None:
104
+ raise ValueError(f'Could not find input port named \'{outputPortName}\'.')
105
+
106
+ return node.__onode.OutputPorts.index(port)
107
+
108
+ def __get_input_port_index_by_name(self, node, inputPortName):
109
+ port = None
110
+ for p in node.__inode.InputPorts:
111
+ if p.PortName == inputPortName:
112
+ port = p
113
+ break
114
+
115
+ if port is None:
116
+ raise ValueError(f'Could not find output port named\'{inputPortName}\'.')
117
+
118
+ return node.__inode.InputPorts.index(port)
119
+
120
+ def __update(self, data):
121
+ if self.__inode is None:
122
+ raise TypeError('No input ports defined. Update is only available if input ports are defined.')
123
+
124
+ dataOut = self.update(data)
125
+
126
+ if self.__onode is not None:
127
+ self.write(dataOut)
128
+
129
+ def write(self, data):
130
+ if self.__onode is None:
131
+ raise TypeError('No output ports defined. Write is only available if output ports are defined.')
132
+
133
+ self.__onode.write_internal(data)
134
+
135
+ def connect_by_name(self, outputPortName, node, inputPortName):
136
+ if self.__onode is None:
137
+ raise ValueError('Connect is only available if outputs are defined.')
138
+ io = self.__get_output_port_index_by_name(self, outputPortName)
139
+ ii = self.__get_input_port_index_by_name(node, inputPortName)
140
+ self.connect_by_id(io, node, ii)
141
+
142
+ def disconnect_by_name(self, outputPortName, node, inputPortName):
143
+ if self.__onode is None:
144
+ raise ValueError('Connect is only available if outputs are defined.')
145
+ io = self.__get_output_port_index_by_name(self, outputPortName)
146
+ ii = self.__get_input_port_index_by_name(node, inputPortName)
147
+ self.disconnect_by_id(io, node, ii)
148
+
149
+ def connect_by_id(self, outputPortId, node, inputPortId):
150
+ if self.__onode is None:
151
+ raise ValueError('Connect is only available if outputs are defined.')
152
+ if len(self.__onode.OutputPorts) <= outputPortId:
153
+ raise ValueError('Port {outputPortId} is out of bounds')
154
+ self.__onode.connect(outputPortId, node.__inode.InputPorts[inputPortId])
155
+
156
+ def disconnect_by_id(self, outputPortId, node, inputPortId):
157
+ if self.__onode is None:
158
+ raise ValueError('Disconnect is only available if outputs are defined.')
159
+ if len(self.__onode.OutputPorts) <= outputPortId:
160
+ raise ValueError('Port {outputPortId} is out of bounds')
161
+ self.__onode.disconnect(outputPortId, node.__inode.InputPorts[inputPortId])
162
+
163
+ @abstractmethod
164
+ def update(self, data):
165
+ pass
@@ -0,0 +1,96 @@
1
+ from .node import Node
2
+ from .sources import *
3
+
4
+ class Pipeline:
5
+ def __init__(self):
6
+ self.__nodes = {}
7
+
8
+ def add_node(self, name: str, node: Node):
9
+ if name in self.__nodes.keys():
10
+ raise ValueError(f"A node with the name '{name}' already exists.")
11
+ self.__nodes[name] = node
12
+
13
+ def remove_node(self, name: str):
14
+ if name not in self.__nodes.keys():
15
+ raise KeyError(f"No node found with the name '{name}'.")
16
+ del self.__nodes[name]
17
+
18
+ def __get_ports(self, n1 : Node):
19
+ return [n1.__dict__()['inputs'], n1.__dict__()['outputs']]
20
+
21
+ def __n1_is_output(self, n1 : Node, port_name1 : str, n2 : Node, port_name2 : str ):
22
+ n1p = self.__get_ports(n1)
23
+ n2p = self.__get_ports(n2)
24
+ if len(n1p[1]) == 0 and len(n2p[1]) == 0 :
25
+ raise ValueError(f"No outputs found for {n1.__class__.__name__} or {n2.__class__.__name__}.")
26
+ if len(n1p[0]) == 0 and len(n2p[0])== 0 :
27
+ raise ValueError(f"No inputs found for {n1.__class__.__name__} or {n2.__class__.__name__}.")
28
+
29
+ n1_isOutput = False
30
+ if port_name1 in n1p[0].values():
31
+ n1_isOutput = False
32
+ if port_name1 in n1p[1].values():
33
+ n1_isOutput = True
34
+ if port_name1 not in n1p[0].values() and port_name1 not in n1p[1].values():
35
+ raise ValueError(f'Port {port_name1} not found in {n1.__class__.__name__}.')
36
+ if port_name2 not in n2p[0].values() and port_name2 not in n2p[1].values():
37
+ raise ValueError(f'Port {port_name2} not found in {n2.__class__.__name__}.')
38
+ return n1_isOutput
39
+
40
+ def __check_nodes(self, node_name1 : str, node_name2 : str):
41
+ n1 : Node = None
42
+ try:
43
+ n1 : Node = self.__nodes[node_name1]
44
+ except:
45
+ raise ValueError(f'Pipeline does not contain \'{node_name1}\'.')
46
+ try:
47
+ n2 : Node = self.__nodes[node_name2]
48
+ except:
49
+ raise ValueError(f'Pipeline does not contain \'{node_name2}\'.')
50
+ return [n1, n2]
51
+
52
+ def connect(self, node_name1 : str, port_name1 : str, node_name2 : str, port_name2 : str):
53
+ nodes = self.__check_nodes(node_name1, node_name2)
54
+ ports = [port_name1, port_name2]
55
+ if self.__n1_is_output(nodes[0], port_name1, nodes[1], port_name2):
56
+ nodes[0].connect_by_name(ports[0], nodes[1], ports[1])
57
+ else:
58
+ nodes[1].connect_by_name(ports[1], nodes[0], ports[0])
59
+
60
+ def disconnect(self, node_name1 : str, port_name1 : str, node_name2 : str, port_name2 : str):
61
+ nodes = self.__check_nodes(node_name1, node_name2)
62
+ ports = [port_name1, port_name2]
63
+ if self.__n1_is_output(nodes[0], port_name1, nodes[1], port_name2):
64
+ nodes[0].disconnect_by_name(ports[0], nodes[1], ports[1])
65
+ else:
66
+ nodes[1].disconnect_by_name(ports[1], nodes[0], ports[0])
67
+
68
+ def get_source_nodes(self):
69
+ sources : list[Node] = []
70
+ for node in self.__nodes.values():
71
+ np = self.__get_ports(node)
72
+ if len(np[0]) == 0 and len(np[1]) > 0:
73
+ sources.append(node)
74
+ if len(sources) is None:
75
+ raise ValueError(f'No source node was found.')
76
+ return sources
77
+
78
+ def start(self):
79
+ found = False
80
+ for node in self.__nodes.values():
81
+ if hasattr(node, 'start'):
82
+ found = True
83
+ node.start()
84
+
85
+ if found == False:
86
+ raise ValueError(f'Could not find a source providing a \'start\' method.')
87
+
88
+ def stop(self):
89
+ found = False
90
+ for node in self.__nodes.values():
91
+ if hasattr(node, 'stop'):
92
+ found = True
93
+ node.stop()
94
+
95
+ if found == False:
96
+ raise ValueError(f'Could not find a source providing a \'stop\' method.')
@@ -0,0 +1,64 @@
1
+ from .node import Node
2
+ from .frequency_generator import FrequencyGenerator
3
+ from abc import abstractmethod
4
+
5
+ class ManualSource(Node):
6
+ def __init__(self, outputPorts):
7
+ super().__init__(inputPorts=None, outputPorts=outputPorts)
8
+
9
+ def __del__(self):
10
+ super().__del__()
11
+
12
+ class FixedFrequencySource(Node, FrequencyGenerator):
13
+ def __init__(self, outputPorts, samplingRateHz):
14
+ super().__init__(inputPorts=None, outputPorts=outputPorts)
15
+ super(Node, self).__init__(samplingRateHz)
16
+
17
+ def __del__(self):
18
+ super().__del__()
19
+ super(Node, self).__del__()
20
+
21
+ def update(self):
22
+ self.write(self.generate_sample())
23
+
24
+ @abstractmethod
25
+ def generate_sample(self):
26
+ pass
27
+
28
+ class DeviceSource(Node):
29
+ def __init__(self, outputPorts, serial):
30
+ super().__init__(inputPorts=None, outputPorts=outputPorts)
31
+
32
+ def __del__(self):
33
+ super().__del__()
34
+
35
+ @staticmethod
36
+ @abstractmethod
37
+ def start_scanning():
38
+ pass
39
+
40
+ @staticmethod
41
+ @abstractmethod
42
+ def stop_scanning():
43
+ pass
44
+
45
+ @staticmethod
46
+ @abstractmethod
47
+ def get_available_devices():
48
+ pass
49
+
50
+ @abstractmethod
51
+ def open(self, serial):
52
+ pass
53
+
54
+ @abstractmethod
55
+ def close(self):
56
+ pass
57
+
58
+ @abstractmethod
59
+ def start(self):
60
+ pass
61
+
62
+ @abstractmethod
63
+ def stop(self):
64
+ pass
File without changes
@@ -0,0 +1,82 @@
1
+ from .i_port import IPort
2
+ import threading
3
+ from abc import ABC, abstractmethod
4
+ from enum import Enum
5
+ import time
6
+
7
+ class INode(ABC):
8
+ class UpdateMode(Enum):
9
+ Synchronized = 1 #update when all input ports delivered data
10
+ Asynchron = 2 #update when any input ports delivered data
11
+
12
+ def __init__(self, inputPort : list[str]):
13
+ self.InputPorts : list[IPort] = []
14
+ for p in inputPort:
15
+ port = IPort(p)
16
+ port.set_data_available_eventhandler(self.__on_data_available)
17
+ self.InputPorts.append(port)
18
+ self.NodeUpdateMode = self.UpdateMode.Synchronized
19
+
20
+ self.__event : threading.Event = threading.Event()
21
+ self.__updateThreadRunning = False
22
+ self.__updateThread = None
23
+ self.__updateCnt = 0
24
+ self.__totalTimeMs = 0
25
+ self.__updateTimeMs = 0
26
+ self.__start()
27
+
28
+ def __del__(self):
29
+ self.__stop()
30
+
31
+ def __start(self):
32
+ if not self.__updateThreadRunning:
33
+ self.__updateThreadRunning = True
34
+ self.__updateThread = threading.Thread(target=self.__updateThread_DoWork, daemon=True)
35
+ self.__updateThread.start()
36
+
37
+ def __stop(self):
38
+ if self.__updateThreadRunning:
39
+ self.__updateThreadRunning = False
40
+ self.__event.set()
41
+ self.__updateThread.join()
42
+ self.__updateThread = None
43
+
44
+ def __on_data_available(self):
45
+ if self.NodeUpdateMode is self.UpdateMode.Asynchron:
46
+ self.__event.set()
47
+ else:
48
+ allPortsAcquired = True
49
+ for port in self.InputPorts:
50
+ if port.DataCount <= 0:
51
+ allPortsAcquired = False
52
+ break
53
+ if allPortsAcquired:
54
+ self.__event.set()
55
+
56
+ def __updateThread_DoWork(self):
57
+ try:
58
+ self.__event.wait()
59
+ self.__event.clear()
60
+ while self.__updateThreadRunning:
61
+ if self.__updateThreadRunning:
62
+ updateCnt = -1
63
+ for port in self.InputPorts:
64
+ if updateCnt == -1 or port.DataCount <= updateCnt:
65
+ updateCnt = port.DataCount
66
+ for i in range(updateCnt):
67
+ start = time.time()
68
+ self.update()
69
+ end = time.time()
70
+ self.__updateCnt += 1
71
+ self.__totalTimeMs += (end - start)*1000
72
+ self.__updateTimeMs = self.__totalTimeMs / self.__updateCnt
73
+
74
+ self.__event.wait()
75
+ self.__event.clear()
76
+
77
+ except Exception as e:
78
+ self.__stop()
79
+
80
+ @abstractmethod
81
+ def update(self):
82
+ pass
@@ -0,0 +1,32 @@
1
+ import queue
2
+
3
+ class IPort:
4
+ def __init__(self, portName : str):
5
+ self.__queue : queue.Queue = queue.Queue()
6
+ self.DataCount : int = 0
7
+ self.PortName = portName
8
+ self.IsConnected = False
9
+ self.__eventHandler : function
10
+ self.__readCnt = 0
11
+
12
+ def write(self, data):
13
+ self.__queue.put(data, block=True, timeout=None)
14
+ self.DataCount = self.__queue.qsize()
15
+ if self.__eventHandler is not None:
16
+ self.__eventHandler()
17
+
18
+ def read(self):
19
+ if self.DataCount > 0:
20
+ try:
21
+ self.__readCnt += 1
22
+ return self.__queue.get(block=True, timeout=None)
23
+ except:
24
+ return None
25
+ else:
26
+ return None
27
+
28
+ def set_data_available_eventhandler(self, handler):
29
+ self.__eventHandler = handler
30
+
31
+ def remove_data_available_eventhandler(self, handler):
32
+ self.__eventHandler = None