pyneosol
Bibliothèque Python asyncio pour les dongles USB 868 MHz utilisant le protocole série AT
« PFX ». Compatible avec les volets roulants Profalux Neosol et le MAI-DONGLE868-1A.
Entièrement locale : ni box Calyps'HOME, ni cloud.
⚠️ PROJET INDÉPENDANT, SANS AUCUNE AFFILIATION
pyneosoln'est en aucun cas affilié, soutenu, approuvé ou validé par Profalux, Stella Advanced Technology, ou l'une quelconque de leurs filiales, marques, sociétés apparentées, sous-traitants ou partenaires. Profalux, Stella Advanced Technology, Neosol et Calyps'HOME sont des marques de leurs titulaires respectifs, citées uniquement pour décrire le matériel avec lequel cette bibliothèque communique, à des fins d'interopérabilité.👉 Lisez impérativement l'avertissement complet ci-dessous avant toute utilisation. Il couvre le risque matériel — certaines commandes du dongle sont destructives et peuvent effacer tous vos appairages —, l'absence totale de garantie et les conditions d'usage. En utilisant cette bibliothèque, vous reconnaissez les avoir lues et acceptées.
⚠️ Avertissement complet — à lire avant toute utilisation
Ce projet est totalement indépendant et n'est en aucun cas affilié, soutenu, approuvé ou validé par Profalux, Stella Advanced Technology, ou l'une quelconque de leurs filiales, marques, sociétés apparentées, sous-traitants ou partenaires.
Profalux, Stella Advanced Technology, Neosol, NeosoL et Calyps'HOME sont des marques de leurs titulaires respectifs. Elles ne sont citées ici que pour décrire le matériel avec lequel cette bibliothèque est susceptible de communiquer, à des fins d'interopérabilité. Aucun code, aucun binaire, aucun micrologiciel, aucune clé cryptographique et aucune documentation du fabricant n'est reproduit ni redistribué dans ce dépôt.
Nom du projet — pyneosol est un nom d'usage choisi pour sa lisibilité. Il
n'emporte aucune affiliation, ne constitue ni une marque, ni une revendication
d'origine, ni une autorisation du titulaire de la marque Neosol. Cette bibliothèque
est un composant tiers compatible avec ce matériel, et rien d'autre.
Méthode — le protocole documenté ici a été reconstitué par la seule observation du dialogue série avec un dongle acquis légalement, sur une installation appartenant à l'auteur. Aucune décompilation de micrologiciel, aucune extraction ni publication de clé cryptographique constructeur n'a été réalisée. Ce travail relève de l'exception d'interopérabilité (art. L122-6-1 III et IV du Code de la propriété intellectuelle, directive 2009/24/CE art. 5 et 6).
Usage — cette bibliothèque est destinée au pilotage d'équipements dont vous êtes propriétaire ou légitime utilisateur, et à eux seuls.
Absence de garantie — ce logiciel est fourni « tel quel », sans aucune garantie d'aucune sorte, expresse ou implicite, y compris, sans s'y limiter, les garanties de qualité marchande, d'adéquation à un usage particulier et d'absence de contrefaçon. Dans les limites permises par le droit applicable, l'auteur ne saurait être tenu responsable d'un quelconque dommage : dysfonctionnement, détérioration de matériel, perte de configuration ou d'appairage, perte de données, ou tout dommage direct ou indirect résultant de l'utilisation de cette bibliothèque.
Risque matériel — certaines commandes du dongle sont destructives et peuvent effacer ses appairages (voir la section Commandes dangereuses des spécifications). Sauvegardez la configuration de votre dongle avant toute expérimentation.
Garantie constructeur — l'usage de ce logiciel avec votre matériel est susceptible d'en affecter la garantie. Vérifiez-le avant de l'utiliser.
Support — assuré bénévolement, sans engagement de délai ni de résultat.
Licence — MIT, voir LICENSE.
En utilisant cette bibliothèque, vous reconnaissez avoir lu et accepté l'ensemble de ces conditions.
Sommaire
- Démarrage rapide
- Matériel
- État du projet
- Installation
- Utilisation
- Logging
- Développement
- Contribuer
- Sécurité
Démarrage rapide
Avec uv et le dongle branché :
git clone https://github.com/bbayszczak/pyneosol
cd pyneosol
uv run demo.py
uv crée l'environnement et installe les dépendances tout seul. Sans argument, demo.py
n'émet rien : il identifie le dongle et liste les canaux utilisés — de quoi vérifier en
quelques secondes que votre matériel est reconnu.
Matériel
Cette bibliothèque est compatible avec le dongle USB 868 MHz distribué pour les volets roulants Profalux Neosol. Elle n'est ni fournie, ni distribuée, ni approuvée par le fabricant.
Elle fonctionne avec le dongle photographié ci-dessous, le MAI-DONGLE868-1A, sur lequel
tout le protocole a été validé.
Le dongle validé, de face et côté étiquette. Le numéro de série est masqué.
| Modèle validé | MAI-DONGLE868-1A |
| Identification interne | PFX KEELOQ |
| Hardware Version | 0 |
| Software Version | Rev10 |
| Interface | USB CDC-ACM (port série virtuel) |
| Débit | 115200 bauds |
ℹ️
MAI-DONGLE868-1Aest la seule référence sur laquelle le protocole a été validé, et la seule dont dispose l'auteur. L'existence et le comportement d'éventuelles autres références ne sont pas connus : rien n'est vérifié ni garanti sur une autre référence, une autre version matérielle ou une autre révision logicielle. Les retours sur d'autres modèles sont les bienvenus.
Identifier votre dongle
Avant d'ouvrir une issue, indiquez toujours la référence de votre dongle ainsi que ses
versions matérielle et logicielle. Elles s'obtiennent avec la commande AT AT&V.
1. Repérer le port série
# Linux
ls -l /dev/ttyACM*
# macOS
ls -1 /dev/cu.usbmodem*
2. Interroger le dongle
python3 -c "
import serial, time
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=1) # adapter le port
time.sleep(0.4)
ser.write(b'AT&V\r\n'); ser.flush()
time.sleep(1.5)
print(ser.read(ser.in_waiting).decode('utf-8', 'replace'))
ser.close()
"
3. Sortie attendue
PFX KEELOQ
Hardware Version: 0
Software Version: Rev10
S/N: XXXXXXXX
ACTIVE CONFIG :
Return Code Active : 1
Frame Repeat Nb : T0=25,T1=15,T2=70,T3=70
Read Protection Active : 0
AT&V:OK
La ligne PFX KEELOQ confirme qu'il s'agit bien d'un dongle de cette famille. Hardware Version et Software Version sont les deux valeurs à communiquer en cas de problème.
🔐 Le numéro de série (
S/N) identifie votre exemplaire : inutile de le publier.
État du projet
Stable. Le pilotage fonctionne, le protocole est documenté dans
docs/SPEC-PROTOCOLE-AT.md, et l'API publique suit le
versionnage sémantique : tout changement cassant passe par une version majeure.
Les changements de chaque version sont consignés dans le CHANGELOG, tenu à jour automatiquement à partir des messages de commit.
Installation
Le paquet est publié sur PyPI :
pip install pyneosol
Avec uv :
uv add pyneosol
Utilisation
La bibliothèque est asynchrone : tout échange avec le dongle est une coroutine, et rien n'y bloque la boucle d'événements — ni l'attente des réponses, ni l'ouverture du port, ni la détection.
import asyncio
from pyneosol import Dongle
async def main() -> None:
async with Dongle.connect() as dongle: # détection automatique du port
print((await dongle.info()).software_version)
for channel in await dongle.used_channels():
print(channel) # la clé n'est jamais affichée
await dongle.close_shutter(0) # descente
await dongle.stop(0) # arrêt en cours de course
await dongle.favourite(0) # position favorite
asyncio.run(main())
Le port peut aussi être imposé : Dongle.connect("/dev/ttyACM0").
Désappairer un volet
await dongle.unregister(canal) émet la trame de désappairage, qui ne fait rien seule : dans
la minute qui suit, amenez le volet deux fois en butée basse depuis sa télécommande
d'origine, en le remontant de 2 lattes entre les deux. Le moteur valide par un bref
va-et-vient et n'obéit plus au canal. Partez du volet à mi-course.
Seule cette confirmation visuelle atteste le succès : le dongle ne reçoit rien et ne touche
pas au canal, qui garde son identité et un compteur non nul, puis se réappaire normalement avec
register(). Le dongle ne sait donc pas quels canaux sont appairés : used_channels() donne
des candidats — tout canal appairé y figure, mais pas seulement —, et c'est à
l'application de retenir quel canal pilote quel volet. Le compteur n'est jamais remis à zéro,
pour des raisons de sécurité KeeLoq.
Procédure et justifications aux §6, §9 et §10 de la spec.
Ouvrir sans bloc async with
Dongle.connect() est un gestionnaire de contexte asynchrone : il ouvre le dongle et le
referme en sortant du bloc. Quand la durée de vie du dongle dépasse un bloc — le cas d'une
intégration domotique, qui l'ouvre au démarrage et le referme à l'arrêt —, utilisez
Dongle.open(), qui rend simplement le dongle ouvert :
dongle = await Dongle.open("/dev/ttyACM0")
try:
await dongle.open_shutter(0)
finally:
await dongle.close()
Dongle.connect() n'existe que pour éviter la forme async with await Dongle.open() ; les
deux prennent les mêmes arguments.
Détection du port
find_ports() est également une coroutine. L'énumération des ports parcourt l'arborescence
des périphériques de l'hôte — /sys sous Linux, IOKit sous macOS —, ce qui bloque : elle est
donc exécutée dans un thread de travail plutôt que sur la boucle. Dongle.open() l'appelle
lorsqu'aucun port n'est précisé, autrement dit elle se trouve sur le chemin asynchrone : une
bibliothèque asyncio n'a pas à y glisser d'appel bloquant à l'insu de l'appelant.
for port in await find_ports():
print(port.device, port.manufacturer, port.product)
⚠️ Aucun retour d'état. Le dongle ne fait qu'émettre. Une commande acceptée signifie qu'une trame est partie, jamais qu'un volet a bougé, et aucune position n'est lisible. Toute notion d'état ou de position relève de la couche appelante.
Essayer sans écrire de code
Le dépôt fournit un script de démonstration à sa racine :
uv run demo.py # identification et table des canaux
uv run demo.py --port /dev/ttyACM0 # forcer le port série
uv run demo.py --channel 2 --close # descente, avec confirmation
uv run demo.py --channel 2 --stop --yes # stop, sans confirmation
uv run demo.py --debug # afficher le dialogue AT (voir Logging)
Sans argument, il n'émet rien : il se contente d'identifier le dongle et de lister les canaux utilisés. C'est le moyen le plus rapide de vérifier que votre dongle est reconnu.
Les actions (--open, --close, --stop, --favourite) doivent être demandées explicitement
et déclenchent une confirmation, puisqu'elles déplacent un volet réel. Les clés ne sont jamais
affichées.
Exemple de sortie (valeurs factices) :
Looking for a dongle...
/dev/ttyACM0 PROFALUX / KEELOQ USB Device
Dongle
hardware version : 0
software version : Rev10
frame repeat : T0=25,T1=15,T2=70,T3=70
read protection : no
transmit power : 14
Channels: 50 total, 2 used
channel 0 serial 000AAAA1 sync 43
channel 1 serial 000AAAA2 sync 14
Read-only run: nothing was transmitted.
Logging
La bibliothèque utilise le module logging standard et ne configure rien : ni handler, ni
niveau, ni format. C'est l'application hôte qui décide. Chaque module a son logger, nommé
d'après le paquet — pyneosol.dongle, pyneosol.discovery — ce qui permet de filtrer au
paquet entier comme au module.
Tout est en DEBUG : le dialogue AT (commande émise, réponse, durée) et la détection du port.
Les erreurs ne sont pas loguées, elles sont levées ; c'est à l'appelant de décider ce qu'il
en fait.
Dans Home Assistant, via configuration.yaml :
logger:
logs:
pyneosol: debug # tout
pyneosol.dongle: debug # seulement le dialogue AT
En script autonome :
import logging
logging.basicConfig(level=logging.DEBUG)
Exemple de trace (valeurs factices) :
DEBUG pyneosol.discovery: 1 of 4 serial ports match 10C4:0003: ['/dev/ttyACM0']
DEBUG pyneosol.dongle: opening /dev/ttyACM0
DEBUG pyneosol.dongle: > AT$C?
DEBUG pyneosol.dongle: < ['0,***,0029, ***', '1,***,0009, ***', 'AT$C:OK'] (0.212s)
DEBUG pyneosol.dongle: > AT$SF=0,1
DEBUG pyneosol.dongle: < ['AT$SF:OK'] (0.004s)
✅ Aucun secret n'entre dans les logs. Le masquage joue dans les deux sens : les réponses du dongle comme les commandes envoyées. Les clés KeeLoq et les numéros de série deviennent
***, tandis que l'index du canal et son compteursyncrestent lisibles. Une traceDEBUGpeut donc être jointe telle quelle à un rapport de bug.Le sens commande compte parce que
Dongle.execute()accepte des commandes brutes : uneAT$C=ou uneAT$SN=formée à la main porte son secret dans la commande elle-même. Elle est loguéeAT$C=0,***,0029,***. Les messages d'exception suivent la même règle.Le chemin du port est masqué lui aussi, car il nomme votre exemplaire sur certains hôtes : macOS baptise le nœud d'après le numéro de série USB (
/dev/cu.usbmodem0000000012341), tout comme les liensby-idsous Linux. Seules les suites d'au moins quatre chiffres disparaissent —/dev/ttyACM0reste lisible tel quel :DEBUG pyneosol.discovery: 1 of 4 serial ports match 10C4:0003: ['/dev/cu.usbmodem***'] DEBUG pyneosol.dongle: opening /dev/cu.usbmodem***
Développement
uv run ruff check . # lint
uv run ruff format . # formatage
uv run pytest # tests — aucun matériel requis, le dongle est simulé
Contribuer
Les retours sont bienvenus, en particulier sur d'autres références de dongle que le
MAI-DONGLE868-1A, seule référence validée à ce jour.
- Signaler un bug ou proposer une évolution — ouvrir une issue, en précisant la référence de votre dongle et ses versions matérielle et logicielle (voir Identifier votre dongle).
- Proposer du code — périmètre, conventions et interdits sont décrits dans CONTRIBUTING.md, à lire avant d'ouvrir une pull request.
Sécurité
Le dongle stocke les clés KeeLoq de vos volets, lisibles en clair via la commande
AT$C?. Quiconque les possède peut commander vos volets.
Pour signaler une faille, utilisez le signalement privé — jamais une issue publique.
Ne publiez jamais le contenu réel de votre table de canaux — ni dans une issue, ni dans un rapport de bug, ni dans un export de configuration. Masquez systématiquement les clés et les numéros de série avant tout partage.
Metadata
Release files for pyneosol 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyneosol-1.1.0.tar.gz | 740.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyneosol-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 764.1 kB
Release files / pyneosol-1.1.0.tar.gz
| Download URL | pyneosol-1.1.0.tar.gz |
|---|---|
| Size | 740.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d59da26d216bcc3de20ca6989bf0ab90c3ccaa92cd8e752a30507f0d02c1e9fc
|
|
BLAKE2b-256 checksum How to use checksums |
bfc51a14df625ca6366f1c0dd0eaca0a7739d357688b7036d0cc0be6a6e20e6a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency logRelease files / pyneosol-1.1.0-py3-none-any.whl
| Download URL | pyneosol-1.1.0-py3-none-any.whl |
|---|---|
| Size | 24.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5a2259c8867f8a0dfbd003e32916bfeb62269f1b1dc7e2925cb36eff9a207a51
|
|
BLAKE2b-256 checksum How to use checksums |
c165fa3e0606202ae23a6f689983480172536e35155a02f14e9d7f3f35fbb3b4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.
Transparency log