Pendant le confinement, on m'a proposé de participer au développement d'un dispositif de mesure de la vitesse des modems LTE pour plusieurs opérateurs de téléphonie mobile.
Le client voulait évaluer la vitesse de divers opérateurs dans différents points géographiques, afin de comprendre quel opérateur de téléphonie mobile lui serait le plus optimal pour l'installation d'équipements utilisant une connexion LTE, par exemple pour des retransmissions vidéo. En même temps, la solution devait être la plus simple et économique possible, sans équipements coûteux.
Je vais dire tout de suite que cette tâche n’est pas simple et demande beaucoup de travail, je vais expliquer les problèmes que j'ai rencontrés et comment je les ai résolus. Alors, allons-y.
Remarque
Mesurer la vitesse d'une connexion LTE est une tâche assez complexe : il est nécessaire de bien choisir l'équipement et la méthode de mesure, ainsi que de bien comprendre la topologie et le fonctionnement du réseau mobile. De plus, plusieurs facteurs peuvent influencer la vitesse : le nombre d'abonnés sur la cellule, les conditions météorologiques, même d'une cellule à l'autre la vitesse peut varier considérablement en raison de la topologie du réseau. En gros, c'est une tâche avec une multitude d'inconnues, et seuls les opérateurs de télécommunications peuvent la résoudre correctement.
Au départ, le client voulait simplement faire courir un livreur avec des téléphones des opérateurs, effectuer des mesures directement sur le téléphone et ensuite noter les résultats dans un carnet. Ma solution de mesure de la vitesse des réseaux LTE, bien qu'imparfaite, résout la tâche qui m'a été confiée.
En raison du manque de temps, j'ai pris des décisions non pas en faveur de la commodité ou de la praticité, mais en faveur de la rapidité de développement. Par exemple, pour l'accès à distance, j'ai établi un reverse ssh, au lieu d'un vpn plus pratique, afin d'économiser du temps sur la configuration du serveur et de chaque client individuel.
Cahier des charges
Comme indiqué dans l'article : Ne travaillez jamais sans cahier des charges ! Jamais, nulle part !
Le cahier des charges était assez simple, je vais l'élargir un peu pour la compréhension de l'utilisateur final. Le choix des solutions techniques et de l'équipement a été dicté par le client. Voici donc le cahier des charges, après toutes les approbations :
Sur la base d'un ordinateur monocarte vim2 faire un testeur de vitesse de connexion LTE via des modems Huawei e3372h — 153 de plusieurs opérateurs de télécommunication (d'un à n). Il est également nécessaire d'obtenir les coordonnées d'un récepteur GPS connecté via UART. Les mesures de vitesse doivent être effectuées à l'aide du service et de les compiler dans un tableau comme suit :
Tableau au format csv. Ensuite, envoyer ce tableau par e-mail toutes les 6 heures. En cas d'erreurs, faire clignoter une LED connectée au GPIO.
J'ai décrit le cahier des charges de manière libre, après de nombreuses validations. Mais le sens de la tâche est déjà visible. Le délai pour tout était d'une semaine. Mais en réalité, il s'est étendu sur trois semaines. Cela en tenant compte du fait que je le faisais uniquement après mon travail principal et pendant les week-ends.
Ici, je tiens à souligner à nouveau que le client avait convenu à l'avance de l'utilisation d'un service de mesure de vitesse et du matériel, ce qui a fortement limité mes possibilités. Le budget était également limité, donc rien de particulier n'a été acheté. J'ai donc dû jouer selon ces règles.
Architecture et développement
Le schéma est simple et évident. Je vais donc le laisser sans commentaires particuliers.

J'ai décidé de réaliser l'ensemble du projet en python, malgré le fait que je n'avais pas d'expérience dans le développement avec ce langage. Je l'ai choisi car il y avait une multitude d'exemples et de solutions prêts à l'emploi, ce qui pouvait accélérer le développement. Par conséquent, je demande à tous les programmeurs professionnels de ne pas critiquer ma première expérience de développement en python, et je suis toujours prêt à entendre des critiques constructives pour améliorer mes compétences.
J'ai également découvert que python a deux versions majeures, 2 et 3, et j'ai finalement opté pour la troisième.
Unités matérielles
Ordinateur à carte unique vim2
En tant que machine principale, on m'a donné un ordinateur à carte unique

Un excellent et puissant media center pour la maison intelligente et le SMART-TV, mais qui s'avère étonnamment inadapté à cette tâche, ou disons, faiblement adapté. Par exemple, son système d'exploitation principal est Android, tandis que Linux est un système secondaire, et donc personne ne garantit un bon fonctionnement de tous les nœuds et pilotes sous Linux. Je suppose qu'une partie des problèmes était liée aux pilotes USB de cette plateforme, c'est pourquoi les modems ne fonctionnaient pas comme je l'attendais sur cette carte. De plus, il a une documentation très pauvre et morcelée, donc chaque opération prenait beaucoup de temps à fouiller dans les docs. Même le travail ordinaire avec le GPIO a été pénible. Par exemple, pour configurer le fonctionnement avec une LED, j'ai mis plusieurs heures. Mais, pour être objectif, il n'était fondamentalement pas important de savoir quel type de carte unique c'était, l'essentiel était qu'elle fonctionne et qu'il y ait des ports USB.
Tout d'abord, j'ai besoin d'installer Linux sur cette carte. Pour ne pas errer dans les méandres de la documentation, et aussi pour ceux qui vont se pencher sur cette carte, j'écris ce chapitre.
Il y a deux options pour installer Linux : sur une carte SD externe ou sur une MMC interne. J'ai passé un moment à essayer avec la carte, mais je n'ai pas réussi à la faire fonctionner, donc j'ai décidé d'installer sur la MMC, même si, sans aucun doute, il aurait été beaucoup plus facile de travailler avec une carte externe.
À propos du firmware . Je traduis de l'étrange au russe. Pour flasher la carte, j'ai besoin de connecter un UART matériel. Je l'ai connecté
- Tool Pin GND: Pin17 du GPIO de VIM
- Tool Pin TXD: Pin18 du GPIO de VIM (Linux_Rx)
- Tool Pin RXD: Pin19 du GPIO de VIM (Linux_Tx)
- Tool Pin VCC: Pin20 du GPIO de VIM

Après cela, j'ai téléchargé le firmware . La version spécifique du firmware .
Pour flasher ce firmware, j'ai besoin de certaines utilitaires. Plus de détails à ce sujet sont expliqués . Je n'ai pas essayé de flasher sous Windows, mais il faut dire quelques mots sur le flashage sous Linux. Tout d'abord, j'installerai les utilitaires, selon les instructions.
git clone https://github.com/khadas/utils
cd /path/to/utils
sudo ./INSTALLEt… Rien ne fonctionne. J'ai passé quelques heures à modifier les scripts d'installation pour que tout s'installe correctement chez moi. Je ne me souviens plus de ce que j'ai fait, mais c'était aussi un vrai cirque. Donc, soyez prudents. Mais sans ces utilitaires, il n'y a pas de sens à continuer à tourmenter vim2. Mieux vaut ne pas s'y frotter du tout !
Après sept cercles de souffrance, la configuration des scripts et l'installation, j'ai obtenu un paquet d'outils fonctionnels. J'ai connecté la carte par USB à mon ordinateur Linux, et j'ai également connecté le UART selon le schéma ci-dessus.
Je configure mon terminal préféré minicom à 115200 bauds, sans contrôle d'erreur matériel ou logiciel. Et nous commençons.

Au démarrage de VIM2 dans le terminal UART, j'appuie sur une touche, par exemple la barre d'espace, pour arrêter le démarrage. Après l'apparition de la ligne
kvim2# J'entre la commande :
kvim2# run updateSur l'hôte depuis lequel nous démarrons, j'exécute :
burn-tool -v aml -b VIM2 -i VIM2_Ubuntu-server-bionic_Linux-4.9_arm64_EMMC_V20191231.imgVoilà, ouf. J'ai flashé, la carte a Linux. Login/mot de passe khadas:khadas.
Après cela, quelques réglages de base. Pour travailler plus tard, je désactive le mot de passe pour sudo (oui, ce n'est pas sûr, mais c'est pratique).
sudo visudoJ'édite la ligne pour qu'elle soit de cette forme et je sauvegarde
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) NOPASSWD: ALLEnsuite, je change la locale courante, pour que l'heure soit celle de Moscou, sinon elle sera celle de Greenwich.
sudo timedatectl set-timezone Europe/Moscowou
ln -s /usr/share/zoneinfo/Europe/Moscow /etc/localtimeSi cela vous semble difficile, ne vous servez pas de cette carte, mieux vaut un Raspberry Pi. Honnêtement.
Modem Huawei e3372h — 153
Ce modem m'a causé bien des soucis et, en fait, il est devenu le maillon faible de tout le projet. En réalité, le terme "modem" pour ces dispositifs ne reflète pas du tout leur fonctionnement : c'est une machine puissante, cet appareil a un dispositif composite qui fait semblant d'être un CD-ROM pour installer des pilotes, puis passe en mode carte réseau.
Architecturalement, du point de vue de l'utilisateur Linux après tous les réglages, cela se présente ainsi : après la connexion du modem, j'obtiens une interface réseau eth*, qui reçoit une adresse IP 192.168.8.100 par DHCP, et la passerelle par défaut est 192.168.8.1.
Et le point le plus important ! Ce modèle de modem ne sait pas fonctionner en mode modem, contrôlé par des commandes AT.. Ce serait beaucoup plus simple de créer des connexions ppp pour chaque modem et de travailler ensuite avec elles. Mais dans mon cas, le "système" (ou plutôt le driver Linux selon les règles de udev) crée une interface eth et lui attribue une adresse IP par DHCP.
Pour ne pas se perdre, je propose d'oublier le mot "modem" et de parler de carte réseau et de passerelle, car en fait, c'est comme connecter une nouvelle carte réseau avec une passerelle.
Quand il y a un modem, cela ne pose pas de problèmes particuliers, mais quand il y en a plusieurs, c'est-à-dire n d'unités, cela entraîne la situation suivante dans le réseau.

C'est-à-dire n cartes réseau, avec une même adresse IP, et chacune a la même passerelle par défaut. Mais en réalité, chacune d'elles est connectée à son propre opérateur.
Au départ, j'avais une solution simple: utiliser la commande ifconfig ou ip pour désactiver toutes les interfaces et simplement activer l'une d'elles à la fois pour la tester. La solution était acceptable, sauf que pendant les commutations, je n'avais pas la possibilité de me connecter à l'appareil. Et comme les commutations étaient fréquentes et rapides, je n'avais en fait pas de possibilité de me connecter du tout.
C'est pourquoi j'ai choisi d'alterner manuellement les adresses IP des modems et de gérer le trafic à l'aide des paramètres de routage.

Cependant, mes problèmes avec les modems n'étaient pas terminés : en cas de problème d'alimentation, ils se déconnectaient, nécessitant une alimentation USB fiable et stable. J'ai résolu ce problème en reliant directement l'alimentation au hub. Un autre problème auquel j'ai été confronté et qui a ruiné tout le projet : après un redémarrage ou un démarrage à froid, tous les modems n'étaient pas reconnus et pas toujours, et je n'ai pas pu établir pourquoi cela se produisait et selon quel algorithme. Mais examinons tout cela dans l'ordre.
Pour assurer le bon fonctionnement du modem, j'ai installé le paquet usb-modeswitch.
sudo apt update
sudo apt install -y usb-modeswitch Après cela, le modem sera correctement détecté et configuré par le sous-système udev après connexion. Je vérifie simplement en connectant le modem et en m'assurant que le réseau est apparu.
Un autre problème que je n'ai pas pu résoudre : comment obtenir le nom de l'opérateur avec lequel nous travaillons depuis ce modem ? Le nom de l'opérateur se trouve dans l'interface web du modem à l'adresse 192.168.8.1. Il s'agit d'une page web dynamique qui obtient des données par des requêtes ajax, donc il n'est pas possible de simplement wget la page et d'extraire le nom. J'ai donc commencé à explorer comment traiter la page web, etc., et j'ai réalisé que je m'attaquais à une absurdité. Au final, j'ai laissé tomber et j'ai commencé à obtenir le nom de l'opérateur via l'API de Speedtest.
Beaucoup de choses seraient plus simples si le modem avait un accès via des commandes AT. Il serait possible de le reconfigurer, de créer une connexion ppp, d'assigner une adresse IP, d'obtenir l'opérateur de télécommunications, etc. Mais hélas, je dois travailler avec ce qu'on m'a donné.
GPS
Le récepteur GPS que l'on m'a remis avait une interface UART et un alimentation. Ce n'était pas la meilleure solution, mais néanmoins fonctionnelle et simple. Le récepteur avait à peu près cet aspect.

Pour être honnête, c'était la première fois que je travaillais avec un récepteur GPS, mais comme je l'avais supposé, tout a déjà été pensé pour nous. Donc, nous utilisons simplement des solutions toutes faites.
Pour commencer, j'active uart_AO_B (UART_RX_AO_B, UART_TX_AO_B) pour connecter le GPS.
khadas@Khadas:~$ sudo fdtput -t s /dtb.img /serial@c81004e0 status okayEnsuite, je vérifie le succès de l'opération.
khadas@Khadas:~$ fdtget /dtb.img /serial@c81004e0 status
okayCette commande semble modifier à la volée le devtree, ce qui est très pratique.
Après le succès de cette opération, nous redémarrons et installons le démon gps.
khadas@Khadas:~$ sudo rebootInstallation du démon gps. J'installe tout et je l'arrête immédiatement pour la configuration ultérieure.
sudo apt install gpsd gpsd-clients -y
sudo killall gpsd
/* Arrêt/désactivation du démon GPS */
sudo systemctl stop gpsd.socket
sudo systemctl disable gpsd.socketJ'édite le fichier de configuration.
sudo vim /etc/default/gpsdJ'installe l'UART sur lequel le GPS sera connecté.
DEVICES="/dev/ttyS4"Et après, nous activons tout et démarrons.
/* GPS daemon enable/start */
sudo systemctl enable gpsd.socket
sudo systemctl start gpsd.socketAprès cela, je connecte le GPS.

Dans mes mains, il y a le câble GPS, sous mes doigts, je sens les câbles UART du débogueur.
Je redémarre et vérifie le fonctionnement du GPS à l'aide du programme gpsmon.

Sur cette capture d'écran, il n'y a pas de satellites visibles, mais la communication avec le récepteur GPS est visible, ce qui indique que tout va bien.
J'ai testé de nombreuses façons de travailler avec ce démon en python, mais je me suis arrêté sur celle qui fonctionnait correctement avec python 3.
J'installe la bibliothèque nécessaire.
sudo -H pip3 install gps3 Et je compile le code de fonctionnement.
from gps3.agps3threaded import AGPS3mechanism
...
def getPositionData(agps_thread):
counter = 0;
while True:
longitude = agps_thread.data_stream.lon
latitude = agps_thread.data_stream.lat
if latitude != 'n/a' and longitude != 'n/a':
return '{}' .format(longitude), '{}' .format(latitude)
counter = counter + 1
print ("Attendre le compteur GPS = %d" % counter)
if counter == 10:
ErrorMessage("Erreur du récepteur GPS!!!")
return "NA", "NA"
time.sleep(1.0)
...
f __name__ == '__main__':
... #gps
agps_thread = AGPS3mechanism() # Instanciation des mécanismes AGPS3
agps_thread.stream_data() # Depuis localhost (), ou d'autres hôtes, par exemple, (host='gps.ddns.net')
agps_thread.run_thread() # Délai d'endormissement après une recherche vide, par défaut '()' 0.2 deux dixièmes de seconde
Si j'ai besoin d'obtenir les coordonnées, je le fais avec cet appel suivant :
longitude, latitude = getPositionData(agps_thread)
En l'espace de 1 à 10 secondes, je recevrai soit les coordonnées, soit non. Oui, j'ai eu dix tentatives pour obtenir les coordonnées. Ce n'est pas optimal, c'est brouillon, mais ça fonctionne. J'ai décidé de faire ainsi car le GPS peut avoir des problèmes de réception et ne pas toujours fournir les données. Si j'attends la réception des données, dans un environnement clos, le programme se bloquera à cet endroit. J'ai donc mis en place une solution qui n'est pas très élégante.
En gros, s'il y avait eu plus de temps, je pourrais obtenir les données directement via UART depuis le GPS, les analyser dans un fil séparé et travailler avec elles. Mais je n'avais absolument pas le temps, d'où ce code peu esthétique. Et oui, je n'en ai pas honte.
LED
La connexion de la LED était à la fois simple et complexe. La principale difficulté réside dans le fait que le numéro de broche système ne correspond pas au numéro de broche sur la carte, et en plus, la documentation est écrite de façon peu claire. Pour faire correspondre le numéro de la broche matérielle et celui du système d'exploitation, il faut exécuter la commande :
gpio readallUne table de correspondance des broches dans le système et sur la carte sera affichée. Après cela, je peux manipuler la broche dans le système d'exploitation. Dans mon cas, la LED est connectée à GPIOH_5.

Je mets la broche GPIO en mode sortie.
gpio -g mode 421 outJ'écris un zéro.
gpio -g write 421 0J'écris un un.
gpio -g write 421 1 
Tout s'allume après avoir écrit « 1 »
#gpio subsistem
def gpio_init():
os.system("gpio -g mode 421 out")
os.system("gpio -g write 421 1")
def gpio_set(val):
os.system("gpio -g write 421 %d" % val)
def error_blink():
gpio_set(0)
time.sleep(0.1)
gpio_set(1)
time.sleep(0.1)
gpio_set(0)
time.sleep(0.1)
gpio_set(1)
time.sleep(0.1)
gpio_set(0)
time.sleep(1.0)
gpio_set(1)
def good_blink():
gpio_set(1)
Maintenant, en cas d'erreurs, j'appelle error_blink() et la LED clignote joliment.
Modules logiciels
API Speedtest
C'est une bonne nouvelle que le service speedtest.net ait sa propre API en python, que l'on peut consulter ici : .
Ce qui est bien, c'est qu'il y a aussi des codes sources que l'on peut consulter. Comment utiliser cette API (exemples simples) est expliqué ici : .
J'installe la bibliothèque python avec la commande suivante.
sudo -H pip3 install speedtest-cliPour l'exemple, vous pouvez tout à fait installer le test de vitesse sur Ubuntu directement depuis le dépôt. C'est la même application python, qui peut ensuite être lancée depuis le terminal.
sudo apt install speedtest-cli -yEt mesurer la vitesse de votre internet.
speedtest-cli
Récupération de la configuration speedtest.net...
Test en cours depuis B***** (*.*.*.*)...
Récupération de la liste des serveurs speedtest.net...
Sélection du meilleur serveur en fonction du ping...
Hébergé par MTS (Moscou) [0.12 km]: 11.8 ms
Test de la vitesse de téléchargement................................................................................
Téléchargement : 7.10 Mbit/s
Test de la vitesse de téléversement......................................................................................................
Téléversement : 3.86 Mbit/s
Au final, comme je l'ai fait. J'ai dû plonger dans le code source de ce test de vitesse pour les intégrer plus pleinement dans mon projet. L'un des objectifs les plus importants est d'obtenir également le nom de l'opérateur de télécommunications, pour l'insérer dans le tableau.
import speedtest
from datetime import datetime
...
# Indiquez un serveur spécifique pour le test
#6053) MaximaTelecom (Moscou, Fédération de Russie)
servers = ["6053"]
# Si vous souhaitez utiliser un test à thread unique
threads = None
s = speedtest.Speedtest()
# Obtenez le nom de l'opérateur de téléphonie mobile
opos = '%(isp)s' % s.config['client']
s.get_servers(servers)
# Obtenez une chaîne de texte avec les paramètres du serveur
testserver = '%(sponsor)s (%(name)s) [%(d)0.2f km]: %(latency)s ms' % s.results.server
# Test de téléchargement
s.download(threads=threads)
# Test de téléchargement
s.upload(threads=threads)
# Obtenez les résultats
s.results.share()
# Ensuite, une chaîne est formée pour l'enregistrement dans un fichier CSV.
# Obtenez la position GPS
longitude, latitude = getPositionData(agps_thread)
# Temps et date
curdata = datetime.now().strftime('%d.%m.%Y')
curtime = datetime.now().strftime('%H:%M:%S')
delimiter = ';'
result_string = opos + delimiter + str(curpos) + delimiter +
curdata + delimiter + curtime + delimiter + longitude + ', ' + latitude + delimiter +
str(s.results.download/1000.0/1000.0) + delimiter + str(s.results.upload / 1000.0 / 1000.0) +
delimiter + str(s.results.ping) + delimiter + testserver + "n"
# Ici, l'enregistrement se fait dans le fichier journal
Ici, cela s'est également avéré être plus compliqué que prévu, bien que cela semble simple. À l'origine, le paramètre servers était égal [], disons de choisir le meilleur serveur. En conséquence, j'avais des serveurs aléatoires, et comme il est facile de le deviner, la vitesse était fluctuante. C'est un sujet assez complexe d'utiliser un serveur fixe, si oui, alors statique ou dynamique, cela nécessite une recherche. Mais voici un exemple de graphiques de mesure de la vitesse de l'opérateur Beeline avec le choix dynamique d'un serveur de test et un fixe.

Résultat de la mesure de vitesse avec un serveur dynamique.

Résultat du test de vitesse avec un serveur rigoureusement choisi.
Il y a des "interférences" lors des tests, tant ici que là, et il faut les éliminer par des méthodes mathématiques. Mais avec un serveur fixe, il y en a un peu moins et l'amplitude est plus stable.
C'est en fait un domaine de grandes recherches. Et je ferais des mesures de vitesse vers mon serveur, en utilisant l'outil iperf. Mais nous restons fidèles au cahier des charges.
Envoi de courriels et erreurs
Pour l'envoi de courriels, j'ai essayé des dizaines de variantes différentes, mais au final, je me suis arrêté sur la suivante. J'ai enregistré une boîte de réception sur yandex et ensuite j'ai pris . Je l'ai testé et intégré dans le programme. Dans cet exemple, plusieurs variantes sont examinées, y compris l'envoi depuis gmail, etc. Je n'avais pas envie de m'occuper de la mise en place de mon propre serveur de messagerie et je n'avais pas de temps pour cela, mais comme il s'est avéré, c'était inutile.
L'envoi des journaux était effectué par le planificateur, en cas de connexion, toutes les 6 heures : à 00h00, 6h00, 12h00 et 18h00. J'envoyais de la manière suivante.
from send_email import *
...
message_log = "Journaux de test de la carte n°1"
EmailForSend = ["dlinyj@trololo.ru", "pupkin@trololo.ru"]
files = ["/home/khadas/modems_speedtest/csv"]
...
def sendLogs():
global EmailForSend
curdata = datetime.now().strftime('%d.%m.%Y')
сurtime = datetime.now().strftime('%H:%M:%S')
try:
for addr_to in EmailForSend:
send_email(addr_to, message_log, "Journaux pour " + curdata + " " + сurtime, files)
except:
print("Problème de réseau lors de l'envoi de l'email")
return False
return True
Les erreurs étaient aussi initialement envoyées. Au départ, elles s'accumulaient dans une liste, puis étaient envoyées également par le planificateur, en cas de connexion. Cependant, des problèmes sont survenus car Yandex a une limite sur le nombre de messages envoyés par jour (c'est une douleur, une tristesse et une humiliation). Étant donné qu'il pouvait y avoir un grand nombre d'erreurs par minute, il a donc fallu renoncer à l'envoi des erreurs par email. Donc, gardez à l'esprit qu'il y a un problème avec l'envoi automatisé via les services de Yandex.
Serveur de retour d'information
Pour avoir accès au matériel distant et pouvoir le configurer et le reconfigurer, j'avais besoin d'un serveur externe. En toute justice, il serait préférable d'envoyer toutes les données sur le serveur et de construire tous les beaux graphiques dans l'interface web. Mais tout ne peut pas être fait en même temps.
J'ai choisi comme VPS . J'aurais pu prendre le serveur le plus simple. Et dans l'ensemble, cela aurait suffi largement pour mes objectifs. Mais comme je ne payais pas le serveur de ma propre poche, j'ai décidé d'en prendre un avec un petit surplus, au cas où nous déploierions une interface web, notre serveur SMTP, un VPN, etc. De plus, avoir la possibilité de configurer un bot Telegram et de ne pas avoir de problèmes de blocage. C'est pourquoi j'ai choisi Amsterdam et les paramètres suivants.

Pour la connexion avec le matériel vim2, j'ai choisi une connexion SSH inversée et comme l'a montré la pratique — ce n'est pas la meilleure solution. En cas de rupture de connexion, le serveur garde le port et il est impossible de se reconnecter pendant un certain temps. Par conséquent, il est quand même préférable d'utiliser d'autres moyens de connexion, comme un VPN. À l'avenir, je voulais passer au VPN, mais je n'ai pas eu le temps.
Je ne vais pas entrer dans les détails de la configuration du pare-feu, des restrictions de droits, de la désactivation de la connexion SSH root et d'autres vérités évidentes sur la configuration d'un VPS. J'espère que vous savez déjà tout cela. Pour la connexion à distance, je crée un nouvel utilisateur sur le serveur.
adduser vimsshSur notre machine, je génère des clés SSH pour la connexion.
ssh-keygenEt je les copie sur notre serveur.
ssh-copy-id vimssh@host.comSur notre machine, je crée une connexion SSH inversée automatique à chaque démarrage.
[Unité]
Description=Auto Reverse SSH
Requires=systemd-networkd-wait-online.service
After=systemd-networkd-wait-online.service
[Service]
User=khadas
ExecStart=\/usr\/bin\/ssh -NT -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -CD 8080 -R 8083:localhost:22 vimssh@host.com
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
Notez le port 8083 : c'est lui qui détermine quel port sera utilisé pour la connexion via SSH inversé. Nous l'ajoutons au démarrage automatique et le démarrons.
sudo systemctl enable autossh.service
sudo systemctl start autossh.serviceOn peut même vérifier le statut :
sudo systemctl status autossh.serviceMaintenant, sur notre serveur VPS, si je fais :
ssh -p 8083 khadas@localhostJe me connecte à ma machine de test. Et depuis la machine, je peux également envoyer des journaux et toutes les données via SSH vers mon serveur, ce qui est très pratique.
Rassemblons tout

On commence à développer et à déboguer
Ouf, eh bien, j'ai décrit tous les nœuds. Il est maintenant temps de rassembler tout cela en un seul ensemble. Vous pouvez voir le code .
Un point important concernant le code : ce projet peut ne pas démarrer tel quel, car il est ajusté pour une tâche spécifique, avec une architecture particulière. Bien que je fournisse le code source, je vais tout de même expliquer les points les plus précieux ici, dans le texte, sinon ce sera complètement incompréhensible.
Au début, j'initialise le GPS, les GPIO et je lance un thread séparé pour le planificateur.
#запуск потока планировщика
pShedulerThread = threading.Thread(target=ShedulerThread, args=(1,))
pShedulerThread.start()Le planificateur est assez simple : il vérifie si c'est le moment d'envoyer des messages et quel est le statut actuellement des erreurs. S'il y a un drapeau d'erreur, on fait clignoter la LED.
#sheduler
def ShedulerThread(name):
global ready_to_send
while True:
d = datetime.today()
time_x = d.strftime('%H:%M')
if time_x in time_send_csv:
ready_to_send = True
if error_status:
error_blink()
else:
good_blink()
time.sleep(1)Le moment le plus difficile dans ce projet est de maintenir la connexion SSH inversée lors de chaque test. À chaque test, la configuration de la passerelle par défaut et du serveur DNS est effectuée à nouveau. Puisque personne ne lit de toute façon, sachez que le train ne roule pas sur des rails en bois. Celui qui trouve l'œuf de Pâques aura une friandise.
Pour cela, je crée une table de routage distincte — set-mark 0x2 et une règle pour rediriger le trafic.
def InitRouteForSSH():
cmd_run("sudo iptables -t mangle -A OUTPUT -p tcp -m tcp --dport 22 -j MARK --set-mark 0x2")
cmd_run("sudo ip rule add fwmark 0x2/0x2 lookup 102")Pour en savoir plus sur le fonctionnement de cela, vous pouvez .
Je passe ensuite dans une boucle infinie, où à chaque fois je récupère la liste des modems connectés (pour savoir si la configuration du réseau a changé).
network_list = getNetworklist()Obtenir la liste des interfaces réseau est assez simple.
def getNetworklist():
full_networklist = os.listdir('/sys/class/net/')
network_list = [x for x in full_networklist if "eth" in x and x != "eth0"]
return network_listAprès avoir obtenu la liste, j'assigne des adresses IP à toutes les interfaces, comme je l'ai montré dans l'image du chapitre sur les modems.
SetIpAllNetwork(network_list)
def SetIpAllNetwork(network_list):
for iface in network_list:
lastip = "%d" % (3 + network_list.index(iface))
cmd_run ("sudo ifconfig " + iface + " 192.168.8." + lastip +" up")Ensuite, je parcours simplement chaque interface dans une boucle. Et je configure chaque interface.
for iface in network_list:
ConfigNetwork(iface)def ConfigNetwork(iface):
# réinitialiser tous les paramètres
cmd_run("sudo ip route flush all")
# attribuer la passerelle par défaut
cmd_run("sudo route add default gw 192.168.8.1 " + iface)
# spécifier le serveur DNS (nécessaire pour le test de vitesse)
cmd_run ("sudo bash -c 'echo nameserver 8.8.8.8 > /etc/resolv.conf'")Je vérifie si l'interface fonctionne, s'il n'y a pas de réseau, je génère des erreurs. S'il y a un réseau, il est temps d'agir !
Ici, je configure le routage SSH sur cette interface (si cela n'a pas été fait), j'envoie les erreurs vers le serveur, si le moment est venu, j'envoie les journaux et enfin, je fais un test de vitesse et sauvegarde les journaux dans un fichier csv.
if not NetworkAvalible():
....
# Ici nous générons des erreurs
....
else: # Il y a réseau, hourra, travaillons !
# Si nous avons une interface problématique, sur laquelle SSH est actif, alors nous la changeons
if (sshint == lastbanint or sshint =="free"):
print("********** Configuration SSH ********************")
if sshint !="free":
cmd_run("sudo ip route del default via 192.168.8.1 dev " + sshint +" table 102")
SetupReverseSSH(iface)
sshint = iface
# maintenant que le réseau fonctionne, envoyons tout rapidement !!!
if ready_to_send:
print ("**** Prêt à envoyer !!!")
if sendLogs():
ready_to_send = False
if error_status:
SendErrors()
# et ensuite nous testons la vitesse et sauvegardons les journaux. Je dois seulement mentionner la fonction de configuration de SSH inversé.
def SetupReverseSSH(iface):
cmd_run("sudo systemctl stop autossh.service")
cmd_run("sudo ip route add default via 192.168.8.1 dev " + iface +" table 102")
cmd_run("sudo systemctl start autossh.service")Et bien sûr, il est nécessaire d'ajouter toute cette beauté au démarrage automatique. Pour cela, je crée un fichier :
sudo vim /etc/systemd/system/modems_speedtest.serviceEt j'y écris :
[Unité]
Description=Test de vitesse du modem
Requires=systemd-networkd-wait-online.service
After=systemd-networkd-wait-online.service
[Service]
User=khadas
ExecStart=\/usr\/bin\/python3.6 \/home\/khadas\/modems_speedtest\/networks.py
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
J'active le démarrage automatique et je démarre !
sudo systemctl enable modems_speedtest.service
sudo systemctl start modems_speedtest.serviceMaintenant, je peux voir les journaux de ce qui se passe avec la commande :
journalctl -u modems_speedtest.service --no-pager -fRésultats
Et maintenant, la question la plus importante : quel a été le résultat ? Je vais présenter quelques graphiques que j'ai pu capturer lors du développement et du débogage. Les graphiques ont été réalisés avec gnuplot à l'aide du script suivant.
#! /usr/bin/gnuplot -persist
set terminal postscript eps enhanced color solid
set output "Rostelecom.ps"
#set terminal png size 1024, 768
#set output "Rostelecom.png"
set datafile separator ';'
set grid xtics ytics
set xdata time
set ylabel "Speed Mb/s"
set xlabel 'Time'
set timefmt '%d.%m.%Y;%H:%M:%S'
set title "Rostelecom Speed"
plot "Rostelecom.csv" using 3:6 with lines title "Download", '' using 3:7 with lines title "Upload"
set title "Rostelecom 2 Ping"
set ylabel "Ping ms"
plot "Rostelecom.csv" using 3:8 with lines title "Ping"
La première expérience a été avec l'opérateur Tele2, que j'ai réalisée pendant plusieurs jours.

Ici, j'ai utilisé un serveur de mesure dynamique. Les mesures de vitesse fonctionnent, mais elles fluctuent énormément. Cependant, on peut tout de même voir une certaine valeur moyenne, et il est possible de l'obtenir en filtrant les données, par exemple, avec une moyenne mobile.
Plus tard, j'ai construit d'autres graphiques pour d'autres opérateurs de télécommunications. Le serveur de test dans ce cas était le même, et les résultats étaient également très intéressants.




Comme on peut le voir, le sujet est très vaste pour la recherche et le traitement de ces données, et cela ne se limite clairement pas à quelques semaines de travail. Mais…
Conclusion du travail
Le travail a été brusquement interrompu en raison de circonstances indépendantes de ma volonté. L'un des points faibles de ce projet, à mon avis, était le modem, qui ne voulait pas vraiment fonctionner en même temps que d'autres modems, et à chaque démarrage, il faisait des caprices. Il existe une multitude d'autres modèles de modems pour ces objectifs, qui ont généralement un format Mini PCI-e et se mettent à l'intérieur de l'appareil, et ils sont beaucoup plus faciles à configurer. Mais c'est une autre histoire. Le projet était intéressant et j'étais très heureux d'avoir pu y participer.
Source : habr.com

