cubesat-space-protocol-py 1.0.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.
- cubesat_space_protocol_py-1.0.0/.github/workflows/build.yml +20 -0
- cubesat_space_protocol_py-1.0.0/.github/workflows/docs.yml +36 -0
- cubesat_space_protocol_py-1.0.0/.github/workflows/publish.yml +24 -0
- cubesat_space_protocol_py-1.0.0/.gitignore +6 -0
- cubesat_space_protocol_py-1.0.0/LICENSE +21 -0
- cubesat_space_protocol_py-1.0.0/PKG-INFO +10 -0
- cubesat_space_protocol_py-1.0.0/docs/conf.py +25 -0
- cubesat_space_protocol_py-1.0.0/docs/explain/filters.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/explain/index.rst +11 -0
- cubesat_space_protocol_py-1.0.0/docs/explain/interfaces.rst +2 -0
- cubesat_space_protocol_py-1.0.0/docs/explain/libcsp_compare.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/explain/router_task.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/explain/why_python.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/how_to/index.rst +11 -0
- cubesat_space_protocol_py-1.0.0/docs/how_to/limit_packet_size.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/how_to/route_packets_indirectly.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/how_to/send_flags_node.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/how_to/use_can_interface.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/how_to/use_serializing_interface.rst +4 -0
- cubesat_space_protocol_py-1.0.0/docs/index.rst +9 -0
- cubesat_space_protocol_py-1.0.0/docs/tutorial/index.rst +7 -0
- cubesat_space_protocol_py-1.0.0/docs/tutorial/simple_server_client.rst +159 -0
- cubesat_space_protocol_py-1.0.0/examples/simple_client_server.py +37 -0
- cubesat_space_protocol_py-1.0.0/pyproject.toml +63 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/__init__.py +18 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/crc32.py +78 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/interface.py +19 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/interfaces/can/__init__.py +6 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/interfaces/can/can_interface.py +180 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/interfaces/interface_pair.py +39 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/interfaces/lo_interface.py +16 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/interfaces/serializing_interface.py +81 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/node.py +54 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/packet.py +85 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/packet_handler.py +24 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/router.py +149 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/rtable.py +49 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/services/ping.py +17 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/services/ping_client.py +16 -0
- cubesat_space_protocol_py-1.0.0/src/csp_py/socket.py +269 -0
- cubesat_space_protocol_py-1.0.0/src/tests/__init__.py +0 -0
- cubesat_space_protocol_py-1.0.0/src/tests/conftest.py +40 -0
- cubesat_space_protocol_py-1.0.0/src/tests/router/__init__.py +0 -0
- cubesat_space_protocol_py-1.0.0/src/tests/router/test_config.py +28 -0
- cubesat_space_protocol_py-1.0.0/src/tests/router/test_packet_to_localhost.py +88 -0
- cubesat_space_protocol_py-1.0.0/src/tests/router/test_route_packet.py +29 -0
- cubesat_space_protocol_py-1.0.0/src/tests/router/test_rtable.py +47 -0
- cubesat_space_protocol_py-1.0.0/src/tests/router/test_send_packet_to_interface.py +48 -0
- cubesat_space_protocol_py-1.0.0/src/tests/services/test_ping.py +69 -0
- cubesat_space_protocol_py-1.0.0/src/tests/socket/test_basic.py +185 -0
- cubesat_space_protocol_py-1.0.0/src/tests/socket/test_bound_socket.py +147 -0
- cubesat_space_protocol_py-1.0.0/src/tests/socket/test_connection.py +250 -0
- cubesat_space_protocol_py-1.0.0/src/tests/support.py +65 -0
- cubesat_space_protocol_py-1.0.0/src/tests/test_crc32_filters.py +141 -0
- cubesat_space_protocol_py-1.0.0/src/tests/test_packet.py +50 -0
- cubesat_space_protocol_py-1.0.0/src/tests/test_serializing_interface.py +50 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
- push
|
|
5
|
+
- pull_request
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
if: (github.event_name == 'push' || github.event_name == 'pull_request') && !startsWith(github.ref, 'refs/tags')
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Install pipx
|
|
13
|
+
run: sudo apt-get install -y pipx
|
|
14
|
+
- name: Install hatch
|
|
15
|
+
run: pipx install hatch==1.14.1
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- name: Run tests
|
|
18
|
+
run: hatch run dev:tests
|
|
19
|
+
- name: Static analysis
|
|
20
|
+
run: hatch run dev:static_analysis
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
- push
|
|
5
|
+
- pull_request
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
if: (github.event_name == 'push' || github.event_name == 'pull_request') && !startsWith(github.ref, 'refs/tags')
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- name: Install pipx
|
|
13
|
+
run: sudo apt-get install -y pipx
|
|
14
|
+
- name: Install hatch
|
|
15
|
+
run: pipx install hatch==1.14.0
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- name: Build documentation
|
|
18
|
+
run: hatch run docs:build
|
|
19
|
+
- uses: actions/upload-pages-artifact@v3
|
|
20
|
+
with:
|
|
21
|
+
path: build/docs/html
|
|
22
|
+
deploy: # https://github.com/actions/deploy-pages
|
|
23
|
+
needs: build
|
|
24
|
+
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
25
|
+
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
|
26
|
+
permissions:
|
|
27
|
+
pages: write # to deploy to Pages
|
|
28
|
+
id-token: write # to verify the deployment originates from an appropriate source
|
|
29
|
+
environment:
|
|
30
|
+
name: github-pages
|
|
31
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- name: Deploy to GitHub Pages
|
|
35
|
+
id: deployment
|
|
36
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Publish package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
- push
|
|
5
|
+
|
|
6
|
+
jobs:
|
|
7
|
+
publish:
|
|
8
|
+
runs-on: ubuntu-latest
|
|
9
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/release-')
|
|
10
|
+
environment:
|
|
11
|
+
name: pypi
|
|
12
|
+
url: https://pypi.org/p/cubesat-space-protocol-py
|
|
13
|
+
permissions:
|
|
14
|
+
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
|
15
|
+
steps:
|
|
16
|
+
- name: Install pipx
|
|
17
|
+
run: sudo apt-get install -y pipx
|
|
18
|
+
- name: Install hatch
|
|
19
|
+
run: pipx install hatch==1.14.1
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Run tests
|
|
22
|
+
run: hatch build --clean
|
|
23
|
+
- name: Publish package
|
|
24
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KP Labs
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cubesat-space-protocol-py
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Cubsat Space Protocol (CSP) native Python implementation
|
|
5
|
+
Author: KP Labs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.12
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
project = 'CSP.py'
|
|
4
|
+
author = 'KP Labs'
|
|
5
|
+
copyright = f'{datetime.datetime.now().year}, {author}'
|
|
6
|
+
|
|
7
|
+
primary_domain = None
|
|
8
|
+
numfig = True
|
|
9
|
+
language = 'en'
|
|
10
|
+
|
|
11
|
+
extensions = [
|
|
12
|
+
'sphinx_immaterial'
|
|
13
|
+
]
|
|
14
|
+
html_theme = 'sphinx_immaterial'
|
|
15
|
+
root_doc = 'index'
|
|
16
|
+
|
|
17
|
+
html_theme_options = {
|
|
18
|
+
'features': [
|
|
19
|
+
'toc.follow'
|
|
20
|
+
],
|
|
21
|
+
'font': {
|
|
22
|
+
'text': 'Fira Sans',
|
|
23
|
+
'code': 'Fira Code',
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Simple server-client application
|
|
2
|
+
================================
|
|
3
|
+
|
|
4
|
+
Goal
|
|
5
|
+
----
|
|
6
|
+
This tutorial walks you through the creation of simple server-client application using Cubesat Space Protocol. We will use two ``asyncio`` tasks - one acting as client and second acting as server. For similarity with TCP sockets, connection-based model will be used.
|
|
7
|
+
|
|
8
|
+
Steps
|
|
9
|
+
-----
|
|
10
|
+
|
|
11
|
+
Application skeleton
|
|
12
|
+
++++++++++++++++++++
|
|
13
|
+
#. Start by creating empty skeleton for CSP application:
|
|
14
|
+
|
|
15
|
+
.. code-block:: python
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import csp_py
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def main():
|
|
22
|
+
node = csp_py.CspNode()
|
|
23
|
+
|
|
24
|
+
router = asyncio.create_task(node.router.arun())
|
|
25
|
+
|
|
26
|
+
# Rest of application goes here
|
|
27
|
+
|
|
28
|
+
router.cancel()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
asyncio.run(main())
|
|
32
|
+
|
|
33
|
+
#. Add two empty (for now) tasks - one for server and one for client:
|
|
34
|
+
|
|
35
|
+
.. code-block:: python
|
|
36
|
+
|
|
37
|
+
async def client_task(node: csp_py.CspNode):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def server_task(node: csp_py.CspNode):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def main():
|
|
46
|
+
node = csp_py.CspNode()
|
|
47
|
+
|
|
48
|
+
router = asyncio.create_task(node.router.arun())
|
|
49
|
+
server = asyncio.create_task(server_task(node))
|
|
50
|
+
client = asyncio.create_task(client_task(node))
|
|
51
|
+
|
|
52
|
+
await client
|
|
53
|
+
|
|
54
|
+
server.cancel()
|
|
55
|
+
router.cancel()
|
|
56
|
+
|
|
57
|
+
Client function
|
|
58
|
+
+++++++++++++++
|
|
59
|
+
#. In client task function, open connection to server on port 20
|
|
60
|
+
|
|
61
|
+
.. code-block:: python
|
|
62
|
+
|
|
63
|
+
async def client_task(node: csp_py.CspNode):
|
|
64
|
+
connection = await node.connect(dst=0, port=20)
|
|
65
|
+
|
|
66
|
+
#. In loop, send packet to server
|
|
67
|
+
|
|
68
|
+
.. code-block:: python
|
|
69
|
+
|
|
70
|
+
async def client_task(node: csp_py.CspNode):
|
|
71
|
+
connection = await node.connect(dst=0, port=20)
|
|
72
|
+
|
|
73
|
+
for i in range(0, 10):
|
|
74
|
+
await connection.send(f'Hello, world! {i}'.encode('utf-8'))
|
|
75
|
+
|
|
76
|
+
#. After sending packet, wait for response and print it
|
|
77
|
+
|
|
78
|
+
.. code-block:: python
|
|
79
|
+
|
|
80
|
+
async def client_task(node: csp_py.CspNode):
|
|
81
|
+
connection = await node.connect(dst=0, port=20)
|
|
82
|
+
|
|
83
|
+
for i in range(0, 10):
|
|
84
|
+
await connection.send(f'Hello, world! {i}'.encode('utf-8'))
|
|
85
|
+
response = await connection.recv()
|
|
86
|
+
print(f'Got response: {response.data.decode('utf-8')}')
|
|
87
|
+
|
|
88
|
+
Server function
|
|
89
|
+
+++++++++++++++
|
|
90
|
+
#. Server task function starts by opening listening socket on port 20
|
|
91
|
+
|
|
92
|
+
.. code-block:: python
|
|
93
|
+
|
|
94
|
+
async def server_task(node: csp_py.CspNode):
|
|
95
|
+
socket = node.listen(20)
|
|
96
|
+
|
|
97
|
+
#. Listening socket can be used to accept connections
|
|
98
|
+
|
|
99
|
+
.. code-block:: python
|
|
100
|
+
|
|
101
|
+
async def server_task(node: csp_py.CspNode):
|
|
102
|
+
socket = node.listen(20)
|
|
103
|
+
|
|
104
|
+
while True:
|
|
105
|
+
connection = await socket.accept()
|
|
106
|
+
|
|
107
|
+
#. Using accepted connection, receive incoming packet
|
|
108
|
+
|
|
109
|
+
.. code-block:: python
|
|
110
|
+
|
|
111
|
+
async def server_task(node: csp_py.CspNode):
|
|
112
|
+
socket = node.listen(20)
|
|
113
|
+
|
|
114
|
+
while True:
|
|
115
|
+
connection = await socket.accept()
|
|
116
|
+
|
|
117
|
+
while True:
|
|
118
|
+
packet = await connection.recv()
|
|
119
|
+
|
|
120
|
+
#. Once packet is received, send response
|
|
121
|
+
|
|
122
|
+
.. code-block:: python
|
|
123
|
+
|
|
124
|
+
async def server_task(node: csp_py.CspNode):
|
|
125
|
+
socket = node.listen(20)
|
|
126
|
+
|
|
127
|
+
while True:
|
|
128
|
+
connection = await socket.accept()
|
|
129
|
+
|
|
130
|
+
while True:
|
|
131
|
+
packet = await connection.recv()
|
|
132
|
+
await connection.send(b'Response to ' + packet.data)
|
|
133
|
+
|
|
134
|
+
.. note:: Full source code for this tutorial can be found in ``examples/simple_server_client.py``.
|
|
135
|
+
|
|
136
|
+
Running
|
|
137
|
+
+++++++
|
|
138
|
+
|
|
139
|
+
#. Execute the application:
|
|
140
|
+
|
|
141
|
+
.. code-block:: shell-session
|
|
142
|
+
|
|
143
|
+
shell$ python examples/simple_server_client.py
|
|
144
|
+
Got response: Response to Hello, world! 0
|
|
145
|
+
Got response: Response to Hello, world! 1
|
|
146
|
+
Got response: Response to Hello, world! 2
|
|
147
|
+
Got response: Response to Hello, world! 3
|
|
148
|
+
Got response: Response to Hello, world! 4
|
|
149
|
+
Got response: Response to Hello, world! 5
|
|
150
|
+
Got response: Response to Hello, world! 6
|
|
151
|
+
Got response: Response to Hello, world! 7
|
|
152
|
+
Got response: Response to Hello, world! 8
|
|
153
|
+
Got response: Response to Hello, world! 9
|
|
154
|
+
|
|
155
|
+
Client got response from server for each packet sent.
|
|
156
|
+
|
|
157
|
+
Summary
|
|
158
|
+
-------
|
|
159
|
+
In this example we've create very simple client-server application using Cubesat Space Protocol. Client task used ``CspNode.connect`` function to establish connection while server followed TCP-like model with ``CspNode.listen`` and ``CspSocket.accept`` functions. Both client and server used ``send`` and ``recv`` functions on their respective connections to send and receive packets.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import csp_py
|
|
3
|
+
|
|
4
|
+
async def client_task(node: csp_py.CspNode):
|
|
5
|
+
connection = await node.connect(dst=0, port=20)
|
|
6
|
+
|
|
7
|
+
for i in range(0, 10):
|
|
8
|
+
await connection.send(f'Hello, world! {i}'.encode('utf-8'))
|
|
9
|
+
response = await connection.recv()
|
|
10
|
+
print(f'Got response: {response.data.decode('utf-8')}')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def server_task(node: csp_py.CspNode):
|
|
14
|
+
socket = node.listen(20)
|
|
15
|
+
|
|
16
|
+
while True:
|
|
17
|
+
connection = await socket.accept()
|
|
18
|
+
|
|
19
|
+
while True:
|
|
20
|
+
packet = await connection.recv()
|
|
21
|
+
await connection.send(b'Response to ' + packet.data)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def main():
|
|
25
|
+
node = csp_py.CspNode()
|
|
26
|
+
|
|
27
|
+
router = asyncio.create_task(node.router.arun())
|
|
28
|
+
server = asyncio.create_task(server_task(node))
|
|
29
|
+
client = asyncio.create_task(client_task(node))
|
|
30
|
+
|
|
31
|
+
await client
|
|
32
|
+
|
|
33
|
+
server.cancel()
|
|
34
|
+
router.cancel()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
asyncio.run(main())
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "hatchling.build"
|
|
3
|
+
requires = [
|
|
4
|
+
"hatchling==1.27.0",
|
|
5
|
+
]
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "cubesat-space-protocol-py"
|
|
9
|
+
license = "MIT"
|
|
10
|
+
version = "1.0.0"
|
|
11
|
+
authors = [ { name = "KP Labs" } ]
|
|
12
|
+
description = "Cubsat Space Protocol (CSP) native Python implementation"
|
|
13
|
+
classifiers = [
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
requires-python = ">=3.12"
|
|
18
|
+
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = [ "src/csp_py" ]
|
|
21
|
+
|
|
22
|
+
[tool.hatch.envs.dev]
|
|
23
|
+
python = '3.12'
|
|
24
|
+
dependencies = [
|
|
25
|
+
'pytest==8.3.4',
|
|
26
|
+
'pytest-asyncio==0.25.2',
|
|
27
|
+
'mypy==1.14.1',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[tool.hatch.envs.dev.scripts]
|
|
31
|
+
static_analysis = [
|
|
32
|
+
'mypy src {args:}',
|
|
33
|
+
]
|
|
34
|
+
tests = 'pytest src/tests {args:}'
|
|
35
|
+
|
|
36
|
+
[tool.hatch.envs.docs]
|
|
37
|
+
python = '3.12'
|
|
38
|
+
dependencies = [
|
|
39
|
+
'Sphinx==8.1.3',
|
|
40
|
+
'sphinx-autobuild==2024.10.3',
|
|
41
|
+
'sphinx-immaterial==0.12.4',
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.hatch.envs.docs.scripts]
|
|
45
|
+
build = 'sphinx-build --builder html --jobs auto --nitpicky --write-all --doctree-dir build/docs/.doctrees {args:} {root}/docs build/docs/html'
|
|
46
|
+
watch = 'sphinx-autobuild --builder html --jobs auto --nitpicky --write-all --doctree-dir build/docs/.doctrees {args:} {root}/docs build/docs/html'
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
asyncio_mode = "auto"
|
|
50
|
+
|
|
51
|
+
[tool.mypy]
|
|
52
|
+
warn_unused_configs = true
|
|
53
|
+
strict = true
|
|
54
|
+
explicit_package_bases = true
|
|
55
|
+
mypy_path = "src"
|
|
56
|
+
|
|
57
|
+
[tool.commitizen]
|
|
58
|
+
name = "cz_conventional_commits"
|
|
59
|
+
tag_format = "release-$major.$minor.$patch$prerelease"
|
|
60
|
+
version = "1.0.0"
|
|
61
|
+
version_files = [
|
|
62
|
+
"pyproject.toml:version"
|
|
63
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .packet import CspId, CspPacket, CspPacketPriority, CspPacketFlags
|
|
2
|
+
from .router import CspRouter
|
|
3
|
+
from .node import CspNode
|
|
4
|
+
from .packet_handler import IPacketHandler
|
|
5
|
+
|
|
6
|
+
from .services.ping_client import ping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
'CspId',
|
|
11
|
+
'CspPacket',
|
|
12
|
+
'CspPacketPriority',
|
|
13
|
+
'CspPacketFlags',
|
|
14
|
+
'CspRouter',
|
|
15
|
+
'CspNode',
|
|
16
|
+
'IPacketHandler',
|
|
17
|
+
'ping',
|
|
18
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import struct
|
|
2
|
+
from csp_py.packet import CspPacket
|
|
3
|
+
from .router import CspRouter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
CRC_TABLE = [
|
|
7
|
+
0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
|
|
8
|
+
0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
|
|
9
|
+
0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
|
|
10
|
+
0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
|
|
11
|
+
0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
|
|
12
|
+
0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
|
|
13
|
+
0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
|
|
14
|
+
0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
|
|
15
|
+
0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
|
|
16
|
+
0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
|
|
17
|
+
0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
|
|
18
|
+
0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
|
|
19
|
+
0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
|
|
20
|
+
0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
|
|
21
|
+
0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
|
|
22
|
+
0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
|
|
23
|
+
0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
|
|
24
|
+
0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
|
|
25
|
+
0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
|
|
26
|
+
0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
|
|
27
|
+
0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
|
|
28
|
+
0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
|
|
29
|
+
0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
|
|
30
|
+
0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
|
|
31
|
+
0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
|
|
32
|
+
0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
|
|
33
|
+
0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
|
|
34
|
+
0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
|
|
35
|
+
0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
|
|
36
|
+
0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
|
|
37
|
+
0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
|
|
38
|
+
0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351,
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def calculate_crc32(data: bytes) -> int:
|
|
43
|
+
crc = 0xFFFF_FFFF
|
|
44
|
+
for b in data:
|
|
45
|
+
crc = CRC_TABLE[(crc ^ b) & 0xFF] ^ (crc >> 8)
|
|
46
|
+
|
|
47
|
+
return crc ^ 0xFFFF_FFFF
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def register_crc32_filters(router: CspRouter) -> None:
|
|
51
|
+
async def incoming_filter(packet: CspPacket) -> CspPacket | None:
|
|
52
|
+
if (packet.packet_id.flags & 1) == 0:
|
|
53
|
+
return packet
|
|
54
|
+
|
|
55
|
+
payload = packet.data[:-4]
|
|
56
|
+
|
|
57
|
+
if len(payload) < 4:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
received_checksum = struct.unpack('!I', packet.data[-4:])[0]
|
|
61
|
+
calculated_checksum = calculate_crc32(payload)
|
|
62
|
+
|
|
63
|
+
if received_checksum != calculated_checksum:
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
return packet.with_data(payload)
|
|
67
|
+
|
|
68
|
+
async def outgoing_filter(packet: CspPacket) -> CspPacket:
|
|
69
|
+
if (packet.packet_id.flags & 1) == 0:
|
|
70
|
+
return packet
|
|
71
|
+
|
|
72
|
+
payload = packet.data
|
|
73
|
+
checksum = calculate_crc32(payload)
|
|
74
|
+
|
|
75
|
+
return packet.with_data(payload + struct.pack('!I', checksum))
|
|
76
|
+
|
|
77
|
+
router.incoming_packet_filters.append(incoming_filter)
|
|
78
|
+
router.outgoing_packet_filters.append(outgoing_filter)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Protocol
|
|
3
|
+
|
|
4
|
+
from .packet import CspPacket
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CspPacketSink(Protocol):
|
|
8
|
+
def __call__(self, packet: CspPacket) -> None:
|
|
9
|
+
raise NotImplementedError()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ICspInterface(ABC):
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def set_packet_sink(self, sink: CspPacketSink) -> None:
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
async def send(self, packet: CspPacket) -> None:
|
|
19
|
+
raise NotImplementedError()
|