dimanche 13 décembre 2020

TinySA

Un petit 'spectrum analyser' étonant, le tinySA. Une cinquantaine d'euros, il permet de se faire une idée du spectre électromagnétique jusqu'à 960 MHz.

Une première captation autour de 162 MHz, avec l'option 'Max hold', où l'on voit les deux canaux de l'AIS à 161.975 et 162.025 MHz. C'est capté du côté de la Citadelle de Liège et c'est émis par des bateaux, en contrebas sur la Meuse. Il faut noter que le scan sur 100 kHz dure 514 mS et que donc, on loupe des paquets. Ceux-ci, émis à 9600 bauds ne font que 168 ou 440 bits et ne sont donc présent que 20 ou 40 mS. Il arrive aussi que le TinySA ne voie que le début ou la fin du paquet et on n'a donc qu'une partie du spectre. La partie qu'il était en train de scanner pendant sa transmission. D'où l'utilisation de 'Max hold' pour cumuler les spectre.


Spectre UHF, jusqu'à 500 MHz, du côté de la Place Saint-Lambert à Liège. Pas de 'Max hold' ici, pour différencier les émissions continues des émissions par paquets.


En allant jusqu'à 960MHz, on voit des bandes GSM (?).




Bizarre, bizarre, le plan des fréquences de l'IBPT ne semble pas aider beaucoup à l'identification des signaux.

Un bout de la bande FM avec 'Max hold'. Le petit pic à droite du curseur est Bel-RTL sur 103.6.


Sur Linux,
$ sudo minicom -D /dev/ttyACM0 -b 115200

ch> help

Commands: version reset freq dac saveconfig clearconfig data frequencies scan scanraw sweep test touchcal 
touchtest pause resume caloutput save recall trace trigger marker usart capture vbat vbat_offset help info
 color if attenuate level sweeptime leveloffset levelchange modulation rbw mode spur load offset output
 deviceid selftest correction threads
permet de contrôler le TinySA en mode terminal

lundi 1 juin 2020

Simple ad hoc SNMP

SNMP est l'acronyme de 'Simple Network Management Protocol' et, comme tout ce qui est simple, cela fini par devenir compliqué...

Ci-dessous un petit programme en 'C', utilisant la librairie Net-snmp pour extraire quelques valeurs de la dskTable en SNMP version 2c.

Le programme fait une peu plus d'une centaine de lignes. On passe en paramètre la cible, la 'community string' et un index. Une session est ouverte vers la machine cible avec sa 'community string', un PDU est construit pour interroger 4 variables. Les 4 OIDs sont construites à partir de l'OID de base et un index passé en paramètre au programme. Le programme envoie la requête et reçoit la réponse. On suppose que la réponse reflète la requête et que l'on reçoit, dans l'ordre, les chaînes de caractères dskPath, dskDevice et les entiers dskUsed et dskTotal. Il ne reste plus qu'à les imprimer. Tous les problèmes possibles ne sont pas traités en profondeur mais il y a quand même un minimum de 'sanity checks'.

#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>

#define DSK_PATH 2
#define DSK_DEVICE 3
#define DSK_TOTAL 6
#define DSK_USED 8
unsigned char oid_str[128];
char *oid_template = ".1.3.6.1.4.1.2021.9.1.%d.%d"; /* What, idx */

void build_part_req(netsnmp_pdu *pdu, int idx, int what)
 {
 oid oid_buf[128];
 size_t oid_len;

 sprintf(oid_str, oid_template, what, idx);
 if (!snmp_parse_oid(oid_str, oid_buf, &oid_len))
  {
  fprintf(stderr, "error snmp_parse_oid()\n");
  exit(-1);
  }
 snmp_add_null_var(pdu, oid_buf, oid_len);
 }
netsnmp_pdu *build_req(int idx)
 {
 netsnmp_pdu *pdu;

 pdu = snmp_pdu_create(SNMP_MSG_GET);

 build_part_req(pdu, idx, DSK_PATH);
 build_part_req(pdu, idx, DSK_DEVICE);
 build_part_req(pdu, idx, DSK_USED);
 build_part_req(pdu, idx, DSK_TOTAL);
 return(pdu);
 }
void use_it(struct snmp_pdu *response)
 {
 char buf[1024];
 struct variable_list *vp;
 char dskPath[1024];
 char dskDevice[1024];
 long dskUsed;
 long dskTotal;

 if (response->errstat == SNMP_ERR_NOERROR)
  { /* vp->type (4=str; 2=int), vp->val.string, *vp->val.integer ; snprint_variable(), snprint_value() */
  vp = response->variables;
  strcpy(dskPath, vp->val.string); /* DSK_PATH */
  vp = vp->next_variable;
  strcpy(dskDevice, vp->val.string); /* DSK_DEVICE */
  vp = vp->next_variable;
  dskUsed = *vp->val.integer;  /* DSK_USED */
  vp = vp->next_variable;
  dskTotal = *vp->val.integer;  /* DSK_TOTAL */
  printf("%ld/%ld for %s on %s\n", dskUsed, dskTotal, dskPath, dskDevice);
  }
 else {
  printf("Uhm... : response->errstat = 0x%lx\n", response->errstat);
  }
 }
struct snmp_session *open_session(char *host, char *community)
 {
 struct snmp_session ss, *sp;

 init_snmp("get_snmp_fs");
 snmp_sess_init(&ss);
 ss.version = SNMP_VERSION_2c;
 ss.peername = strdup(host);
 ss.community = strdup(community);
 ss.community_len = strlen(community);
 if ((sp = snmp_open(&ss)) == NULL)
  {
  snmp_perror("snmp_open");
  exit(-1);
  }
 return(sp);
 }
void main (int argc, char **argv)
 {
 struct snmp_session *sp;
 netsnmp_pdu *req, *resp;
 char  *host, *community;
 int  index, status;

 if (argc != 4)
  {
  fprintf(stderr, "usage is '%s <host> <community> <index>\n", argv[0]);
  exit(-1);
  }
 host = argv[1];
 community = argv[2];
 index = atoi(argv[3]);

 sp = open_session(host, community);
 req = build_req(index);

 if ((status = snmp_synch_response(sp, req, &resp)) == STAT_SUCCESS)
  use_it(resp);
 else fprintf(stderr, "snmp_synch_response() returns %d\n", status);

 snmp_free_pdu(resp);
 snmp_close(sp);

 exit(0);
 }


Le Makefile, lui aussi, réduit à sa plus simple expression est :

CFLAGS=-I. `net-snmp-config --cflags`
BUILDLIBS=`net-snmp-config --libs`

get_snmp_fs: get_snmp_fs.o
 gcc -o get_snmp_fs get_snmp_fs.o $(BUILDLIBS)




Et maintenant, le test...



On regarde d'abord un disque avec df(1). On retrouve son indice en faisant un snmpwalk(1). On peut retrouver plusieurs valeurs avec snmpget(1) et les comparer avec ce que le programme ci-dessus donne.

$ df -h |grep sda1
/dev/sda1       916G  159G  711G  19% /


$ snmpwalk localhost -Os -m /usr/share/snmp/mibs/UCD-SNMP-MIB.txt -v 2c -c public  dskPath
dskPath.1 = STRING: /
dskPath.2 = STRING: /var
dskPath.3 = STRING: /
dskPath.4 = STRING: /run
dskPath.5 = STRING: /dev/shm
dskPath.6 = STRING: /run/lock
dskPath.7 = STRING: /sys/fs/cgroup


$ snmpget localhost -Os -m /usr/share/snmp/mibs/UCD-SNMP-MIB.txt -v 2c -c public  dskPath.1 dskDevice.1 dskUsed.1 dskTotal.1
dskPath.1 = STRING: /
dskDevice.1 = STRING: /dev/sda1
dskUsed.1 = INTEGER: 166654692
dskTotal.1 = INTEGER: 960379920


$ ./get_snmp_fs localhost public 1
166654708/960379920 for / on /dev/sda1


Les machines actuelles sont tellement rapides qu'il est difficile de mesurer la différence entre un programme ne faisant qu'un échange de PDUs et un programme qui doit, en plus, analyser une MIB (sinon plusieurs)... J'avais été motivé par le fait que Nagios utilise souvent des scripts shell lançant des tas de processus pour exécuter des fonctions élémentaires.

vendredi 17 avril 2020

De grand matin...

Activité aéroportuaire, ce jeudi 16 avril 2020, entre 04h13 et 05h13 captée avec un stick RTL-SDR (NewGen/RTL2832SDR alu TCXO .5 ppm) et dump1090 de la Citadelle de Liège... De la Citadelle de Liège, on capte l'ADS-B jusque Nancy.

jeudi 2 janvier 2020

SigInt ISM433

Petite captation sur la bande ISM 433 MHz avec un stick RTL-SDR v3, centrée sur 433.500 MHz avec 2048000 échantillons par seconde.

En regardant avec SDRAngel (ou sdrangelove)
En regardant avec gnuradio, on remarque de courtes trames centrées vers 433.922 MHz.
En important les données dans GNUoctave et en s'intéressant à la magnitude du signal, on observe :
En regardant de plus près, cela ressemble à du codage Manchester modulé en amplitude (ASK/OOK). Ce qui ne semble pas très économe en bande passante(?).
En s'intéressant à quelques bits seulement, on devrait pouvoir déduire le 'baud rate'...
En principe, on a tout pour créer un système pour décoder les trames avec gnuradio-companion...

Avec GnuRadio

(je ne maîtrise pas GnuRadio...) La première chose à faire est d'isoler le signal intéressant avec le 'Frequency Xlating FIR filter bloc'.
En envoyant ça dans 'Complex to Mag', puis dans un fichier qu'on analyse avec Octave, on obtient :


Autre signal, un FSK très soutenu

Si on s'intéresse au signal en FSK un peu à gauche, avec le bloc, 'Quadrature demod', le signal ressemble à :

dimanche 1 décembre 2019

BeSt - XSLT - mySQL

Il est possible de télécharger des listes de rues et de communes belges sur le site Open Data de BoSa (SPF Stratégie et Appui) : best-full-latest.zip (BeSt = Belgian Street).

Ce .zip contient des fichiers XML avec diverses informations concernant les rues et les communes de Belgique. C'est la référence officielle. Ces données sont aussi accessibles via un webservice (REST).

En ne prenant que la Wallonie, on a un peu plus que 61.000 rues et 263 communes. Ce qui va permettre de jouer un peu avec SQL.
J'ignore quelle est la meilleure manière d'injecter ces données XML dans une base de données mySQL. Alors, je le fais à ma manière... J'utilise un fichier de transformation XSL pour extraire les données qui m'intéressent et les mettre sous une forme facilement injectable dans une DB mySQL. Je crée une nouvelle base de données, best_street, Je crée deux tables (et comme je n'y connais rien, je leur donne de mauvais noms... : street_table et municipality_table.
$ mysql -u root -p
CREATE DATABASE best_street;
GRANT select, show view, lock tables, reload, replication client on *.* to 'bibi'@'localhost';
USE best_street;
CREATE TABLE street_table(
          street_id INT,
          street_name VARCHAR(100),
          municipality_id INT,
          PRIMARY KEY (street_id));
CREATE TABLE municipality_table(
          municipality_id INT,
          municipality_name VARCHAR(100),
          PRIMARY KEY (municipality_id));


Je récupère deux fichiers dans le .zip : WalloniaStreetname20191018.xml et WalloniaMunicipality20191018.xml. Ils contiennent des trucs du genre (attention, les fichiers des rues sont très grands, Firefox s'engorge) :

et


Que l'on peut traiter (sous Linux) avec
$ xsltproc x.xls y.xml > z.sql
Les fichiers .xsl étant, pour les rues,
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 xmlns:tns="http://fsb.belgium.be/mappingservices/FullDownload/v1_00"
 xmlns:com="http://vocab.belgif.be/ns/inspire/"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
INSERT INTO street_table VALUES
      <xsl:for-each select="tns:StreetResponseBySource/tns:Streetname">
   (<xsl:value-of select="com:streetnameCode/com:objectIdentifier"/>,"<xsl:value-of select="com:streetname/com:spelling"/>",<xsl:value-of select="com:isAssignedBy/com:Municipality/com:objectIdentifier"/>),</xsl:for-each>
</xsl:template>

</xsl:stylesheet> 

Et pour les communes,
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
        xmlns:tns="http://fsb.belgium.be/mappingservices/FullDownload/v1_00"
        xmlns:com="http://vocab.belgif.be/ns/inspire/"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
INSERT INTO municipality_table VALUES
      <xsl:for-each select="tns:MunResponseBySource/tns:Municipality">
          (<xsl:value-of select="com:municipalityCode/com:objectIdentifier"/>,"<xsl:value-of select="com:municipalityName/com:spelling"/>"),</xsl:for-each>
</xsl:template>

</xsl:stylesheet>

Le résultat n'est pas parfait et quelques adaptations cosmétiques sont nécessaires pour en faire de bons fichiers SQL. Il y a aussi des 'streetname' qui contiennent des '"' et le .xsl est trop primitif pour en tenir compte. Et, en fait, ce sont des données incorrectes. Cela donne quelque chose comme :
INSERT INTO street_table VALUES
   (7700015,"Chemin des Prés",25005),
   (7700016,"Chemin des Ramiers",25005),
   (7700017,"Chemin des Soeurs",25005),
   (7700018,"Chemin du Grand Champ",25005),
   (7700019,"Chemin du Petit Brou",25005),
...
   (7741188,"Rue Jean-Louis Dumont",64074),
   (7741189,"Rue Joseph Nicolas",64074),
   (7741190,"Rue Joseph Noville",64074),
   (7741254,"Rue des Bâtis",64075),
   (7736185,"Tribomont",63035),
   (7736549,"Route de Manhay",63045);

et
INSERT INTO municipality_table VALUES
   (52074,"Aiseau-Presles"),
   (61003,"Amay"),
   (63001,"Amel"),
   (92003,"Andenne"),
   (56001,"Anderlues"),
   (91005,"Anhée"),
...
   (25110,"Waterloo"),
   (25112,"Wavre"),
   (63084,"Welkenraedt"),
   (84075,"Wellin"),
   (91141,"Yvoir");

Il faut aussi noter qu'ils n'ont pas été fichus de donner des street_id distincts couvrant les trois régions. Il y a des duplicates du côté des 36000 et 37000 (!).
Il suffit maintenant d'injecter ces données dans notre DB :
SOURCE insert_streets.sql;
SOURCE insert_municipality.sql;

Notre DB est prête, on peut commencer à jouer avec...
mysql> SELECT * FROM street_table LIMIT 5;
+-----------+------------------------+-----------------+
| street_id | street_name            | municipality_id |
+-----------+------------------------+-----------------+
|   7700001 | Aux Tiennes            |           25005 |
|   7700002 | Avenue des Bouleaux    |           25005 |
|   7700003 | Avenue des Cerisiers   |           25005 |
|   7700004 | Avenue des Combattants |           25005 |
|   7700005 | Avenue des Pruniers    |           25005 |
+-----------+------------------------+-----------------+
5 rows in set (0.00 sec)

mysql> SELECT * FROM municipality_table ORDER BY municipality_name DESC LIMIT 5;
+-----------------+-------------------+
| municipality_id | municipality_name |
+-----------------+-------------------+
|           91141 | Yvoir             |
|           84075 | Wellin            |
|           63084 | Welkenraedt       |
|           25112 | Wavre             |
|           25110 | Waterloo          |
+-----------------+-------------------+
5 rows in set (0.00 sec)

mysql> 
Par exemple, je peux avoir la liste de commune ayant le moins ou le plus de rues :
mysql> SELECT count(*), m.municipality_name from street_table s LEFT JOIN municipality_table m USING (municipality_id) GROUP BY municipality_id ORDER BY count(*) LIMIT 10;
+----------+-------------------+
| count(*) | municipality_name |
+----------+-------------------+
|       54 | Martelange        |
|       58 | Hélécine          |
|       61 | Stoumont          |
|       62 | Gouvy             |
|       63 | Lincent           |
|       64 | Flobecq           |
|       65 | Verlaine          |
|       65 | Rumes             |
|       66 | Donceel           |
|       66 | Oreye             |
+----------+-------------------+
10 rows in set (0.38 sec)

mysql> SELECT count(*), m.municipality_name from street_table s LEFT JOIN municipality_table m USING (municipality_id) GROUP BY municipality_id ORDER BY count(*) DESC LIMIT 10;
+----------+----------------------------+
| count(*) | municipality_name          |
+----------+----------------------------+
|     2117 | Charleroi                  |
|     1998 | Liège                      |
|     1730 | Namur                      |
|     1280 | Mons                       |
|     1086 | Tournai                    |
|      923 | La Louvière                |
|      812 | Ottignies-Louvain-la-Neuve |
|      738 | Wavre                      |
|      677 | Mouscron                   |
|      649 | Ath                        |
+----------+----------------------------+
10 rows in set (0.10 sec)

mysql> 

Cette dernière requête étant :
SELECT count(*), m.municipality_name from street_table s
            LEFT JOIN municipality_table m USING (municipality_id)
            GROUP BY municipality_id
            ORDER BY count(*)
            DESC LIMIT 10;



lundi 18 novembre 2019

pps

Je me demandais à quel point les signaux PPS de deux modules GPS à quelques euros étaient synchronisé...
Trigger sur le channel-1. Il y a du jitter sur l'autre trace.
La réception n'est pas idéale à l'endroit où j'ai effectué la mesure, sur une terrasse entre les maisons. Trigger sur le channel-2. Idem.
Un autre point à noter est que les modules semblent interférer entre eux. J'ai utilisé deux alimentations différentes et j'ai dû en démarrer un avant l'autre... Une question intéressante serait de savoir si le jitter diminue quand on a une meilleure réception avec plus de satellites visibles en même temps.

dimanche 15 septembre 2019

NanoVNA

Un 'Vector Network Analyzer' à 40.00 euros sur eBay... Ce petit bidule 'stand alone' permet de mesurer la réflexion (S11) et la transmission de signaux entre 50 kHz et 900 MHz.

Mesure en transmission d'un filtre 'SAW' passe-bande 433 MHz.

Le nanoVNA est équipé d'un petit 'touchscreen' mais on peut également communiquer via le port USB (USBc). Sur Linux, on peut utiliser 'minicom -D /dev/ttyACM0', par exemple. Et sur Windows, 'putty'.

L'interface est bien conçue. CH0 mesure la réflexion (S11) et CH1, la transmission (S21) en valeur imaginaires. On définit la plage de fréquences (sweep) qui sera divisée en 101 points. On peut afficher 4 traces. Pour chaque trace, on peut choisir le canal, le type d'affichage (logmag, smith, phase), etc...

Via le port USB, on a une console série dans laquelle on peut taper des commandes et afficher les résultats. Ainsi, par exemple, on peut facilement obtenir les mesures en transmission entre 400 et 500 MHz.
ch> sweep 400000000 500000000 101
ch> data 1
-0.004106074 0.001582601
-0.004401572 0.001613250
-0.004341298 0.001819048
-0.004164417 0.001013602
-0.006044433 0.001735574
...
Un petit script PERL vite écrit, convertit les valeurs en dB :
#!/usr/bin/perl
$f0 = 400.0; $f1 = 500.0;
$inc = ($f1 - $f0) / 101;
$df = 0;
while ($line = <STDIN>)
 {
 chomp $line;
 ($a, $b) = split /\s/,$line;
 $mag = sqrt ($a*$a + $b*$b);
 if ($mag == 0) {$mag = 0.001;}
 printf("%6.2f\t%f\n", $f0 + $df, 20*log($mag)/log(10.0));
 $df += $inc;
 }
et on obtient le même graphe que l'écran du nanoVNA avec gnuplot :
gnuplot <<END
set term png size 1200,800
set output 'S21-logmag.png'
set title 'S21-logmag of a "NMRF FBp - 433s" filter'
set xlabel 'Frequency (MHz)'
set ylabel 'S21 dB'
plot 'S21-logmag' with lines
END
Graphe calculé à partir des données obtenues via l'interface série.

C'est magique!

Quelques exemples pour la route :
ch>
ch> help
Commands: help exit info echo systime threads reset freq offset time dac saveconfig
 clearconfig data dump frequencies port stat sweep test touchcal touchtest pause
 resume cal save recall trace marker edelay 

ch> info
Kernel:       4.0.0
Compiler:     GCC 5.4.1 20160919
Architecture: ARMv6-M
Core Variant: Cortex-M0
Port Info:    Preemption through NMI
Platform:     STM32F072xB Entry Level Medium Density devices
Board:        NanoVNA
Build time:   Aug  2 2019 - 16:40:01

ch> trace
0 LOGMAG CH0 1.000000000 7.000000000
1 LOGMAG CH1 1.000000000 7.000000000
2 SMITH CH0 1.000000000 0.000000000
3 PHASE CH1 1.000000000 4.000000000
ch> trace 99
trace {0|1|2|3|all} [logmag|phase|smith|linear|delay|swr|off] [src]
ch> trace 1 phase 0                                                                
ch> trace                                                                          
0 LOGMAG CH0 1.000000000 7.000000000                                               
1 PHASE CH0 1.000000000 4.000000000                                                
2 SMITH CH0 1.000000000 0.000000000                                                
3 PHASE CH1 1.000000000 4.000000000
                                               
ch> sweep                                                                          
400000000 500000000 101                                                            
ch> sweep 420000000 440000000 101                                                  
ch> sweep                                                                          
420000000 440000000 101                                            
ch>

ch> data 1                                                                         
-0.004733881 0.003933506                                                           
-0.004605576 0.004513134                                                           
-0.004246967 0.003887655                                                           
-0.004040227 0.003405604                                                           
-0.003938771 0.004323835                                                           
-0.004104804 0.004129481
...
data0 pour les mesures de réflexion, data 1 pour la transmission. Après, ce sont les données de calibration.

Quelques liens