Skip to main content

pyneosol

CI Version Python Licence

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

pyneosol 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 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

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é.

Dongle USB Neosol vu de face     Étiquette du dongle : Dongle Neosol, MAI-DONGLE868-1A, Lot 25/22, 5V 0.15 W
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-1A est 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 compteur sync restent lisibles. Une trace DEBUG peut donc être jointe telle quelle à un rapport de bug.

Le sens commande compte parce que Dongle.execute() accepte des commandes brutes : une AT$C= ou une AT$SN= formée à la main porte son secret dans la commande elle-même. Elle est loguée AT$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 liens by-id sous Linux. Seules les suites d'au moins quatre chiffres disparaissent — /dev/ttyACM0 reste 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)

Source distribution for pyneosol 1.1.0
File Size Uploaded
pyneosol-1.1.0.tar.gz 740.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyneosol 1.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.0

2 release files

0.4.2

2 release files

0.4.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page