pyneosol 0.4.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 (35) hide show
  1. pyneosol-0.4.1/.github/ISSUE_TEMPLATE/bug_report.yml +51 -0
  2. pyneosol-0.4.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  3. pyneosol-0.4.1/.github/ISSUE_TEMPLATE/feature_request.yml +28 -0
  4. pyneosol-0.4.1/.github/dependabot.yml +12 -0
  5. pyneosol-0.4.1/.github/workflows/ci.yml +44 -0
  6. pyneosol-0.4.1/.github/workflows/release.yml +141 -0
  7. pyneosol-0.4.1/.gitignore +218 -0
  8. pyneosol-0.4.1/.release-please-manifest.json +3 -0
  9. pyneosol-0.4.1/CHANGELOG.md +68 -0
  10. pyneosol-0.4.1/CLAUDE.md +133 -0
  11. pyneosol-0.4.1/CONTRIBUTING.md +44 -0
  12. pyneosol-0.4.1/LICENSE +21 -0
  13. pyneosol-0.4.1/PKG-INFO +383 -0
  14. pyneosol-0.4.1/README.md +360 -0
  15. pyneosol-0.4.1/SECURITY.md +23 -0
  16. pyneosol-0.4.1/demo.py +128 -0
  17. pyneosol-0.4.1/docs/SPEC-PROTOCOLE-AT.md +672 -0
  18. pyneosol-0.4.1/docs/images/dongle-mai-dongle868-1a-etiquette.jpg +0 -0
  19. pyneosol-0.4.1/docs/images/dongle-mai-dongle868-1a-face.jpg +0 -0
  20. pyneosol-0.4.1/pyproject.toml +79 -0
  21. pyneosol-0.4.1/release-please-config.json +12 -0
  22. pyneosol-0.4.1/src/pyneosol/__init__.py +61 -0
  23. pyneosol-0.4.1/src/pyneosol/discovery.py +51 -0
  24. pyneosol-0.4.1/src/pyneosol/dongle.py +267 -0
  25. pyneosol-0.4.1/src/pyneosol/exceptions.py +66 -0
  26. pyneosol-0.4.1/src/pyneosol/models.py +74 -0
  27. pyneosol-0.4.1/src/pyneosol/protocol.py +168 -0
  28. pyneosol-0.4.1/src/pyneosol/py.typed +0 -0
  29. pyneosol-0.4.1/src/pyneosol/transport.py +75 -0
  30. pyneosol-0.4.1/tests/__init__.py +0 -0
  31. pyneosol-0.4.1/tests/fake.py +165 -0
  32. pyneosol-0.4.1/tests/test_discovery.py +48 -0
  33. pyneosol-0.4.1/tests/test_dongle.py +159 -0
  34. pyneosol-0.4.1/tests/test_protocol.py +116 -0
  35. pyneosol-0.4.1/uv.lock +121 -0
@@ -0,0 +1,51 @@
1
+ name: Rapport de bug
2
+ description: Un comportement inattendu du pilote ou du dongle
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ ⚠️ **Masquez vos clés KeeLoq et numéros de série réels** dans tout log ou sortie
9
+ collé ici. Quiconque les possède peut commander vos volets.
10
+ - type: textarea
11
+ id: description
12
+ attributes:
13
+ label: Description
14
+ description: Ce qui se passe, et ce que vous attendiez.
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: reproduction
19
+ attributes:
20
+ label: Reproduction
21
+ description: Les étapes, et le code minimal qui déclenche le problème.
22
+ validations:
23
+ required: true
24
+ - type: input
25
+ id: dongle
26
+ attributes:
27
+ label: Dongle
28
+ description: Référence, version matérielle et logicielle (sortie de `AT?`).
29
+ placeholder: MAI-DONGLE868-1A, HW 0, SW Rev10
30
+ validations:
31
+ required: true
32
+ - type: input
33
+ id: environment
34
+ attributes:
35
+ label: Environnement
36
+ placeholder: macOS 15.4, Python 3.13, pyneosol 0.2.0
37
+ validations:
38
+ required: true
39
+ - type: textarea
40
+ id: logs
41
+ attributes:
42
+ label: Logs
43
+ description: Le dialogue AT si vous l'avez. Rendu en bloc de code, pas de formatage requis.
44
+ render: text
45
+ - type: checkboxes
46
+ id: hygiene
47
+ attributes:
48
+ label: Vérification
49
+ options:
50
+ - label: J'ai masqué toutes les clés et tous les numéros de série réels.
51
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Faille de sécurité
4
+ url: https://github.com/bbayszczak/pyneosol/security/advisories/new
5
+ about: Signalement privé — n'ouvrez jamais d'issue publique pour une vulnérabilité.
@@ -0,0 +1,28 @@
1
+ name: Proposition
2
+ description: Une évolution du pilote
3
+ labels: [enhancement]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ `pyneosol` est un pilote bas niveau **sans état** : position estimée, calibration et
9
+ persistance appartiennent à la couche appelante. Voir [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md).
10
+ - type: textarea
11
+ id: besoin
12
+ attributes:
13
+ label: Besoin
14
+ description: Le problème concret que cela résout, pas la solution envisagée.
15
+ validations:
16
+ required: true
17
+ - type: textarea
18
+ id: proposition
19
+ attributes:
20
+ label: Proposition
21
+ description: Ce que vous imaginez, et l'état de validation du protocole s'il est concerné.
22
+ - type: checkboxes
23
+ id: perimetre
24
+ attributes:
25
+ label: Périmètre
26
+ options:
27
+ - label: Cette proposition ne fait pas remonter d'état (position, calibration) dans le pilote.
28
+ required: true
@@ -0,0 +1,12 @@
1
+ # Les actions sont épinglées sur des SHA complets dans .github/workflows (protection contre
2
+ # le redéplacement d'un tag). Dependabot est donc indispensable : sans lui, ces SHA figés ne
3
+ # recevraient jamais les correctifs de sécurité amont.
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: github-actions
7
+ directory: /
8
+ schedule:
9
+ interval: weekly
10
+ commit-message:
11
+ # Conventional Commits : `ci` n'entraîne pas de release (cf. release-please).
12
+ prefix: ci
@@ -0,0 +1,44 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ lint:
17
+ name: Lint
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
21
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
22
+ with:
23
+ enable-cache: true
24
+ - name: ruff check
25
+ run: uv run ruff check --output-format=github .
26
+ - name: ruff format
27
+ run: uv run ruff format --check --diff .
28
+
29
+ test:
30
+ name: Test (Python ${{ matrix.python-version }})
31
+ runs-on: ubuntu-latest
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ python-version: ["3.13", "3.14"]
36
+ steps:
37
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
38
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
39
+ with:
40
+ enable-cache: true
41
+ python-version: ${{ matrix.python-version }}
42
+ # The test suite runs against a fake dongle, so no hardware is required.
43
+ - name: pytest
44
+ run: uv run --python ${{ matrix.python-version }} pytest
@@ -0,0 +1,141 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ concurrency:
11
+ group: release-${{ github.ref }}
12
+ cancel-in-progress: false
13
+
14
+ jobs:
15
+ # Maintient une PR de release à partir des Conventional Commits : elle accumule le
16
+ # CHANGELOG et le bump de version, et déclenche tag + GitHub Release une fois fusionnée.
17
+ release-please:
18
+ name: Release PR
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ # Le GITHUB_TOKEN ne porte plus aucune écriture : le token de l'App s'en charge.
22
+ contents: read
23
+ outputs:
24
+ release_created: ${{ steps.release.outputs.release_created }}
25
+ tag_name: ${{ steps.release.outputs.tag_name }}
26
+ steps:
27
+ # release-please s'authentifie en GitHub App plutôt qu'avec le GITHUB_TOKEN, pour deux
28
+ # raisons. Ce dernier se voit refuser la création de PR tant que le réglage « Allow
29
+ # GitHub Actions to create and approve pull requests » du dépôt est décoché ; et surtout
30
+ # ses écritures ne déclenchent aucun workflow, si bien que la PR de release n'obtiendrait
31
+ # jamais les checks que le ruleset de main exige et resterait infusionnable. Une App est
32
+ # une identité distincte : ses événements déclenchent la CI, et le token d'installation
33
+ # qu'on en dérive expire au bout d'une heure.
34
+ - name: Mint a GitHub App token
35
+ id: app-token
36
+ uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
37
+ with:
38
+ client-id: ${{ secrets.RELEASE_PLEASE_CLIENT_ID }}
39
+ private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
40
+
41
+ - uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
42
+ id: release
43
+ with:
44
+ token: ${{ steps.app-token.outputs.token }}
45
+ config-file: release-please-config.json
46
+ manifest-file: .release-please-manifest.json
47
+
48
+ # release-please bump la version dans pyproject.toml mais ignore uv.lock, qui porte lui
49
+ # aussi la version du paquet. Sans resynchronisation, le lockfile est périmé dès la
50
+ # release et le premier `uv run` venu le réécrit. On corrige sur la branche de la PR de
51
+ # release, avant sa fusion, pour que main ne reçoive jamais de lockfile désaligné.
52
+ # `uv lock` relit pyproject.toml : la règle survit à tout changement de schéma de version.
53
+ - name: Check out the release pull request branch
54
+ if: steps.release.outputs.pr
55
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
56
+ with:
57
+ ref: ${{ fromJson(steps.release.outputs.pr).headBranchName }}
58
+
59
+ - name: Set up uv
60
+ if: steps.release.outputs.pr
61
+ uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
62
+
63
+ - name: Sync uv.lock with the bumped version
64
+ if: steps.release.outputs.pr
65
+ env:
66
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
67
+ # Le court-circuit n'est pas décoratif : les expressions d'un bloc `env:` de step
68
+ # sont évaluées avant son `if:`, à la différence de celles d'un `with:`. Sur les runs
69
+ # qui publient une release au lieu d'ouvrir une PR, `steps.release.outputs.pr` est
70
+ # vide ; sans garde, `fromJson` recevrait la chaîne vide et ferait échouer le step
71
+ # alors même qu'il doit être sauté.
72
+ BRANCH: ${{ steps.release.outputs.pr && fromJson(steps.release.outputs.pr).headBranchName || '' }}
73
+ run: |
74
+ uv lock
75
+ if git diff --quiet uv.lock; then
76
+ echo "uv.lock already in sync"
77
+ exit 0
78
+ fi
79
+ # Le commit passe par l'API et non par `git commit` : le ruleset de main exige des
80
+ # signatures vérifiées, or un commit fabriqué dans le runner n'est pas signé et
81
+ # bloquerait la fusion de la PR de release (« Commits must have verified
82
+ # signatures »). GitHub signe de lui-même les commits créés via son API, comme il
83
+ # le fait déjà pour ceux de release-please. `sha` est le blob du fichier tel qu'il
84
+ # est sur la branche : `uv lock` a modifié la copie de travail, pas HEAD.
85
+ gh api -X PUT "repos/${{ github.repository }}/contents/uv.lock" \
86
+ -f message="chore: sync uv.lock with the release version" \
87
+ -f branch="$BRANCH" \
88
+ -f sha="$(git rev-parse HEAD:uv.lock)" \
89
+ -f content="$(base64 -w0 uv.lock)"
90
+
91
+ # `uv build` exécute le backend hatchling et ses dépendances, soit du code tiers. Ce job
92
+ # est donc volontairement dépourvu de `id-token: write` : une dépendance de build
93
+ # compromise n'y trouve aucun jeton OIDC dont elle pourrait se servir pour publier.
94
+ # Les distributions sont aussi attachées à la GitHub Release, d'où elles restent
95
+ # installables hors index (`pip install <url du .whl>`).
96
+ build:
97
+ name: Build distributions
98
+ needs: release-please
99
+ if: needs.release-please.outputs.release_created == 'true'
100
+ runs-on: ubuntu-latest
101
+ permissions:
102
+ contents: write
103
+ steps:
104
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
105
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
106
+ with:
107
+ enable-cache: true
108
+ - name: Build sdist and wheel
109
+ run: uv build
110
+ - name: Attach them to the release
111
+ env:
112
+ GH_TOKEN: ${{ github.token }}
113
+ TAG_NAME: ${{ needs.release-please.outputs.tag_name }}
114
+ run: gh release upload "$TAG_NAME" dist/*
115
+ - name: Hand the distributions over to the publishing job
116
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
117
+ with:
118
+ name: dist
119
+ path: dist/
120
+ if-no-files-found: error
121
+
122
+ # Trusted Publishing : PyPI authentifie le workflow sur le jeton OIDC que GitHub lui
123
+ # délivre, il n'y a donc aucun token d'API à stocker en secret.
124
+ # Ce job détient ce jeton, il est donc réduit au strict minimum : pas de checkout, pas de
125
+ # dépendance, aucun code du dépôt — seulement les distributions déjà construites et
126
+ # l'action de publication. Rien de ce qui s'y exécute ne provient d'une PR.
127
+ publish:
128
+ name: Publish to PyPI
129
+ needs: build
130
+ runs-on: ubuntu-latest
131
+ permissions:
132
+ id-token: write
133
+ steps:
134
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
135
+ with:
136
+ name: dist
137
+ path: dist/
138
+ # L'action refuse de réécrire une version déjà présente sur PyPI : un rejeu du
139
+ # workflow échoue au lieu de corrompre.
140
+ - name: Publish to PyPI
141
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,218 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ # Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ # uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ # poetry.lock
109
+ # poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ # pdm.lock
116
+ # pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ # pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # Redis
135
+ *.rdb
136
+ *.aof
137
+ *.pid
138
+
139
+ # RabbitMQ
140
+ mnesia/
141
+ rabbitmq/
142
+ rabbitmq-data/
143
+
144
+ # ActiveMQ
145
+ activemq-data/
146
+
147
+ # SageMath parsed files
148
+ *.sage.py
149
+
150
+ # Environments
151
+ .env
152
+ .envrc
153
+ .venv
154
+ env/
155
+ venv/
156
+ ENV/
157
+ env.bak/
158
+ venv.bak/
159
+
160
+ # Spyder project settings
161
+ .spyderproject
162
+ .spyproject
163
+
164
+ # Rope project settings
165
+ .ropeproject
166
+
167
+ # mkdocs documentation
168
+ /site
169
+
170
+ # mypy
171
+ .mypy_cache/
172
+ .dmypy.json
173
+ dmypy.json
174
+
175
+ # Pyre type checker
176
+ .pyre/
177
+
178
+ # pytype static type analyzer
179
+ .pytype/
180
+
181
+ # Cython debug symbols
182
+ cython_debug/
183
+
184
+ # PyCharm
185
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
186
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
187
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
188
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
189
+ # .idea/
190
+
191
+ # Abstra
192
+ # Abstra is an AI-powered process automation framework.
193
+ # Ignore directories containing user credentials, local state, and settings.
194
+ # Learn more at https://abstra.io/docs
195
+ .abstra/
196
+
197
+ # Visual Studio Code
198
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
199
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
200
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
201
+ # you could uncomment the following to ignore the entire vscode folder
202
+ # .vscode/
203
+ # Temporary file for partial code execution
204
+ tempCodeRunnerFile.py
205
+
206
+ # Ruff stuff:
207
+ .ruff_cache/
208
+
209
+ # PyPI configuration file
210
+ .pypirc
211
+
212
+ # Marimo
213
+ marimo/_static/
214
+ marimo/_lsp/
215
+ __marimo__/
216
+
217
+ # Streamlit
218
+ .streamlit/secrets.toml
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.1"
3
+ }
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ ## [0.4.1](https://github.com/bbayszczak/pyneosol/compare/v0.4.0...v0.4.1) (2026-09-20)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **ci:** guard the branch name against an absent release pull request ([#20](https://github.com/bbayszczak/pyneosol/issues/20)) ([42f7195](https://github.com/bbayszczak/pyneosol/commit/42f7195db34eb8c7d9e3a0b9b4f1bf533e026598))
9
+
10
+ ## [0.4.0](https://github.com/bbayszczak/pyneosol/compare/v0.3.0...v0.4.0) (2026-09-20)
11
+
12
+
13
+ ### Features
14
+
15
+ * log the AT dialogue without leaking secrets ([#11](https://github.com/bbayszczak/pyneosol/issues/11)) ([d3ad93a](https://github.com/bbayszczak/pyneosol/commit/d3ad93a45dab04358907548ee191f0a09bab7246))
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **ci:** create the uv.lock sync commit through the GitHub API ([#19](https://github.com/bbayszczak/pyneosol/issues/19)) ([b91eee0](https://github.com/bbayszczak/pyneosol/commit/b91eee0e8dc7b798fa2ab0bc7c0cc6e571439a07))
21
+ * **dongle:** wrap a non-numeric transmit power in ProtocolError ([#12](https://github.com/bbayszczak/pyneosol/issues/12)) ([4e560cd](https://github.com/bbayszczak/pyneosol/commit/4e560cd32a2a0d54f7e1bb882913d516121d3871))
22
+
23
+
24
+ ### Documentation
25
+
26
+ * **readme:** add photos of the validated dongle ([#15](https://github.com/bbayszczak/pyneosol/issues/15)) ([e405433](https://github.com/bbayszczak/pyneosol/commit/e4054337294fab6407f91b32e9b8706f64722636))
27
+
28
+ ## [0.3.0](https://github.com/bbayszczak/pyneosol/compare/v0.2.0...v0.3.0) (2026-09-18)
29
+
30
+
31
+ ### Features
32
+
33
+ * add the dongle driver ([f04cddc](https://github.com/bbayszczak/pyneosol/commit/f04cddc815d5152e1e92584fdc0a3a643eba9f16))
34
+ * update README ([2d2048e](https://github.com/bbayszczak/pyneosol/commit/2d2048e4e55e708816482b470eba46e43d4cf30e))
35
+
36
+
37
+ ### Documentation
38
+
39
+ * add AT protocol specification for the Neosol 868 dongle ([d02aa2b](https://github.com/bbayszczak/pyneosol/commit/d02aa2b0a16c19c186464910927a5c6eac80f30c))
40
+ * add project disclaimer and hardware identification ([0c750c9](https://github.com/bbayszczak/pyneosol/commit/0c750c93b185686716f5d07197cfa41ea7dbda77))
41
+ * add USB identification and the two rejection forms ([c4c9a8b](https://github.com/bbayszczak/pyneosol/commit/c4c9a8b7c3eb879a1298fdeebbf2be043af06816))
42
+ * confirm the favourite position action and how it is recorded ([4748b5f](https://github.com/bbayszczak/pyneosol/commit/4748b5fe97a12d94652b056434daac40fef2750b))
43
+ * document installation, usage and development ([f3e8039](https://github.com/bbayszczak/pyneosol/commit/f3e8039bc5adad5eb631a8ff9640673a2aaa798c))
44
+ * explain how to run the demonstration script ([7d122af](https://github.com/bbayszczak/pyneosol/commit/7d122af3c0d996c2964bc9ab3287444e7d128ed9))
45
+ * frame the project as compatible with the hardware, not derived from it ([14e4444](https://github.com/bbayszczak/pyneosol/commit/14e444451046bad2235de4c6ac7664b996c7d66e))
46
+ * record that the dongle has no usable radio reception ([499491a](https://github.com/bbayszczak/pyneosol/commit/499491a38093c48b44d150c84f32f2886cbbe937))
47
+ * use the exact dongle reference and drop the untested one ([c989827](https://github.com/bbayszczak/pyneosol/commit/c98982794ae318158f3c388a073f00cc76d32022))
48
+
49
+ ## [0.2.0](https://github.com/bbayszczak/pyneosol/compare/v0.1.0...v0.2.0) (2026-09-18)
50
+
51
+
52
+ ### Features
53
+
54
+ * add the dongle driver ([e41e284](https://github.com/bbayszczak/pyneosol/commit/e41e284fec4763a2b4252db31e51581023371395))
55
+ * update README ([534c10f](https://github.com/bbayszczak/pyneosol/commit/534c10f7ffe93fa8806535decdc3603bf0ce225f))
56
+
57
+
58
+ ### Documentation
59
+
60
+ * add AT protocol specification for the Neosol 868 dongle ([e8e2f36](https://github.com/bbayszczak/pyneosol/commit/e8e2f36c333166339f42695901d0df51e340bc13))
61
+ * add project disclaimer and hardware identification ([720a5f6](https://github.com/bbayszczak/pyneosol/commit/720a5f61b0190f59e7662944e2c688dab253fb41))
62
+ * add USB identification and the two rejection forms ([997bed3](https://github.com/bbayszczak/pyneosol/commit/997bed39ac82149fc144e32007edc323f8a2991d))
63
+ * confirm the favourite position action and how it is recorded ([ae09232](https://github.com/bbayszczak/pyneosol/commit/ae09232829c8fcc37b81e70bf8fa2a3a7a772790))
64
+ * document installation, usage and development ([569b1e0](https://github.com/bbayszczak/pyneosol/commit/569b1e02481880294482f0644704dd0ec5d7220e))
65
+ * explain how to run the demonstration script ([44e8deb](https://github.com/bbayszczak/pyneosol/commit/44e8debd9e6433f45ec3574c0516adbbe3da9e4f))
66
+ * frame the project as compatible with the hardware, not derived from it ([3f6cd9a](https://github.com/bbayszczak/pyneosol/commit/3f6cd9a1901d976536548137dc3c605670cff1af))
67
+ * record that the dongle has no usable radio reception ([9aa1a1f](https://github.com/bbayszczak/pyneosol/commit/9aa1a1fcb9197a9f503ffc04addd859ad32e4967))
68
+ * use the exact dongle reference and drop the untested one ([49c2b60](https://github.com/bbayszczak/pyneosol/commit/49c2b60521a9e53afc8e96638f0a4e3bbdebda0f))