Envoyer des SMS en Python avec des commandes AT

Envoyer des SMS à partir d’un logiciel peut-être utile dans plusieurs situations. Transmettre un code de double authentification pour se connecter à un système, envoyer des messages autogénérés à des clients, alerte en cas de problème sur un serveur…

Ayant à disposition un modem/routeur (ER75i v2) connecté au réseau GSM , je me suis penché sur l’implémentation d’un script d’envoi de SMS.

Python est un super langage avec tout un tas de fonctionnalités intégrées et installé de base sur les systèmes d’exploitation Linux. Il est tout naturel que je me sois tourné vers lui pour réaliser ce projet.

Connexion au modem

Pour mon modèle de modem, la connexion s’établit en TCP/IP. Nous allons utiliser un socket Python comme dans le code ci-dessous.

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((adresse_du_modem, port_du_modem))

Lorsque la communication est établie, il faut contrôler l’appareil en lui communiquant des commandes nommées AT.

Les commandes de contrôle

Il existe un grand nombre de ces commandes AT. Chacune donne accès à des informations et des fonctionnalités du modem. Elles sont standardisées, mais les paramètres qui leur sont passés dépendent de chaque modèle de modem. Heureusement, nous aurons besoin d’en connaître que deux d’entre elles pour atteindre notre but.

Il s’agit de :

  • AT+CMGF=1 : préciser au modem de passer en mode SMS texte.
  • AT+CMGS=+numéro_de_téléphone : dire à quel numéro de téléphone envoyer le message.

Ces commandes doivent être suivies du caractère CARIAGE_RETURN (CR) pour les valider. Confirmer l’envoi du message se fait avec les touches clavier CTRL+Z, ce qui correspond au caractère 26 de la table ASCII dans notre code.

s.sendall('AT+CMGF=1' + chr(13))
s.sendall('AT+CMGS=0478000000' + chr(13))
s.sendall('message test à envoyer' + chr(26))

L’exemple ci-dessus reprend la suite des commandes à envoyer. Cependant, le modem a besoin de recevoir les informations en bytes. Le code suivant effectue cette transformation.

bytes(string, 'latin_1')

Voici une ligne de commande complète comprenant la transformation.

at_code = bytes('AT+CMGF=1' + chr(13), 'latin_1')
s.sendall(at_code)

Nous avons à présent toutes les connaissances nécessaires pour créer un script complet d’envoi de SMS.

Il est à noter que les informations données dans cet article peuvent également être transposées dans un autre langage de programmation en se basant sur le même principe d’une connexion au modem et de l’envoi de commandes AT.

Exemple complet

Voici un exemple de code fonctionnel et commenté pour faciliter la compréhension.

#!/usr/bin/env python3

import socket
import sys

class SmsService():

    """
    Service pour envoyer des SMS en utilisant les commandes AT.
    Basé sur un routeur ER75i v2.
    """

    RETURN = chr(13) #  caractère pour passer à la ligne.
    CTRL_Z = chr(26) #  caractère pour préciser la fin du message et l'envoi.

    def __init__(self, host: str='192.168.90.103', port: int=8081):
        self.host = host
        self.port = port

    def send_sms(self, phone_number:str, message:str) -> str:
        """
        Envoyer un SMS.
        """
        #  connexion TCP/IP vers le modem/routeur.
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((self.host, self.port))
            #  commande AT pour dire au modem de passer en mode SMS texte.
            s.sendall(self.str_to_bytes('AT+CMGF=1' + self.RETURN))
            #  commande AT pour dire au modem à quel numéro envoyer le message.
            s.sendall(self.str_to_bytes('AT+CMGS='+ phone_number + self.RETURN))
            #  message texte à envoyer.
            s.sendall(self.str_to_bytes(message + self.CTRL_Z))
            #  réponse du modem afin de savoir si le message est envoyé.
            #  devrait être "OK"
            data = s.recv(1024)
            return data.decode('utf_8')

    def str_to_bytes(self, string:str):
        """
        Convertir les strings en bytes pour la communication vers le modem.
        """
        return bytes(string, 'latin_1')


"""
Envoyer un message en ligne de commande.
Paramètre 1 : numéro de téléphone.
Paramètre 2 : le message.

python3 .\send-sms.py 04700000 'sample test'
"""

if __name__ == "__main__":
    smsService: SmsService = SmsService()
    print(smsService.send_sms(sys.argv[1], sys.argv[2]))