1 Commits
Author SHA1 Message Date
magnum88 36e2955ee2 first commit 2022-08-24 08:33:23 +02:00
48 changed files with 563 additions and 1523 deletions
+34 -193
View File
@@ -1,4 +1,4 @@
RadioSonde Version 0.9.2 RadioSonde Version 0.9.0
============================ ============================
<img src="http://xavier.debert.free.fr/RS/TTGO.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO2.jpg" width="50%"> <img src="http://xavier.debert.free.fr/RS/TTGO.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO2.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO3.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO4.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO5.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO6.jpg" width="50%"> <img src="http://xavier.debert.free.fr/RS/TTGO3.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO4.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO5.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO6.jpg" width="50%">
@@ -12,202 +12,49 @@
Projet basé sur le travail de DL9RDZ Projet basé sur le travail de DL9RDZ
==================================== ====================================
Pour TTGO LORA 32 esp32 pico D4 <br> Pour TTGO LORA 32 esp32 pico D4
Décodage de RadioSonde RS41/92 and DFM06/09/17 et M10+/20 Décodage de RadioSonde RS41 and DFM06/09 et M10
Attention à la version de votre TTGO! <br> Attention à la version de votre TTGO!
vous devez modifier dans config.txt, le port de l'écran OLED <br> vous devez modifier dans config.txt, le port de l'écran OLED
- TTGO v1: SDA=4 SCL=15, RST=16 <br> - TTGO v1: SDA=4 SCL=15, RST=16
- TTGO v2: SDA=21 SCL=22, RST=16 <br>
puis pour le GPS:<br>
- Lilygo esp32 GPS pin 34 Rx, 12 Tx<br>
puis Buzzer<br>
- par defaut 12 si pas de GPS si non pin 25<br>
## Version en production 0.9.1 devel 0.9.2
## 0.9.2
Correctif Dash SiteWeb config
ADD API SondeHub Thank's DL9RDZ
## 0.9.1
Corection RS41 <br>
Correction DFM 06/09 <br>
Add DFM17 <br>
Correction for all trame recived for M10 and M20 1000ms to 1512ms,<br>
Correction formulaire QRG, and end RS no save
## 0.9.0
Add M20
## 0.8.8
Add M10+ <br>
Add Temps restant avant impacte au sol si 99: 0. 0 soit le balon de la sonde n a pas encore éclaté, <br>
ou les informations ne sont pas disponible actuellement <br>
Add Test Buzzer au démarrage "Arche Perdu" Lol pour des chasseurs de sonde!<br>
Compatible Lilygo esp32 GPS inboard pin 34 Rx, 12 Tx
## 0.8.7
correction bug Buzzer Off->On->Off <br>
Add GainLNA RX SX1278FSK on Web config paramètre <br>
Add update OTA Os + DataWeb <br>
correction bugs sondmap.html <br>
correction text upgrade Os et DataWeb <br>
correction texte boussole S et N <br>
correction bugs distance 4928Km si lat et lon =0 erroné <br>
correction bugs fonction Vbat <br>
Add Telemetry width export data.csv <br>
Suppression µSD incompatible avec pin SX1278FSK et SPI <br>
Add transfert Telemetry To µSD on put SD automatic
## 0.8.5
Evolution majeur du système <br>
affichage du pourcentage de la batterie en mode scanning <br>
création d'une fenetre Batterie, Boussole <br>
suppresion lib et code TFT <br>
création Azimute, elevation correction de Bugs majeur , mineur <br>
Ajout fonction Smetre, Buzzer, QTH, Gps on off ... <br>
mise à jour OTA <br>
trop de modification pour toutes les expliciter!
## 0.8.1
modification de la partie Web
## 0.8.0
travail de refonte et réécriture du code
-------------------------------------------------------------------------------
## Les Boutons optionnel à ajouter(souder)
sur les GPIO 1002 et 1004 <br>
attention:
+3.3V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms
- appuie court <1.5 seconds <br>
- appuie double court 0.5 seconds <br>
- appuie moyen 2-4 seconds <br>
- appuie long >5 seconds
## Buzzer optionnel à ajouter(souder)
sur les GPIO 25 ou 12 suivant le modèl
GPIO --[ BUZZER ]---/
## Wifi configuration
Au démarrage, si aucune connexion possible au wifi paramètré, il monte un Wifi AP<br>
le SSID et mot de passe par défaut est: <b>Radiosonde</b> <br>
en mode AP, il doit être en 192.168.4.1, <br>
mais vous avez aussi la possibilité de mettre http://radiosonde.local dans n'importe quel Wifi
connecté.
## Mode Scanne
Le fichier qrg.txt contient la liste par défaut des cannaux.
pour y configurer les noms, fréquences et mode [M=M10, 6=DFM06, 9=DFM09 et 4=RS41]
## Mode Réception
En réception, une seul fréquence est décodé, les infos de la sonde (ID, GPS, RSSI, ...)
seront affichées dans plusieur fenetre à choisir ( 0 à 6) à configurer dans la page Web
rubrique config.
In receiving mode, a single frequency will be decoded, and sonde info (ID, GPS
coordinates, RSSI) will be displayed. The bar above the IP address indicates,
for the last 18 frames, if reception was successfull (|) or failed (.)
A DOUBLE press will switch to scanning mode.
A SHORT press will switch to the next channel in channels.txt
## Mode Analyseur
Le mode analyseur de spectre (400..406 MHz) est affiché (chaque ligne == 50 kHz)
Pour les cartes TTGO sans bouton configurable, il y a un nouveau paramètre dans config.txt:
- spectrum=10 // 0=off / 1-99 nombre de seconds pour afficher l'analyseur
- timer=1 // 0=off / 1= afficher le compte à rebours du spectre dans l'affichage du spectre
- marker=1 // 0=off / 1= afficher la fréquence dans l'affichage du spectre
## Setup
voir Setup.md pour l'installation!
73
Xavier
RadioSonde Version 0.9.1
============================
<img src="http://xavier.debert.free.fr/RS/TTGO.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO2.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO3.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO4.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO5.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO6.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO7.jpg" width="50%"><img src="http://xavier.debert.free.fr/RS/TTGO8.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/Web4.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web3.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web2.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web5.png" width="20%"><img src="http://xavier.debert.free.fr/RS/Web6.png" width="20%">
<img src="http://xavier.debert.free.fr/RS/Web7.png" width="20%">
Projet basé sur le travail de DL9RDZ
====================================
Pour TTGO LORA 32 esp32 pico D4 <br>
Décodage de RadioSonde RS41/92 and DFM06/09/17 et M10+/20
Attention à la version de votre TTGO! <br>
vous devez modifier dans config.txt, le port de l'écran OLED <br>
- TTGO v1: SDA=4 SCL=15, RST=16 <br>
- TTGO v2: SDA=21 SCL=22, RST=16 - TTGO v2: SDA=21 SCL=22, RST=16
## Version en production 0.9.1 devel 0.9.2 ## Version en production 0.9.0 devel 0.9.1
## 0.9.1
Corection RS41 <br>
Correction DFM 06/09 <br>
Add DFM17 <br>
Correction for all trame recived for M10 and M20 1000ms to 1512ms,<br>
Correction formulaire QRG, and end RS no save
## 0.9.0 ## 0.9.0
Add M20 Add M20
## 0.8.8 ## 0.8.8
Add M10+ <br> Add M10+
Add Temps restant avant impacte au sol si 99: 0. 0 soit le balon de la sonde n a pas encore éclaté, <br> Add Temps restant avant impacte au sol si 99: 0. 0 soit le balon de la sonde n a pas encore éclaté, ou les informations ne sont pas disponible actuellement
ou les informations ne sont pas disponible actuellement <br> Add Test Buzzer au démarrage "Arche Perdu" Lol pour des chasseurs de sonde!
Add Test Buzzer au démarrage "Arche Perdu" Lol pour des chasseurs de sonde!<br>
Compatible Lilygo esp32 GPS inboard pin 34 Rx, 12 Tx Compatible Lilygo esp32 GPS inboard pin 34 Rx, 12 Tx
## 0.8.7 ## 0.8.7
correction bug Buzzer Off->On->Off <br> correction bug Buzzer Off->On->Off
Add GainLNA RX SX1278FSK on Web config paramètre <br> Add GainLNA RX SX1278FSK on Web config paramètre
Add update OTA Os + DataWeb <br> Add update OTA Os + DataWeb
correction bugs sondmap.html <br> correction bugs sondmap.html
correction text upgrade Os et DataWeb <br> correction text upgrade Os et DataWeb
correction texte boussole S et N <br> correction texte boussole S et N
correction bugs distance 4928Km si lat et lon =0 erroné <br> correction bugs distance 4928Km si lat et lon =0 erroné
correction bugs fonction Vbat <br> correction bugs fonction Vbat
Add Telemetry width export data.csv <br> Add Telemetry width export data.csv
Suppression µSD incompatible avec pin SX1278FSK et SPI <br> Suppression µSD incompatible avec pin SX1278FSK et SPI
Add transfert Telemetry To µSD on put SD automatic Add transfert Telemetry To µSD on put SD automatic
## 0.8.5 ## 0.8.5
Evolution majeur du système <br> Evolution majeur du système
affichage du pourcentage de la batterie en mode scanning <br> affichage du pourcentage de la batterie en mode scanning
création d'une fenetre Batterie, Boussole <br> création d'une fenetre Batterie, Boussole
suppresion lib et code TFT <br> suppresion lib et code TFT
création Azimute, elevation correction de Bugs majeur , mineur <br> création Azimute, elevation correction de Bugs majeur , mineur
Ajout fonction Smetre, Buzzer, QTH, Gps on off ... <br> Ajout fonction Smetre, Buzzer, QTH, Gps on off ...
mise à jour OTA <br> mise à jour OTA
trop de modification pour toutes les expliciter! trop de modification pour toutes les expliciter!
## 0.8.1 ## 0.8.1
@@ -221,26 +68,21 @@ travail de refonte et réécriture du code
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
## Les Boutons optionnel à ajouter(souder) ## Les Boutons optionnel à ajouter(souder)
sur les GPIO 1002 et 1004 <br> sur les GPIO 1002 et 1004
attention: attention:
+3.3V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms +3.3V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms
- appuie court <1.5 seconds <br> - appuie court <1.5 seconds
- appuie double court 0.5 seconds <br> - appuie double court 0.5 seconds
- appuie moyen 2-4 seconds <br> - appuie moyen 2-4 seconds
- appuie long >5 seconds - appuie long >5 seconds
## Buzzer optionnel à ajouter(souder)
sur les GPIO 25 ou 12 suivant le modèl
GPIO --[ BUZZER ]---/
## Wifi configuration ## Wifi configuration
Au démarrage, si aucune connexion possible au wifi paramètré, il monte un Wifi AP<br> Au démarrage, si aucune connexion possible au wifi paramètré, il monte un Wifi AP
le SSID et mot de passe par défaut est: <b>Radiosonde</b> <br> le SSID et mot de passe par défaut est: Radiosonde
en mode AP, il doit être en 192.168.4.1, <br> en mode AP, il doit être en 192.168.4.1,
mais vous avez aussi la possibilité de mettre http://radiosonde.local dans n'importe quel Wifi mais vous avez aussi la possibilité de mettre http://radiosonde.local dans n'importe quel Wifi
connecté. connecté.
@@ -280,4 +122,3 @@ Xavier
+81 -511
View File
@@ -5,6 +5,8 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <SPIFFS.h> #include <SPIFFS.h>
//#include <U8x8lib.h>
//#include <U8g2lib.h>
#include <SPI.h> #include <SPI.h>
#include <Update.h> #include <Update.h>
#include <ESPmDNS.h> #include <ESPmDNS.h>
@@ -22,8 +24,6 @@
#include "spiffs_sd.h" #include "spiffs_sd.h"
#include <APRS-IS.h>
int e; int e;
enum MainState { ST_DECODER, ST_SPECTRUM, ST_WIFISCAN, ST_UPDATE, ST_TOUCHCALIB }; enum MainState { ST_DECODER, ST_SPECTRUM, ST_WIFISCAN, ST_UPDATE, ST_TOUCHCALIB };
static MainState mainState = ST_WIFISCAN; // ST_WIFISCAN; static MainState mainState = ST_WIFISCAN; // ST_WIFISCAN;
@@ -42,20 +42,22 @@ String updateBinM = "/RS/radiosonde/update.ino.bin";
String updateBinD = "/RS/radiosondedevel/update.ino.bin"; String updateBinD = "/RS/radiosondedevel/update.ino.bin";
String *updateBin = &updateBinM; String *updateBin = &updateBinM;
String updateDataWeb = "/RS/dataweb/data.tar"; String updateDataWeb = "/RS/dataweb/index.html.txt";
String updateDataWeb2 = "/RS/dataweb/sondemap.html.txt";
String updateDataWeb3 = "/RS/dataweb/config.txt";
String updateDataWeb4 = "/RS/dataweb/screens.txt";
String updateDataWeb5 = "/RS/dataweb/style.css.txt";
String updateDataWeb6 = "/RS/dataweb/qrg.txt";
String updateDataWeb7 = "/RS/dataweb/networks.txt";
String updateDataWeb8 = "/RS/dataweb/fontawesome-webfont.eot.txt";
String updateDataWeb9 = "/RS/dataweb/fontawesome-webfont.woff.txt";
#define LOCALUDPPORT 14580 #define LOCALUDPPORT 9002
boolean connected = false; boolean connected = false;
WiFiUDP udp; WiFiUDP udp;
WiFiClient client; WiFiClient client;
//SondeHub
#define SONDEHUB_STATION_UPDATE_TIME (60*60*1000) // 60 min
#define SONDEHUB_MOBILE_STATION_UPDATE_TIME (30*1000) // 30 sec
WiFiClient shclient; // Sondehub v2
unsigned long time_last_update = 0;
// KISS over TCP für communicating with APRSdroid // KISS over TCP für communicating with APRSdroid
WiFiServer tncserver(14580); WiFiServer tncserver(14580);
WiFiClient tncclient; WiFiClient tncclient;
@@ -162,10 +164,9 @@ void setupChannelList() {
sonde.clearSonde(); sonde.clearSonde();
Serial.println("Reading channel config:"); Serial.println("Reading channel config:");
while (file.available()) { while (file.available()) {
//String line = readLine(file); //file.readStringUntil('\n'); String line = readLine(file); //file.readStringUntil('\n');
String line = file.readStringUntil('\n');
String sitename; String sitename;
//if (!file.available()) break; if (!file.available()) break;
if (line[0] == '#') continue; if (line[0] == '#') continue;
char *space = strchr(line.c_str(), ' '); char *space = strchr(line.c_str(), ' ');
if (!space) continue; if (!space) continue;
@@ -183,9 +184,6 @@ void setupChannelList() {
else if (space[1] == '6') { else if (space[1] == '6') {
type = STYPE_DFM06; type = STYPE_DFM06;
} }
else if (space[1] == 'D') {
type = STYPE_DFM;
}
else if (space[1] == 'M') { else if (space[1] == 'M') {
type = STYPE_M10; type = STYPE_M10;
} }
@@ -210,55 +208,25 @@ void setupChannelList() {
const char *createQRGForm() { const char *createQRGForm() {
char *ptr = message; char *ptr = message;
strcpy(ptr, "<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">"); strcpy(ptr, "<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\"></head><body><form action=\"qrg.html\" method=\"post\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>");
strcat(ptr, "<script type=\"text/javascript\">"
"let stypes=new Map();"
"stypes.set('4', 'RS41');"
"stypes.set('R', 'RS92');"
"stypes.set('9', 'DFM9');"
"stypes.set('6', 'DFM6');"
"stypes.set('D', 'DFM All');"
"stypes.set('M', 'M10');"
"stypes.set('2', 'M20');"
"function prep() {"
" var stlist=document.querySelectorAll(\"input.stype\");"
" for(txt of stlist){"
" var val=txt.getAttribute('value'); var nam=txt.getAttribute('name'); "
" var sel=document.createElement('select');"
" sel.setAttribute('name',nam);"
" for(stype of stypes) { "
" var opt=document.createElement('option');"
" opt.value=stype[0];"
" opt.innerHTML=stype[1];"
" if(stype[0]==val) { opt.setAttribute('selected','selected'); }"
" sel.appendChild(opt);"
" } txt.replaceWith(sel); } } "
" window.onload = prep; "
"</script>");
strcat(ptr, "</head><body><form action=\"qrg.html\" method=\"post\"><table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>");
for (int i = 0; i < sonde.config.maxsonde; i++) { for (int i = 0; i < sonde.config.maxsonde; i++) {
//String s = sondeTypeSelect(i >= sonde.nSonde ? 2 : sonde.sondeList[i].type); String s = sondeTypeSelect(i >= sonde.nSonde ? 2 : sonde.sondeList[i].type);
String site = sonde.sondeList[i].launchsite; String site = sonde.sondeList[i].launchsite;
sprintf(ptr + strlen(ptr), "<tr><td>%d</td><td><input name=\"A%d\" type=\"checkbox\" %s/></td>" sprintf(ptr + strlen(ptr), "<tr><td>%d</td><td><input name=\"A%d\" type=\"checkbox\" %s/></td>"
"<td><input name=\"F%d\" type=\"text\" value=\"%3.3f\"></td>" "<td><input name=\"F%d\" type=\"text\" value=\"%3.3f\"></td>"
"<td><input name=\"S%d\" type=\"text\" value=\"%s\"></td>" "<td><input name=\"S%d\" type=\"text\" value=\"%s\"></td>"
//"<td><select name=\"T%d\">%s</select></td>", "<td><select name=\"T%d\">%s</select></td>",
"<td><input class='stype' name='T%d' value='%c'>",
i + 1, i + 1,
i + 1, (i < sonde.nSonde && sonde.sondeList[i].active) ? "checked" : "", i + 1, (i < sonde.nSonde && sonde.sondeList[i].active) ? "checked" : "",
i + 1, i >= sonde.nSonde ? 400.000 : sonde.sondeList[i].freq, i + 1, i >= sonde.nSonde ? 400.000 : sonde.sondeList[i].freq,
i + 1, i >= sonde.nSonde ? " " : sonde.sondeList[i].launchsite, i + 1, i >= sonde.nSonde ? " " : sonde.sondeList[i].launchsite,
i + 1, i >= sonde.nSonde ? 2 : sondeTypeChar[sonde.sondeList[i].type] ); i + 1, s.c_str());
//i + 1, s.c_str());
} }
strcat(ptr, "</table><input type=\"submit\" value=\"Update\"/></form></body></html>"); strcat(ptr, "</table><input type=\"submit\" value=\"Update\"/></form></body></html>");
Serial.printf("QRG form: size=%d bytes\n", strlen(message)); Serial.printf("QRG form: size=%d bytes\n",strlen(message));
return message; return message;
} }
const char *handleQRGPost(AsyncWebServerRequest *request) { const char *handleQRGPost(AsyncWebServerRequest *request) {
char label[10]; char label[10];
// parameters: a_i, f_1, t_i (active/frequency/type) // parameters: a_i, f_1, t_i (active/frequency/type)
@@ -295,7 +263,7 @@ const char *handleQRGPost(AsyncWebServerRequest *request) {
const char *tstr = tstring.c_str(); const char *tstr = tstring.c_str();
const char *sstr = sstring.c_str(); const char *sstr = sstring.c_str();
Serial.printf("Processing a=%s, f=%s, t=%s, site=%s\n", active ? "YES" : "NO", fstr, tstr, sstr); Serial.printf("Processing a=%s, f=%s, t=%s, site=%s\n", active ? "YES" : "NO", fstr, tstr, sstr);
char typech = tstr[0]; char typech = (tstr[2] == '4' ? '4' : tstr[2] == '9' ? 'R' : tstr[1] == '1' ? 'M' : tstr[1] == '2' ? '2' : tstr[3]); // a bit ugly
file.printf("%3.3f %c %c %s\n", atof(fstr), typech, active ? '+' : '-', sstr); file.printf("%3.3f %c %c %s\n", atof(fstr), typech, active ? '+' : '-', sstr);
} }
file.close(); file.close();
@@ -345,6 +313,7 @@ void setupWifiList() {
} }
} }
const char *createWIFIForm() { const char *createWIFIForm() {
char *ptr = message; char *ptr = message;
char tmp[4]; char tmp[4];
@@ -408,7 +377,7 @@ void addSondeStatus(char *ptr, int i)
strcat(ptr, "<table>"); strcat(ptr, "<table>");
sprintf(ptr + strlen(ptr), "<tr><td id=\"sfreq\">%3.3f MHz, Type: %s</td><tr><td>ID: %s", s->freq, sondeTypeStr[s->type], sprintf(ptr + strlen(ptr), "<tr><td id=\"sfreq\">%3.3f MHz, Type: %s</td><tr><td>ID: %s", s->freq, sondeTypeStr[s->type],
s->validID ? s->id : "<?""?>"); s->validID ? s->id : "<?""?>");
if (s->validID && (s->type == STYPE_DFM || s->type == STYPE_DFM06 || s->type == STYPE_DFM09 || s->type == STYPE_M10 || s->type == STYPE_M20)) { if (s->validID && (s->type == STYPE_DFM06 || s->type == STYPE_DFM09 || s->type == STYPE_M10 || s->type == STYPE_M20)) {
sprintf(ptr + strlen(ptr), " (ser: %s)", s->ser); sprintf(ptr + strlen(ptr), " (ser: %s)", s->ser);
} }
sprintf(ptr + strlen(ptr), "</td></tr><tr><td>QTHSonde: %.6f,%.6f h=%.0fm</td></tr>\n", s->lat, s->lon, s->alt); sprintf(ptr + strlen(ptr), "</td></tr><tr><td>QTHSonde: %.6f,%.6f h=%.0fm</td></tr>\n", s->lat, s->lon, s->alt);
@@ -421,11 +390,9 @@ void addSondeStatus(char *ptr, int i)
sprintf(ptr + strlen(ptr), "<tr><td>Burst-KT=%d Launch-KT=%d Countdown=%d (vor %ds)</td></tr>\n", sprintf(ptr + strlen(ptr), "<tr><td>Burst-KT=%d Launch-KT=%d Countdown=%d (vor %ds)</td></tr>\n",
s->burstKT, s->launchKT, s->countKT, ((uint16_t)s->frame - s->crefKT)); s->burstKT, s->launchKT, s->countKT, ((uint16_t)s->frame - s->crefKT));
} }
sprintf(ptr + strlen(ptr), "<tr><td>"); sprintf(ptr + strlen(ptr), "<tr><td><a target=\"_empty\" href=\"geo:%.6f,%.6f\">GEO-App</a> - ", s->lat, s->lon);
sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://www.google.fr/maps/dir//%.6f,%.6f\">G-MAPS</a> - ", s->lat, s->lon);
sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://tracker.sondehub.org/?sondehub=1#!mt=osm&mz=8&qm=6_hours&mc=%.6f,%.6f&q=RS_%s\">Sondehub</a> - ", s->lat, s->lon, s->id); sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://tracker.sondehub.org/?sondehub=1#!mt=osm&mz=8&qm=6_hours&mc=%.6f,%.6f&q=RS_%s\">Sondehub</a> - ", s->lat, s->lon, s->id);
sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://www.openstreetmap.org/?mlat=%.6f&mlon=%.6f&zoom=14\">OSM</a> - ", s->lat, s->lon); sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://www.openstreetmap.org/?mlat=%.6f&mlon=%.6f&zoom=14\">OSM</a></td></tr>", s->lat, s->lon);
sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://www.google.com/maps/search/?api=1&query=%.6f,%.6f\">Google</a></td></tr>", s->lat, s->lon);
strcat(ptr, "</table><p/>\n"); strcat(ptr, "</table><p/>\n");
} }
@@ -471,7 +438,7 @@ struct st_configitems config_list[] = {
/* General config settings */ /* General config settings */
{"", "Software configuration", -5, NULL}, {"", "Software configuration", -5, NULL},
{"wifi", "Wifi mode (0[off]/1[On]/2[Apmode]/3[Scan])", 0, &sonde.config.wifi}, {"wifi", "Wifi mode (0/1/2/3)", 0, &sonde.config.wifi},
{"debug", "Debug mode (0/1)", 0, &sonde.config.debug}, {"debug", "Debug mode (0/1)", 0, &sonde.config.debug},
{"maxsonde", "Maxsonde", 0, &sonde.config.maxsonde}, {"maxsonde", "Maxsonde", 0, &sonde.config.maxsonde},
{"display", "Display screens (scan,default,...)", -6, sonde.config.display}, {"display", "Display screens (scan,default,...)", -6, sonde.config.display},
@@ -497,7 +464,7 @@ struct st_configitems config_list[] = {
{"call", "Call", 8, sonde.config.call}, {"call", "Call", 8, sonde.config.call},
{"passcode", "Passcode", 8, sonde.config.passcode}, {"passcode", "Passcode", 8, sonde.config.passcode},
/* KISS tnc settings */ /* KISS tnc settings */
{"kisstnc.active", "KISS TNC (1 active) (needs reboot)", 0, &sonde.config.kisstnc.active}, {"kisstnc.active", "KISS TNC (port 14590) (needs reboot)", 0, &sonde.config.kisstnc.active},
{"kisstnc.idformat", "KISS TNC ID Format", -2, &sonde.config.kisstnc.idformat}, {"kisstnc.idformat", "KISS TNC ID Format", -2, &sonde.config.kisstnc.idformat},
/* AXUDP settings */ /* AXUDP settings */
{"axudp.active", "AXUDP active", -3, &sonde.config.udpfeed.active}, {"axudp.active", "AXUDP active", -3, &sonde.config.udpfeed.active},
@@ -511,17 +478,6 @@ struct st_configitems config_list[] = {
{"tcp.port", "APRS TCP Port", 0, &sonde.config.tcpfeed.port}, {"tcp.port", "APRS TCP Port", 0, &sonde.config.tcpfeed.port},
{"tcp.idformat", "DFM ID Format", -2, &sonde.config.tcpfeed.idformat}, {"tcp.idformat", "DFM ID Format", -2, &sonde.config.tcpfeed.idformat},
{"tcp.highrate", "Rate limit", 0, &sonde.config.tcpfeed.highrate}, {"tcp.highrate", "Rate limit", 0, &sonde.config.tcpfeed.highrate},
/* Sondehub v2 settings */
{"", "Sondehub v2 settings", -5, NULL},
{"sondehub.active", "Sondehub reporting active", 0, &sonde.config.sondehub.active},
{"sondehub.chase", "Sondehub chase location active", 0, &sonde.config.sondehub.chase},
{"sondehub.host", "Sondehub host", 63, &sonde.config.sondehub.host},
{"sondehub.callsign", "Callsign", 63, &sonde.config.sondehub.callsign},
{"sondehub.lat", "Latitude", 19, &sonde.config.sondehub.lat},
{"sondehub.lon", "Longitude", 19, &sonde.config.sondehub.lon},
{"sondehub.alt", "Altitude", 19, &sonde.config.sondehub.alt},
{"sondehub.antenna", "Antenna", 63, &sonde.config.sondehub.antenna},
{"sondehub.email", "Sondehub email", 63, &sonde.config.sondehub.email},
/* Hardware dependeing settings */ /* Hardware dependeing settings */
{"", "Hardware configuration (requires reboot)", -5, NULL}, {"", "Hardware configuration (requires reboot)", -5, NULL},
{"disptype", "Display type (0=OLED/SSD1306, 1=TFT/ILI9225, 2=OLED/SH1106)", 0, &sonde.config.disptype}, {"disptype", "Display type (0=OLED/SSD1306, 1=TFT/ILI9225, 2=OLED/SH1106)", 0, &sonde.config.disptype},
@@ -530,7 +486,7 @@ struct st_configitems config_list[] = {
{"oled_scl", "OLED SCL CLK", 0, &sonde.config.oled_scl}, {"oled_scl", "OLED SCL CLK", 0, &sonde.config.oled_scl},
{"oled_rst", "OLED RST (needs reboot)", 0, &sonde.config.oled_rst}, {"oled_rst", "OLED RST (needs reboot)", 0, &sonde.config.oled_rst},
{"oled_orient", "Oled orientation default 1, OLED flip: 3", 0, &sonde.config.oled_orient}, {"oled_orient", "Oled orientation default 1, OLED flip: 3", 0, &sonde.config.oled_orient},
{"button_pin", "Button input port (38)", -4, &sonde.config.button_pin}, {"button_pin", "Button input port", -4, &sonde.config.button_pin},
{"button2_pin", "Button 2 input port", -4, &sonde.config.button2_pin}, {"button2_pin", "Button 2 input port", -4, &sonde.config.button2_pin},
{"button2_axp", "Use AXP192 PWR as Button 2", 0, &sonde.config.button2_axp}, {"button2_axp", "Use AXP192 PWR as Button 2", 0, &sonde.config.button2_axp},
{"touch_thresh", "Touch button threshold<br>(0 for calib mode)", 0, &sonde.config.touch_thresh}, {"touch_thresh", "Touch button threshold<br>(0 for calib mode)", 0, &sonde.config.touch_thresh},
@@ -547,7 +503,7 @@ struct st_configitems config_list[] = {
{"vbatmin", "VBat Min", 5, sonde.config.vbatmin}, {"vbatmin", "VBat Min", 5, sonde.config.vbatmin},
{"telemetryOn", "Telemetry (0=Disabled, 1 Enable)", 0, &sonde.config.telemetryOn}, {"telemetryOn", "Telemetry (0=Disabled, 1 Enable)", 0, &sonde.config.telemetryOn},
{"buzzerOn", "Buzzer (0=Disabled, 1 Enable)", 0, &sonde.config.buzzerOn}, {"buzzerOn", "Buzzer (0=Disabled, 1 Enable)", 0, &sonde.config.buzzerOn},
{"buzzerPort", "Buzzer Port (25)", 0, &sonde.config.buzzerPort}, {"buzzerPort", "Buzzer Port (12)", 0, &sonde.config.buzzerPort},
{"buzzerFreq", "Buzzer Frequency (700)", 0, &sonde.config.buzzerFreq}, {"buzzerFreq", "Buzzer Frequency (700)", 0, &sonde.config.buzzerFreq},
{"dbsmetre", "dB = 0 / Smetre =1)", 0, &sonde.config.dbsmetre}, {"dbsmetre", "dB = 0 / Smetre =1)", 0, &sonde.config.dbsmetre},
{"degdec", "Lat,Lon Decimal =0 ou Degres =1)", 0, &sonde.config.degdec}, {"degdec", "Lat,Lon Decimal =0 ou Degres =1)", 0, &sonde.config.degdec},
@@ -1150,11 +1106,7 @@ char buffer[85];
MicroNMEA nmea(buffer, sizeof(buffer)); MicroNMEA nmea(buffer, sizeof(buffer));
int lastCourse = 0; int lastCourse = 0;
void unkHandler(const MicroNMEA& nmea) {
/// Arrg. MicroNMEA changes type definition... so lets auto-infer type
template<typename T>
//void unkHandler(const MicroNMEA& nmea) {
void unkHandler(T nmea) {
if (strcmp(nmea.getMessageID(), "VTG") == 0) { if (strcmp(nmea.getMessageID(), "VTG") == 0) {
const char *s = nmea.getSentence(); const char *s = nmea.getSentence();
while (*s && *s != ',') s++; while (*s && *s != ',') s++;
@@ -1164,7 +1116,6 @@ void unkHandler(T nmea) {
Serial.printf("Course update: %d\n", lastCourse); Serial.printf("Course update: %d\n", lastCourse);
} }
} }
void gpsTask(void *parameter) { void gpsTask(void *parameter) {
nmea.setUnknownSentenceHandler(unkHandler); nmea.setUnknownSentenceHandler(unkHandler);
@@ -1188,31 +1139,10 @@ void gpsTask(void *parameter) {
} }
} }
#define UBX_SYNCH_1 0xB5
#define UBX_SYNCH_2 0x62
uint8_t ubx_set9k6[]={UBX_SYNCH_1, UBX_SYNCH_2, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00, 0x80, 0x25, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8D, 0x8F};
uint8_t ubx_factorydef[]={UBX_SYNCH_1, UBX_SYNCH_2, 0x06, 0x09, 13, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0x17, 0x8A };
void initGPS() { void initGPS() {
if (sonde.config.gps_rxd < 0) return; // GPS disabled if (sonde.config.gps_rxd < 0) return; // GPS disabled
Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd); Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
if (sonde.config.gps_txd >= 0) { // TX enable, thus try setting baud to 9600 and do a factory reset
Serial.println("Trying to reset GPS...");
Serial2.begin(115200, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
Serial2.write(ubx_set9k6, sizeof(ubx_set9k6));
delay(100);
Serial2.begin(38400, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
Serial2.write(ubx_set9k6, sizeof(ubx_set9k6));
delay(100);
Serial2.begin(19200, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
Serial2.write(ubx_set9k6, sizeof(ubx_set9k6));
delay(100);
Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
Serial2.write(ubx_factorydef, sizeof(ubx_factorydef));
delay(1000);
} else {
Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
}
xTaskCreate( gpsTask, "gpsTask", xTaskCreate( gpsTask, "gpsTask",
5000, /* stack size */ 5000, /* stack size */
NULL, /* paramter */ NULL, /* paramter */
@@ -1530,7 +1460,6 @@ void setup()
return; return;
} }
Serial.println("Reading initial configuration"); Serial.println("Reading initial configuration");
setupConfigData(); // configuration must be read first due to OLED ports!!! setupConfigData(); // configuration must be read first due to OLED ports!!!
@@ -1737,10 +1666,10 @@ const int melodie[][3] = { {4, 2, 2}, {5, 2, 1}, {7, 2, 3}, {0, 3, 6},
setupChannelList(); setupChannelList();
#if 0 #if 0
sonde.clearSonde(); sonde.clearSonde();
sonde.addSonde(402.800, STYPE_RS41); sonde.addSonde(402.700, STYPE_RS41);
sonde.addSonde(402.000, STYPE_M10); sonde.addSonde(405.700, STYPE_RS41);
sonde.addSonde(404.800, STYPE_RS41); sonde.addSonde(405.900, STYPE_RS41);
sonde.addSonde(403.000, STYPE_DFM09); sonde.addSonde(403.450, STYPE_DFM09);
Serial.println("No channel config file, using defaults!"); Serial.println("No channel config file, using defaults!");
Serial.println(); Serial.println();
#endif #endif
@@ -1854,24 +1783,22 @@ void loopDecoder() {
Serial.printf("current main is %d, current rxtask is %d\n", sonde.currentSonde, rxtask.currentSonde); Serial.printf("current main is %d, current rxtask is %d\n", sonde.currentSonde, rxtask.currentSonde);
} }
//APRSIs if (!tncclient.connected()) {
APRS_IS aprs_is(sonde.config.call, sonde.config.passcode, "RadioSonde", version_id); Serial.println("TNC client not connected");
if(!aprs_is.connected()) tncclient = tncserver.available();
{ if (tncclient.connected()) {
Serial.print("connecting to server: "); Serial.println("new TCP KISS connection");
Serial.print(sonde.config.tcpfeed.host); }
Serial.print(" on port: "); }
Serial.println(sonde.config.tcpfeed.port); if (tncclient.available()) {
if(!aprs_is.connect(sonde.config.tcpfeed.host, sonde.config.tcpfeed.port, "-t/po")) Serial.print("TCP KISS socket: recevied ");
{ while (tncclient.available()) {
Serial.println("Connection failed."); Serial.print(tncclient.read()); // Check if we receive anything from from APRSdroid
Serial.println("Waiting 5 seconds before retrying..."); }
delay(5000); Serial.println("");
return; }
} // wifi (axudp) or bluetooth (bttnc) active => send packet
Serial.println("Connected to server!"); if ((res & 0xff) == 0 && (connected || tncclient.connected() )) {
}
if ((res & 0xff) == 0) {
//Send a packet with position information //Send a packet with position information
// first check if ID and position lat+lonis ok // first check if ID and position lat+lonis ok
SondeInfo *s = &sonde.sondeList[rxtask.receiveSonde]; SondeInfo *s = &sonde.sondeList[rxtask.receiveSonde];
@@ -1886,25 +1813,17 @@ void loopDecoder() {
udp.write((const uint8_t *)raw, rawlen); udp.write((const uint8_t *)raw, rawlen);
udp.endPacket(); udp.endPacket();
} }
if (aprs_is.connected()) { if (tncclient.connected()) {
Serial.println("Sending position via TCP"); Serial.println("Sending position via TCP");
char raw[201]; char raw[201];
int rawlen = aprsstr_mon2kiss(str, raw, APRS_MAXLEN); int rawlen = aprsstr_mon2kiss(str, raw, APRS_MAXLEN);
Serial.print("sending: "); Serial.println(raw); Serial.print("sending: "); Serial.println(raw);
aprs_is.sendMessage(str); tncclient.write(raw, rawlen);
aprs_is.sendMessage(raw);
} }
} else {
sondehub_finish_data(&shclient, s, &sonde.config.sondehub);
} }
// also send to web socket
// Sonde hub //TODO
if (sonde.config.sondehub.active) { }
sondehub_send_data(&shclient, s, &sonde.config.sondehub);
}
}
Serial.println("updateDisplay started"); Serial.println("updateDisplay started");
if (forceReloadScreenConfig) { if (forceReloadScreenConfig) {
disp.initFromFile(); disp.initFromFile();
@@ -1993,26 +1912,15 @@ String translateEncryptionType(wifi_auth_mode_t encryptionType) {
} }
} }
enum t_wifi_state { WIFI_DISABLED, WIFI_SCAN, WIFI_CONNECT, WIFI_CONNECTED, WIFI_APMODE };
static t_wifi_state wifi_state = WIFI_DISABLED;
void enableNetwork(bool enable) { void enableNetwork(bool enable) {
if (enable) { if (enable) {
MDNS.begin(sonde.config.mdnsname); MDNS.begin(sonde.config.mdnsname);
SetupAsyncServer(); SetupAsyncServer();
udp.begin(WiFi.localIP(), LOCALUDPPORT); udp.begin(WiFi.localIP(), LOCALUDPPORT);
MDNS.addService("http", "tcp", 80); MDNS.addService("http", "tcp", 80);
MDNS.addService("kisstnc", "tcp", 14570);
if (sonde.config.kisstnc.active) { if (sonde.config.kisstnc.active) {
tncserver.begin(); tncserver.begin();
} }
if (sonde.config.sondehub.active && wifi_state != WIFI_APMODE) {
sondehub_station_update(&shclient, &sonde.config.sondehub);
time_last_update = millis();
}
connected = true; connected = true;
} else { } else {
MDNS.end(); MDNS.end();
@@ -2021,6 +1929,10 @@ void enableNetwork(bool enable) {
} }
enum t_wifi_state { WIFI_DISABLED, WIFI_SCAN, WIFI_CONNECT, WIFI_CONNECTED, WIFI_APMODE };
static t_wifi_state wifi_state = WIFI_DISABLED;
// Events used only for debug output right now // Events used only for debug output right now
void WiFiEvent(WiFiEvent_t event) void WiFiEvent(WiFiEvent_t event)
{ {
@@ -2508,14 +2420,28 @@ void execOTA() {
// execOTA(); // execOTA();
} }
/* //ICI Update Data
//ICI Update Data /*
// Update DATAWeb!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// Update DATAWeb!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //Data1 index.html
//Data data.tar updatedataweb(updateHost,updatePort,updateDataWeb,"index.html");
updatedataweb(updateHost,updatePort,updateDataWeb,"index.html"); //Data2 sondemap.html
*/ updatedataweb(updateHost,updatePort,updateDataWeb2,"sondemap.html");
//Data3 config.txt
updatedataweb(updateHost,updatePort,updateDataWeb3,"config.txt");
//Data4 screens.txt
updatedataweb(updateHost,updatePort,updateDataWeb4,"screens.txt");
//Data5 styles.css
updatedataweb(updateHost,updatePort,updateDataWeb5,"style.css");
//Data6 qrg.txt
updatedataweb(updateHost,updatePort,updateDataWeb6,"qrg.txt");
//Data7 networks.txt
updatedataweb(updateHost,updatePort,updateDataWeb7,"networks.txt");
//Data8 fontawesome-webfont.eot
updatedataweb(updateHost,updatePort,updateDataWeb8,"fontawesome-webfont.eot");
//Data9 fontawesome-webfont.eot
updatedataweb(updateHost,updatePort,updateDataWeb9,"fontawesome-webfont.woff");
*/
if (Update.end()) { if (Update.end()) {
Serial.println("OTA done!"); Serial.println("OTA done!");
@@ -2602,8 +2528,6 @@ void updatedataweb(String host, int port, String dataweb, String fichier){
file.close(); file.close();
} }
client.flush(); client.flush();
//Untar data.tar
} }
void loop() { void loop() {
@@ -2646,359 +2570,5 @@ void loop() {
lastDisplay = currentDisplay; lastDisplay = currentDisplay;
} }
Serial.printf("Unused stack: %d\n", uxTaskGetStackHighWaterMark(0)); Serial.printf("Unused stack: %d\n", uxTaskGetStackHighWaterMark(0));
//transfert_sd(); //transfert_sd();
//SondHub
if (sonde.config.sondehub.active) {
unsigned long time_now = millis();
// time_delta will be correct, even if time_now overflows
unsigned long time_delta = time_now - time_last_update;
if ((sonde.config.sondehub.chase == 0) && (time_delta >= SONDEHUB_STATION_UPDATE_TIME) && (wifi_state != WIFI_APMODE)) { // 60 min
sondehub_station_update(&shclient, &sonde.config.sondehub);
time_last_update = time_now;
}
else if ((sonde.config.sondehub.chase == 1) && (time_delta >= SONDEHUB_MOBILE_STATION_UPDATE_TIME) && (wifi_state != WIFI_APMODE)) { // 30 sec
sondehub_station_update(&shclient, &sonde.config.sondehub);
time_last_update = time_now;
}
}
} }
// Sondehub v2 DB related codes
/*
Update station data to the sondehub v2 DB
*/
void sondehub_station_update(WiFiClient *client, struct st_sondehub *conf) {
#define STATION_DATA_LEN 300
char data[STATION_DATA_LEN];
char *w;
Serial.println("sondehub_station_update()");
if (!client->connected()) {
if (!client->connect(conf->host, 80)) {
Serial.println("Connection FAILED");
return;
}
}
w = data;
memset(w, 0, STATION_DATA_LEN);
sprintf(w,
"{"
"\"software_name\": \"%s\","
"\"software_version\": \"%s\","
"\"uploader_callsign\": \"%s\","
"\"uploader_contact_email\": \"%s\",",
version_name, version_id, conf->callsign, conf->email);
w += strlen(w);
if ((conf->chase == 0) && (conf->lat[0] != '\0') && (conf->lon[0] != '\0')) {
if (conf->alt[0] != '\0') {
sprintf(w,
"\"uploader_position\": [%s,%s,%s],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->alt, conf->antenna);
} else {
sprintf(w,
"\"uploader_position\": [%s,%s,null],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->antenna);
}
}
else if (sonde.config.gps_Valid && sonde.config.gps_Lat != 0 && sonde.config.gps_Lon != 0) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%d],"
"\"uploader_antenna\": \"%s\","
"\"mobile\": true"
"}",
sonde.config.gps_Lat, sonde.config.gps_Lon, sonde.config.gps_Alt, conf->antenna);
}
else {
return;
}
client->println("PUT /listeners HTTP/1.1");
client->print("Host: ");
client->println(conf->host);
client->println("accept: text/plain");
client->println("Content-Type: application/json");
client->print("Content-Length: ");
client->println(strlen(data));
client->println();
client->println(data);
Serial.println(strlen(data));
Serial.println(data);
Serial.println("Waiting for response");
String response = client->readString();
Serial.println(response);
Serial.println("Response done...");
//client->stop();r
}
/*
Update sonde data to the sondehub v2 DB
*/
enum SHState { SH_DISCONNECTED, SH_CONNECTING, SH_CONN_IDLE, SH_CONN_APPENDING, SH_CONN_WAITACK };
SHState shState = SH_DISCONNECTED;
time_t shStart = 0;
/* Sonde.h: enum SondeType { STYPE_DFM06, STYPE_DFM09, STYPE_RS41, STYPE_RS92, STYPE_M10, STYPE_M20, STYPE_DFM }; */
const char *sondeTypeStrSH[NSondeTypes] = { "DFM", "DFM", "RS41", "RS92", "M10", "M20", "DFM" };
const char *dfmSubtypeStrSH[16] = { NULL, NULL, NULL, NULL, NULL, NULL,
"DFM06", // 0x06
"PS15", // 0x07
NULL, NULL,
"DFM09", // 0x0A
"DFM17", // 0x0B
"DFM09P", // 0x0C
"DFM17", // 0x0D
NULL, NULL
};
// in hours.... max allowed diff UTC <-> sonde time
#define SONDEHUB_TIME_THRESHOLD (3)
void sondehub_send_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
Serial.println("sondehub_send_data()");
Serial.printf("shState = %d\n", shState);
// max age of data in JSON request (in seconds)
#define SONDEHUB_MAXAGE 15
#define MSG_SIZE 550
char rs_msg[MSG_SIZE];
char *w;
struct tm ts;
// For DFM, s->time is data from subframe DAT8 (gps date/hh/mm), and sec is from DAT1 (gps sec/usec)
// For all others, sec should always be 0 and time the exact time in seconds
time_t t = s->time + s->sec;
while (client->available() > 0) {
// data is available from remote server, process it...
int cnt = client->readBytesUntil('\n', rs_msg, MSG_SIZE);
rs_msg[cnt] = 0;
Serial.println(rs_msg);
// If something that looks like a valid HTTP response is received, we are ready to send the next data item
if (shState == SH_CONN_WAITACK && cnt > 11 && strncmp(rs_msg, "HTTP/1", 6) == 0) {
shState = SH_CONN_IDLE;
}
}
struct tm timeinfo;
time_t now;
time(&now);
gmtime_r(&now, &timeinfo);
if (timeinfo.tm_year <= (2016 - 1900)) {
Serial.println("Failed to obtain time");
return;
}
// Check if current sonde data is valid. If not, don't do anything....
if (*s->ser == 0) return; // Don't send anything without serial number
if (((int)s->lat == 0) && ((int)s->lon == 0)) return; // Sometimes these values are zeroes. Don't send those to the sondehub
if ((int)s->alt > 50000) return; // If alt is too high don't send to SondeHub
// M20 data does not include #sat information
if ( s->type != STYPE_M20 && (int)s->sats < 4) return; // If not enough sats don't send to SondeHub
// If not connected to sondehub, try reconnecting.
// TODO: do this outside of main loop
if (!client->connected()) {
Serial.println("NO CONNECTION");
shState = SH_DISCONNECTED;
if (!client->connect(conf->host, 80)) {
Serial.println("Connection FAILED");
return;
}
client->Client::setTimeout(0); // does this work?
shState = SH_CONN_IDLE;
}
if ( shState == SH_CONN_WAITACK ) {
Serial.println("Previous SH-frame not yet ack'ed, not sending new data");
return;
}
if ( abs(now - (time_t)s->time) > (3600 * SONDEHUB_TIME_THRESHOLD) ) {
Serial.printf("Sonde time %d too far from current UTC time %ld", s->time, now);
return;
}
// DFM uses UTC. Most of the other radiosondes use GPS time
// SondeHub expect datetime to be the same time sytem as the sonde transmits as time stamp
if ( s->type == STYPE_RS41 || s->type == STYPE_RS92 || s->type == STYPE_M20 ) {
t += 18; // convert back to GPS time from UTC time +18s
}
gmtime_r(&t, &ts);
memset(rs_msg, 0, MSG_SIZE);
w = rs_msg;
sprintf(w,
" {"
"\"software_name\": \"%s\","
"\"software_version\": \"%s\","
"\"uploader_callsign\": \"%s\","
"\"time_received\": \"%04d-%02d-%02dT%02d:%02d:%02d.000Z\","
"\"manufacturer\": \"%s\","
"\"serial\": \"%s\","
"\"datetime\": \"%04d-%02d-%02dT%02d:%02d:%02d.000Z\","
"\"lat\": %.6f,"
"\"lon\": %.6f,"
"\"alt\": %.3f,"
"\"frequency\": %.3f,"
"\"vel_h\": %.3f,"
"\"vel_v\": %.3f,"
"\"heading\": %.3f,"
"\"rssi\": %.1f,",
version_name, version_id, conf->callsign,
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec,
manufacturer_string[s->type], s->ser,
ts.tm_year + 1900, ts.tm_mon + 1, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec + s->sec,
(float)s->lat, (float)s->lon, (float)s->alt, (float)s->freq, (float)s->hs, (float)s->vs,
(float)s->dir, -((float)s->rssi / 2)
);
w += strlen(w);
if (s->type != STYPE_M20) {
sprintf(w, "\"sats\": %d,", (int)s->sats);
w += strlen(w);
}
if ( TYPE_IS_DFM(s->type) || TYPE_IS_METEO(s->type) ) {
// send frame as gps timestamp for these sonde, identical to autorx
// For M10, this is real GPS time (seconds since Jqn 6 1980, without adjusting for leap seconds)
// DFM and MP3H send real UTC (with leap seconds considered), so for them the frame number actually
// is gps time plus number of leap seconds since the beginning of GPS time.
int frame = (int)(t - 315964800);
if (s->type == STYPE_M10) {
frame += 18;
};
sprintf(w, "\"frame\": %d,", frame);
} else {
sprintf(w, "\"frame\": %d,", s->frame);
}
w += strlen(w);
sprintf(w, "\"type\": \"%s\",", sondeTypeStrSH[s->type]);
w += strlen(w);
/* if there is a subtype (DFM only) */
if ( TYPE_IS_DFM(s->type) && s->subtype > 0 && s->subtype < 16 ) {
const char *t = dfmSubtypeStrSH[s->subtype];
// as in https://github.com/projecthorus/radiosonde_auto_rx/blob/e680221f69a568e1fdb24e76db679233f32cb027/auto_rx/autorx/sonde_specific.py#L84
if (t) sprintf(w, "\"subtype\": \"%s\",", t);
else sprintf(w, "\"subtype\": \"DFMx%X\",", s->subtype); // Unknown subtype
w += strlen(w);
}
if (((int)s->temperature != 0) && ((int)s->relativeHumidity != 0)) {
sprintf(w,
"\"temp\": %.1f,"
"\"humidity\": %.1f,",
float(s->temperature), float(s->relativeHumidity)
);
w += strlen(w);
}
if ((conf->chase == 0) && (conf->lat[0] != '\0') && (conf->lon[0] != '\0')) {
if (conf->alt[0] != '\0') {
sprintf(w,
"\"uploader_position\": [%s,%s,%s],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->alt, conf->antenna
);
} else {
sprintf(w,
"\"uploader_position\": [%s,%s,null],"
"\"uploader_antenna\": \"%s\""
"}",
conf->lat, conf->lon, conf->antenna
);
}
}
else if (sonde.config.gps_Valid && sonde.config.gps_Lat != 0 && sonde.config.gps_Lon != 0) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%d],"
"\"uploader_antenna\": \"%s\""
"}",
sonde.config.gps_Lat, sonde.config.gps_Lon, sonde.config.gps_Alt, conf->antenna
);
}
else {
sprintf(w,
"\"uploader_antenna\": \"%s\""
"}",
conf->antenna
);
}
if (shState != SH_CONN_APPENDING) {
sondehub_send_header(client, s, conf);
sondehub_send_next(client, s, conf, rs_msg, strlen(rs_msg), 1);
shState = SH_CONN_APPENDING;
shStart = now;
} else {
sondehub_send_next(client, s, conf, rs_msg, strlen(rs_msg), 0);
}
if (now - shStart > SONDEHUB_MAXAGE) { // after MAXAGE seconds
sondehub_send_last(client, s, conf);
shState = SH_CONN_WAITACK;
shStart = 0;
}
//client->println(rs_msg);
//Serial.println(rs_msg);
//String response = client->readString();
//Serial.println(response);
}
void sondehub_finish_data(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
// If there is an "old" pending collection of JSON data sets, send it even if no now data is received
if (shState == SH_CONN_APPENDING) {
time_t now;
time(&now);
if (now - shStart > SONDEHUB_MAXAGE + 3) { // after MAXAGE seconds
sondehub_send_last(client, s, conf);
shState = SH_CONN_WAITACK;
shStart = 0;
}
}
}
void sondehub_send_header(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
Serial.print("PUT /sondes/telemetry HTTP/1.1\r\n"
"Host: ");
Serial.println(conf->host);
Serial.println("accept: text/plain\r\n"
"Content-Type: application/json\r\n"
"Transfer-Encoding: chunked\r\n");
client->print("PUT /sondes/telemetry HTTP/1.1\r\n"
"Host: ");
client->println(conf->host);
client->println("accept: text/plain\r\n"
"Content-Type: application/json\r\n"
"Transfer-Encoding: chunked\r\n");
}
void sondehub_send_next(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf, char *chunk, int chunklen, int first) {
// send next chunk of JSON request
client->printf("%x\r\n", chunklen + 1);
client->write(first ? "[" : ",", 1);
client->write(chunk, chunklen);
client->print("\r\n");
Serial.printf("%x\r\n", chunklen + 1);
Serial.write(first ? "[" : ",", 1);
Serial.write(chunk, chunklen);
Serial.print("\r\n");
}
void sondehub_send_last(WiFiClient *client, SondeInfo *s, struct st_sondehub *conf) {
// last chunk. just the closing "]" of the json request
client->printf("1\r\n]\r\n0\r\n\r\n");
Serial.printf("1\r\n]\r\n0\r\n\r\n");
}
+8 -21
View File
@@ -24,8 +24,8 @@
#oled_rst=16 #oled_rst=16
oled_orient=1 oled_orient=1
gpsOn=0 gpsOn=0
gps_rxd=34 gps_rxd=-1
gps_txd=12 gps_txd=-1
gps_lat=43.59169 gps_lat=43.59169
gps_lon=7.10071 gps_lon=7.10071
gps_alt=123 gps_alt=123
@@ -54,7 +54,7 @@ vbatmax=1.86
vbatmin=1.64 vbatmin=1.64
telemetryOn=0 telemetryOn=0
buzzerOn=0 buzzerOn=0
buzzerPort=25 buzzerPort=12
buzzerFreq=700 buzzerFreq=700
dbsmetre=1 dbsmetre=1
gainLNA=0 gainLNA=0
@@ -90,8 +90,8 @@ dfm.rxbw=10400
# local use only, do not feed to public services # local use only, do not feed to public services
# data not sanitized / quality checked, outliers not filtered out # data not sanitized / quality checked, outliers not filtered out
axudp.active=1 axudp.active=1
axudp.host=fra1od.freeboxos.fr axudp.host=fra1od.fr.to
axudp.port=20350 axudp.port=14580
axudp.symbol=/O axudp.symbol=/O
axudp.highrate=1 axudp.highrate=1
axudp.idformat=0 axudp.idformat=0
@@ -99,25 +99,12 @@ axudp.idformat=0
# maybe some time in the future # maybe some time in the future
#-------------------------------# #-------------------------------#
# currently simply not implemented, no need to put anything here anyway # currently simply not implemented, no need to put anything here anyway
tcp.active=1 tcp.active=0
tcp.host=fra1od.freeboxos.fr tcp.host=radiosondy.info
tcp.port=14580 tcp.port=14590
tcp.symbol=/O tcp.symbol=/O
tcp.highrate=20 tcp.highrate=20
tcp.idformat=0 tcp.idformat=0
#-------------------------------# #-------------------------------#
# Sondehub v2 settings
#-------------------------------#
# Sondehub v2 DB settings
sondehub.active=0
sondehub.chase=0
sondehub.host=api.v2.sondehub.org
sondehub.callsign=FRS2013
sondehub.lat=
sondehub.lon=
sondehub.alt=
sondehub.antenna=CJU 403Mhz
sondehub.email=14frs2013@free.fr
#-------------------------------#
# EOF # EOF
#-------------------------------# #-------------------------------#
+1 -1
View File
@@ -77,7 +77,7 @@
<br> <br>
Autodetect info: %AUTODETECT_INFO%<br> Autodetect info: %AUTODETECT_INFO%<br>
<br> <br>
CopyLeft 2021 Modifier par <a href="http://openpmr.cla.fr">FRS2013</a> & Vigor<br> CopyLeft 2020 Modifier par <a href="http://openpmr.cla.fr">FRS2013</a><br>
Licence GNU GPL <a href="https://www.gnu.org/licenses/gpl-2.0.txt">https://www.gnu.org/licenses/gpl-2.0.txt</a> Licence GNU GPL <a href="https://www.gnu.org/licenses/gpl-2.0.txt">https://www.gnu.org/licenses/gpl-2.0.txt</a>
for details for details
</div> </div>
+1 -1
View File
@@ -1,5 +1,5 @@
# Frequency in Mhz (format nnn.nnn) # Frequency in Mhz (format nnn.nnn)
# Type (4=RS41, R=RS92, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09, D=DFM, M=M10, 2=M20) # Type (4=RS41, R=RS92, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09, M=M10, 2=M20)
# #
400.000 9 + Test(FR) 400.000 9 + Test(FR)
401.500 4 - Santander(ES) 401.500 4 - Santander(ES)
+3 -1
View File
@@ -1,2 +1,4 @@
const char *version_name = "RadioSonde"; const char *version_name = "RadioSonde";
const char *version_id = "0.9.2"; const char *version_id = "0.9.0";
//const int SPIFFS_MAJOR=3;
//const int SPIFFS_MINOR=5;
@@ -194,7 +194,7 @@ uint8_t SX1278FSK::setFSK()
{ {
uint8_t state = 2; uint8_t state = 2;
byte st0; byte st0;
//byte config1; byte config1;
#if (SX1278FSK_debug_mode > 1) #if (SX1278FSK_debug_mode > 1)
Serial.println(); Serial.println();
@@ -109,14 +109,14 @@
#define REG_MODEM_CONFIG3 0x26 #define REG_MODEM_CONFIG3 0x26
#define REG_SYNC_CONFIG 0x27 #define REG_SYNC_CONFIG 0x27
#define REG_SYNC_VALUE1 0x28 #define REG_SYNC_VALUE1 0x28
#define REG_LORA_FEI_MSB 0x28 #define REG_LORA_FEI_MSB 0x28
#define REG_SYNC_VALUE2 0x29 #define REG_SYNC_VALUE2 0x29
#define REG_LORA_FEI_MID 0x29 #define REG_LORA_FEI_MID 0x29
#define REG_SYNC_VALUE3 0x2A #define REG_SYNC_VALUE3 0x2A
#define REG_LORA_FEI_LSB 0x2A #define REG_LORA_FEI_LSB 0x2A
#define REG_SYNC_VALUE4 0x2B #define REG_SYNC_VALUE4 0x2B
#define REG_SYNC_VALUE5 0x2C #define REG_SYNC_VALUE5 0x2C
#define REG_RSSI_WIDEBAND 0x2C #define REG_RSSI_WIDEBAND 0x2C
#define REG_SYNC_VALUE6 0x2D #define REG_SYNC_VALUE6 0x2D
#define REG_SYNC_VALUE7 0x2E #define REG_SYNC_VALUE7 0x2E
#define REG_SYNC_VALUE8 0x2F #define REG_SYNC_VALUE8 0x2F
+347
View File
@@ -0,0 +1,347 @@
/* DFM decoder functions */
#include "DFM.h"
#include "SX1278FSK.h"
#include "Sonde.h"
#define DFM_DEBUG 1
#if DFM_DEBUG
#define DFM_DBG(x) x
#else
#define DFM_DBG(x)
#endif
int DFM::setup(float frequency, int inv)
{
inverse = inv;
#if DFM_DEBUG
Serial.printf("Setup sx1278 for DFM sonde (inv=%d)\n",inv);
#endif
if(sx1278.ON()!=0) {
DFM_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
DFM_DBG(Serial.println("Setting FSM mode FAILED"));
return 1;
}
if(sx1278.setBitrate(2500)!=0) {
DFM_DBG(Serial.println("Setting bitrate 2500bit/s FAILED"));
return 1;
}
#if DFM_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.dfm.agcbw)!=0) {
DFM_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.dfm.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.dfm.rxbw)!=0) {
DFM_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.dfm.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
DFM_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
const char *SYNC=inverse?"\x9A\x99\x5A\x55":"\x65\x66\xA5\xAA";
if(sx1278.setSyncConf(0x53, 4, (const uint8_t *)SYNC)!=0) {
DFM_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
DFM_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// Packet config 1: fixed len, mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x28, 0x40)!=0) {
DFM_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("DFM: setting RX frequency to ");
Serial.println(frequency);
int retval = sx1278.setFrequency(frequency);
sx1278.clearIRQFlags();
DFM_DBG(Serial.println("Setting SX1278 config for DFM finished\n"); Serial.println());
return retval;
}
#define bitpick(value,bitpos) (((value)>>(7-(bitpos)))&0x01)
// Input: str: packed data, MSB first
void DFM::deinterleave(uint8_t *str, int L, uint8_t *block) {
int i, j;
for (j = 0; j < B; j++) { // L = 7 (CFG), 13 (DAT1, DAT2)
for (i = 0; i < L; i++) {
block[B*i+j] = bitpick( str[(L*j+i)/8], (L*j+i)&7 )?0:1;
}
}
}
uint32_t DFM::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
// Error correction for hamming code
// returns 0: ok >0: 1 error was corrected -1: uncorrectable error
int DFM::check(uint8_t code[8]) {
int i, j;
uint32_t synval = 0;
uint8_t syndrom[4];
int ret=0;
for (i = 0; i < 4; i++) {
syndrom[i] = 0;
for (j = 0; j < 8; j++) {
syndrom[i] ^= H[i][j] & code[j];
}
}
synval = bits2val(syndrom, 4);
if (synval) {
ret = -1;
for (j = 0; j < 8; j++) { // 1-bit-error
if (synval == He[j]) { // reicht auf databits zu pruefen, d.h.
ret = j+1; // (systematischer Code) He[0..3]
break;
}
}
}
else ret = 0;
if (ret > 0) code[ret-1] ^= 0x1;
return ret;
}
// Extended (8,4) Hamming code
// Return number of corrected bits, -1 if uncorrectable error
int DFM::hamming(uint8_t *ham, int L, uint8_t *sym) {
int i, j;
int ret = 0; // DFM: length L = 7 or 13
for (i = 0; i < L; i++) { // L bytes (4bit data, 4bit parity)
if (use_ecc) {
int res = check(ham+8*i);
if(ret>=0 && res>=0) ret += res; else ret=-1;
}
// systematic Hamming code: copy bits 0..3
for (j = 0; j < 4; j++) {
sym[4*i+j] = ham[8*i+j];
}
}
return ret;
}
DFM::DFM() {
}
void DFM::printRaw(const char *label, int len, int ret, const uint8_t *data)
{
Serial.print(label); Serial.print("(");
Serial.print(ret);
Serial.print("):");
int i;
for(i=0; i<len/2; i++) {
char str[10];
snprintf(str, 10, "%02X", data[i]);
Serial.print(str);
}
Serial.print(data[i]&0x0F, HEX);
Serial.print(" ");
}
void DFM::decodeCFG(uint8_t *cfg)
{
static int lowid, highid, idgood=0, type=0;
if((cfg[0]>>4)==0x06 && type==0) { // DFM-6 ID
lowid = ((cfg[0]&0x0F)<<20) | (cfg[1]<<12) | (cfg[2]<<4) | (cfg[3]&0x0f);
Serial.print("DFM-06 ID: "); Serial.print(lowid, HEX);
snprintf(sonde.si()->id, 10, "%x", lowid);
sonde.si()->validID = true;
}
if((cfg[0]>>4)==0x0A) { // DMF-9 ID
type=9;
if(cfg[3]==1) {
lowid = (cfg[1]<<8) | cfg[2];
idgood |= 1;
} else {
highid = (cfg[1]<<8) | cfg[2];
idgood |= 2;
}
if(idgood==3) {
uint32_t dfmid = (highid<<16) | lowid;
Serial.print("DFM-09 ID: "); Serial.print(dfmid);
snprintf(sonde.si()->ser, 10, "%d", dfmid);
// dxlAPRS sonde number (DF6 (why??) and 5 last digits of serial number as hex number
snprintf(sonde.si()->id, 9, "DF6%05X", dfmid&0xfffff);
sonde.si()->validID = true;
}
}
}
static int bitCount(int x) {
int m4 = 0x1 | (0x1<<8) | (0x1<<16) | (0x1<<24);
int m1 = 0xFF;
int s4 = (x&m4) + ((x>>1)&m4) + ((x>>2)&m4) + ((x>>3)&m4) + ((x>>4)&m4) + ((x>>5)&m4) + ((x>>6)&m4) + ((x>>7)&m4);
int s1 = (s4&m1) + ((s4>>8)&m1) + ((s4>>16)&m1) + ((s4>>24)&m1);
return s1;
}
static uint16_t MON[]={0,0,31,59,90,120,151,181,212,243,273,304,334};
void DFM::decodeDAT(uint8_t *dat)
{
Serial.print(" DAT["); Serial.print(dat[6]); Serial.print("]: ");
switch(dat[6]) {
case 0:
Serial.print("Packet counter: "); Serial.print(dat[3]);
sonde.si()->frame = dat[3];
break;
case 1:
{
int val = (((uint16_t)dat[4])<<8) + (uint16_t)dat[5];
Serial.print("UTC-msec: "); Serial.print(val);
sonde.si()->sec = val/1000;
uint32_t tmp = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
sonde.si()->sats = bitCount(tmp); // maybe!?!?!?
}
break;
case 2:
{
float lat, vh;
lat = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
vh = ((uint16_t)dat[4]<<8) + dat[5];
Serial.print("GPS-lat: "); Serial.print(lat*0.0000001);
Serial.print(", hor-V: "); Serial.print(vh*0.01);
sonde.si()->lat = lat*0.0000001;
sonde.si()->hs = vh*0.01;
sonde.si()->validPos |= 0x11;
}
break;
case 3:
{
float lon, dir;
lon = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + (uint32_t)dat[3];
dir = ((uint16_t)dat[4]<<8) + dat[5];
Serial.print("GPS-lon: "); Serial.print(lon*0.0000001);
Serial.print(", dir: "); Serial.print(dir*0.01);
sonde.si()->lon = lon*0.0000001;
sonde.si()->dir = dir*0.01;
sonde.si()->validPos |= 0x42;
}
break;
case 4:
{
float alt, vv;
alt = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + dat[3];
vv = (int16_t)( ((int16_t)dat[4]<<8) | dat[5] );
Serial.print("GPS-height: "); Serial.print(alt*0.01);
Serial.print(", vv: "); Serial.print(vv*0.01);
sonde.si()->alt = alt*0.01;
sonde.si()->vs = vv*0.01;
sonde.si()->validPos |= 0x0C;
}
break;
case 8:
{
int y = (dat[0]<<4) + (dat[1]>>4);
int m = dat[1]&0x0F;
int d = dat[2]>>3;
int h = ((dat[2]&0x07)<<2) + (dat[3]>>6);
int mi = (dat[3]&0x3F);
char buf[100];
snprintf(buf, 100, "%04d-%02d-%02d %02d:%02dz", y, m, d, h, mi);
Serial.print("Date: "); Serial.print(buf);
// convert to unix time
int tt = (y-1970)*365 + (y-1969)/4; // days since 1970
if(m<=12) { tt += MON[m]; if((y%4)==0 && m>2) tt++; }
tt = (tt+d-1)*(60*60*24) + h*3600 + mi*60;
sonde.si()->time = tt;
}
break;
default:
Serial.print("(?)");
break;
}
}
void DFM::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
//Serial.print(bits[i]?"1":"0");
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
int DFM::receive() {
byte data[1000]; // pending data from previous mode may write more than 33 bytes. TODO.
for(int i=0; i<2; i++) {
sx1278.setPayloadLength(33); // Expect 33 bytes (7+13+13 bytes)
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
int e = sx1278.receivePacketTimeout(1000, data);
if(e) { return RX_TIMEOUT; } //if timeout... return 1
Serial.printf("inverse is %d\b", inverse);
if(!inverse) { for(int i=0; i<33; i++) { data[i]^=0xFF; } }
deinterleave(data, 7, hamming_conf);
deinterleave(data+7, 13, hamming_dat1);
deinterleave(data+20, 13, hamming_dat2);
int ret0 = hamming(hamming_conf, 7, block_conf);
int ret1 = hamming(hamming_dat1, 13, block_dat1);
int ret2 = hamming(hamming_dat2, 13, block_dat2);
byte byte_conf[4], byte_dat1[7], byte_dat2[7];
bitsToBytes(block_conf, byte_conf, 7);
bitsToBytes(block_dat1, byte_dat1, 13);
bitsToBytes(block_dat2, byte_dat2, 13);
printRaw("CFG", 7, ret0, byte_conf);
printRaw("DAT", 13, ret1, byte_dat1);
printRaw("DAT", 13, ret2, byte_dat2);
decodeCFG(byte_conf);
decodeDAT(byte_dat1);
decodeDAT(byte_dat2);
}
return RX_OK;
}
// moved to a single function in Sonde(). This function can be used for additional
// processing here, that takes too long for doing in the RX task loop
int DFM::waitRXcomplete() {
#if 0
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult < 0 && millis()-t0 < 2000) { delay(50); }
if( rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if ( rxtask.receiveResult ==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = -1;
Serial.printf("waitRXcomplete returning %d\n", res);
return res;
#endif
return 0;
}
DFM dfm = DFM();
@@ -23,23 +23,16 @@
class DFM class DFM
{ {
private: private:
int stype; int inverse=0;
char *stypename=NULL;
void deinterleave(uint8_t *str, int L, uint8_t *block); void deinterleave(uint8_t *str, int L, uint8_t *block);
uint32_t bits2val(const uint8_t *bits, int len); uint32_t bits2val(const uint8_t *bits, int len);
int check(uint8_t code[8]); int check(uint8_t code[8]);
int hamming(uint8_t *ham, int L, uint8_t *sym); int hamming(uint8_t *ham, int L, uint8_t *sym);
void printRaw(const char *prefix, int len, int ret, const uint8_t* data); void printRaw(const char *prefix, int len, int ret, const uint8_t* data);
void finddfname(uint8_t *cfg);
void decodeCFG(uint8_t *cfg); void decodeCFG(uint8_t *cfg);
void decodeDAT(uint8_t *dat); void decodeDAT(uint8_t *dat);
void bitsToBytes(uint8_t *bits, uint8_t *bytes, int len); void bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int processDFMdata(uint8_t dt);
int decodeFrameDFM(uint8_t *data);
int receiveOld();
int receiveNew();
#define B 8 #define B 8
#define S 4 #define S 4
@@ -62,7 +55,7 @@ private:
public: public:
DFM(); DFM();
// main decoder API // main decoder API
int setup(float frequency, int type); int setup(float frequency, int inverse);
int receive(); int receive();
int waitRXcomplete(); int waitRXcomplete();
@@ -13,11 +13,11 @@ int readLine(Stream &stream, char *buffer, int maxlen);
extern const char *version_name; extern const char *version_name;
extern const char *version_id; extern const char *version_id;
#include <fonts/FreeMono9pt7b.h> #include <../fonts/FreeMono9pt7b.h>
#include <fonts/FreeMono12pt7b.h> #include <../fonts/FreeMono12pt7b.h>
#include <fonts/FreeSans9pt7b.h> #include <../fonts/FreeSans9pt7b.h>
#include <fonts/FreeSans12pt7b.h> #include <../fonts/FreeSans12pt7b.h>
#include <fonts/Picopixel.h> #include <../fonts/Picopixel.h>
extern Sonde sonde; extern Sonde sonde;
@@ -27,10 +27,10 @@ extern AXP20X_Class axp;
extern bool axp192_found; extern bool axp192_found;
extern SemaphoreHandle_t axpSemaphore; extern SemaphoreHandle_t axpSemaphore;
SPIClass spiDisp(HSPI); SPIClass spiDisp(HSPI);
const char *sondeTypeStr[NSondeTypes] = { "DFM6", "DFM9", "RS41", "RS92", "M10 ", "M20 ", "DFM " }; const char *sondeTypeStr[NSondeTypes] = { "DFM6", "DFM9", "RS41", "RS92", "M10 ", "M20 " };
const char sondeTypeChar[NSondeTypes] = { '6', '9', '4', 'R', 'M', '2', 'D' };
byte myIP_tiles[8*11]; byte myIP_tiles[8*11];
static uint8_t ap_tile[8]={0x00,0x04,0x22,0x92, 0x92, 0x22, 0x04, 0x00}; static uint8_t ap_tile[8]={0x00,0x04,0x22,0x92, 0x92, 0x22, 0x04, 0x00};
@@ -268,12 +268,12 @@ void U8x8Display::drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y
void U8x8Display::welcome() { void U8x8Display::welcome() {
u8x8->clear(); u8x8->clear();
setFont(FONT_LARGE); setFont(FONT_LARGE);
drawString(0, 1, version_name); drawString(8 - strlen(version_name) / 2, 1, version_name);
drawString(11, 1, version_id); //drawString(8 - strlen(version_id) / 2, 2, version_id);
setFont(FONT_SMALL); setFont(FONT_SMALL);
drawString(0, 4, "RS41/92,DFM,M1&2"); drawString(0, 4, "RS41/92,DFM,M1&2");
drawString(0, 6, "by Vigor & Xav "); drawString(0, 6, "by FRS2013 ");
//drawString(11, 6, version_id); drawString(11, 6, version_id);
} }
static String previp; static String previp;
@@ -858,7 +858,7 @@ void Display::drawHS(DispEntry *de) {
} }
boolean is_ms = (de->extra && de->extra[0]=='m')?true:false; // m/s or km/h boolean is_ms = (de->extra && de->extra[0]=='m')?true:false; // m/s or km/h
float hs = sonde.si()->hs; float hs = sonde.si()->hs;
if(is_ms) hs = hs * 3.6; if(is_ms) hs = hs / 3.6;
boolean has_extra = (de->extra && de->extra[1]!=0)? true: false; boolean has_extra = (de->extra && de->extra[1]!=0)? true: false;
snprintf(buf, 16, hs>99?" %3.0f":" %2.1f", hs); snprintf(buf, 16, hs>99?" %3.0f":" %2.1f", hs);
if(has_extra) { strcat(buf, de->extra+1); } if(has_extra) { strcat(buf, de->extra+1); }
@@ -1127,7 +1127,6 @@ static int tmpc=0;
gpsCourse = lastCourse; gpsCourse = lastCourse;
} }
} }
sonde.config.gps_Valid=gpsValid;
sonde.config.gps_Lat=gpsLat; sonde.config.gps_Lat=gpsLat;
sonde.config.gps_Lon=gpsLon; sonde.config.gps_Lon=gpsLon;
sonde.config.gps_Alt=gpsAlt; sonde.config.gps_Alt=gpsAlt;
@@ -1215,7 +1214,7 @@ void Display::calcDurVol() {
} }
} else { } else {
heure=0; heure=99;
} }
sonde.si()->durvolheure=heure; sonde.si()->durvolheure=heure;
sonde.si()->durvolminute=minute; sonde.si()->durvolminute=minute;
@@ -80,13 +80,12 @@ int M10::setup(float frequency)
//const char *SYNC="\x99\x9A"; //const char *SYNC="\x99\x9A";
#if 1 #if 1
// version 1, working with continuous RX // version 1, working with continuous RX
//const char *SYNC="\x66\x65"; const char *SYNC="\x66\x65";
if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
const char *SYNC="\x99\x99\x4C\x99";
if(sx1278.setSyncConf(0x70, 4, (const uint8_t *)SYNC)!=0) {
M10_DBG(Serial.println("Setting SYNC Config FAILED")); M10_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1; return 1;
} }
//if(sx1278.setPreambleDetect(0xA8)!=0) {
if((sx1278.setPreambleDetect(0x9F)!=0)&&((sx1278.setPreambleDetect(0xAF)!=0))) { if((sx1278.setPreambleDetect(0x9F)!=0)&&((sx1278.setPreambleDetect(0xAF)!=0))) {
M10_DBG(Serial.println("Setting PreambleDetect FAILED")); M10_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1; return 1;
@@ -259,7 +258,7 @@ int M10::decodeframeM10(uint8_t *data) {
} }
} }
Serial.println(crcok?"CRC OK":"CRC NOT OK"); Serial.println(crcok?"CRC OK":"CRC NOT OK");
//M10 0x9F M10Plus 0xAF //M10 0x9F M10Plus 0xAF M20 0x20
if((data[1]==0x9F && data[2]==0x20) || (data[1]==0xAF && data[2]==0x20)) { if((data[1]==0x9F && data[2]==0x20) || (data[1]==0xAF && data[2]==0x20)) {
Serial.println("Decoding..."); Serial.println("Decoding...");
// Its a M10 // Its a M10
@@ -382,7 +381,7 @@ void M10::processM10data(uint8_t dt)
int M10::receive() { int M10::receive() {
unsigned long t0 = millis(); unsigned long t0 = millis();
Serial.printf("M10::receive() start at %ld\n",t0); Serial.printf("M10::receive() start at %ld\n",t0);
while( millis() - t0 < 1512 ) { while( millis() - t0 < 1000 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2); uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) { if ( bitRead(value, 7) ) {
Serial.println("FIFO full"); Serial.println("FIFO full");
@@ -79,9 +79,8 @@ int M20::setup(float frequency)
//const char *SYNC="\x99\x9A"; //const char *SYNC="\x99\x9A";
#if 1 #if 1
// version 1, working with continuous RX // version 1, working with continuous RX
//const char *SYNC="\x66\x65"; const char *SYNC="\x66\x65";
const char *SYNC="\x99\x99\x4C\x99"; if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
if(sx1278.setSyncConf(0x70, 4, (const uint8_t *)SYNC)!=0) {
M20_DBG(Serial.println("Setting SYNC Config FAILED")); M20_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1; return 1;
} }
@@ -160,7 +159,7 @@ M20::M20() {
} }
#define M20_FRAMELEN 70 #define M20_FRAMELEN 70
#define M20_CRCPOS 68 #define M20_CRCPOS 22
void M20::printRaw(uint8_t *data, int len) void M20::printRaw(uint8_t *data, int len)
{ {
@@ -221,10 +220,6 @@ static int16_t getint16(uint8_t *data) {
return (int16_t)(data[1]|((uint16_t)data[0]<<8)); return (int16_t)(data[1]|((uint16_t)data[0]<<8));
} }
static int16_t getint16_r(uint8_t *data) {
return (int16_t)(((uint16_t)data[1]<<8) |data[0]);
}
static char dez(uint8_t nr) { static char dez(uint8_t nr) {
nr = nr%10; nr = nr%10;
return '0'+nr; return '0'+nr;
@@ -269,35 +264,49 @@ int M20::decodeframeM20(uint8_t *data) {
} }
Serial.println(crcok?"CRC OK":"CRC NOT OK"); Serial.println(crcok?"CRC OK":"CRC NOT OK");
//M20 0x20 //M20 0x20
if(data[1]==0x20) { if(data[1]==0x20 && data[2]==0x20) {
Serial.println("Decoding..."); Serial.println("Decoding...");
// Its a M20 // Its a M20
// getid... // getid...
char ids[11]={'M','E','0','0','0','0','0','0','0','0','0'}; char ids[11]={'M','E','0','0','0','0','0','0','0','0','0'};
/*
ids[0] = 'M'; ids[0] = 'M';
ids[1] = 'E'; ids[1] = 'E';
uint32_t id = getint16(data+18); ids[2] = hex(data[95]/16);
ids[2] = hex(id/16); ids[3] = hex(data[95]);
ids[3] = hex(id); ids[4] = hex(data[93]);
// uint32_t id = data[96] + data[97]*256;
id = getint16_r(data+19)/4; ids[5] = hex(id/4096);
ids[4] = (char)((id/10000)%10+48); ids[6] = hex(id/256);
ids[5] = (char)((id/1000)%10+48); ids[7] = hex(id/16);
ids[6] = (char)((id/100)%10+48); ids[8] = hex(id);
ids[7] = (char)((id/10)%10+48); ids[9] = 0;
ids[8] = (char)(id%10+48); strncpy(sonde.si()->id, ids, 10);
ids[0] = hex(data[95]/16);
ids[1] = dez((data[95]&0x0f)/10);
ids[2] = dez((data[95]&0x0f));
ids[3] = dez(data[93]);
ids[4] = dez(id>>13);
id &= 0x1fff;
ids[5] = dez(id/1000);
ids[6] = dez((id/100)%10);
ids[7] = dez((id/10)%10);
ids[8] = dez(id%10);
*/
uint32_t id = data[96] + data[97]*256;
id &= 0x1fff;
strncpy(sonde.si()->ser, ids, 10); strncpy(sonde.si()->ser, ids, 10);
sonde.si()->validID = true; sonde.si()->validID = true;
//Serial.printf("ID is %s [%02x %02x %d]\n", ids, data[95], data[93], id); Serial.printf("ID is %s [%02x %02x %d]\n", ids, data[95], data[93], id);
// ID printed on sonde is ...-.-abbbb, with a=id>>13, bbbb=id&0x1fff in decimal // ID printed on sonde is ...-.-abbbb, with a=id>>13, bbbb=id&0x1fff in decimal
// position data // position data
// 0x1C 4 byte // 0x1C 4 byte
sonde.si()->lat = getint32(data+28) * 1e-6; // DEGMUL like M10
sonde.si()->lat = getint32(data+28) * DEGMUL;
//0x20 4 byte //0x20 4 byte
sonde.si()->lon = getint32(data+32) * 1e-6; sonde.si()->lon = getint32(data+32) * DEGMUL;
//0x08 3 byte //0x08 3 byte
sonde.si()->alt = getint24(data+8) * VMUL_M20; sonde.si()->alt = getint24(data+8) * 0.01;
//0x0B 2 byte //0x0B 2 byte
//VMUL_M20 specific //VMUL_M20 specific
float ve = getint16(data+11)*VMUL_M20; float ve = getint16(data+11)*VMUL_M20;
@@ -310,11 +319,20 @@ int M20::decodeframeM20(uint8_t *data) {
if(dir<0) dir+=360; if(dir<0) dir+=360;
sonde.si()->dir = dir; sonde.si()->dir = dir;
sonde.si()->validPos = 0x3f; sonde.si()->validPos = 0x3f;
/*
uint32_t gpstime = getint32(data+10);
uint16_t gpsweek = getint16(data+32);
// UTC is GPSTIME - 18s (24*60*60-18 = 86382)
// one week = 7*24*60*60 = 604800 seconds
// unix epoch starts jan 1st 1970 0:00
// gps time starts jan 6, 1980 0:00. thats 315964800 epoch seconds.
// subtracting 86400 yields 315878400UL
sonde.si()->time = (gpstime/1000) + 86382 + gpsweek*604800 + 315878400UL;
*/
//0x0F 3 byte //0x0F 3 byte
uint32_t tow = getint24(data+15); uint32_t gpstime = getint24(data+15);
uint16_t week = getint16(data+26); uint16_t gpsweek = getint16(data+26);
sonde.si()->time = (tow+week*604800+315964800)-18; sonde.si()->time = gpstime + 86382 + gpsweek*604800 + 315878400UL;
sonde.si()->validTime = true; sonde.si()->validTime = true;
} else { } else {
@@ -386,7 +404,7 @@ void M20::processM20data(uint8_t dt)
int M20::receive() { int M20::receive() {
unsigned long t0 = millis(); unsigned long t0 = millis();
Serial.printf("M20::receive() start at %ld\n",t0); Serial.printf("M20::receive() start at %ld\n",t0);
while( millis() - t0 < 1512 ) { while( millis() - t0 < 1000 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2); uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) { if ( bitRead(value, 7) ) {
Serial.println("FIFO full"); Serial.println("FIFO full");
@@ -532,4 +550,3 @@ int oldwaitRXcomplete() {
#endif #endif
M20 m20 = M20(); M20 m20 = M20();
@@ -356,8 +356,7 @@ static void posrs41(const byte b[], uint32_t b_len, uint32_t p)
if (dir<0.0) dir = 360.0+dir; if (dir<0.0) dir = 360.0+dir;
sonde.si()->dir = dir; sonde.si()->dir = dir;
Serial.print(" "); Serial.print(" ");
//sonde.si()->hs = sqrt((float)(vn*vn+ve*ve))*3.6f; sonde.si()->hs = sqrt((float)(vn*vn+ve*ve))*3.6f;
sonde.si()->hs = sqrt((float)(vn*vn+ve*ve));
Serial.print(sonde.si()->hs); Serial.print(sonde.si()->hs);
Serial.print("km/h "); Serial.print("km/h ");
Serial.print(dir); Serial.print(dir);
@@ -20,21 +20,16 @@ const char *evstring[]={"NONE", "KEY1S", "KEY1D", "KEY1M", "KEY1L", "KEY2S", "KE
const char *RXstr[]={"RX_OK", "RX_TIMEOUT", "RX_ERROR", "RX_UNKNOWN"}; const char *RXstr[]={"RX_OK", "RX_TIMEOUT", "RX_ERROR", "RX_UNKNOWN"};
// Dependency to enum SondeType int fingerprintValue[]={ 17, 31, 64, 4, 55, 48, 23, 128+23, -1 };
const char *manufacturer_string[]={"Graw", "Graw", "Vaisala", "Vaisala", "Meteomodem", "Meteomodem", "Graw"};
int fingerprintValue[]={ 17, 31, 64, 4, 55, 48, 23, 128+23, 119,128+119, -1 };
const char *fingerprintText[]={ const char *fingerprintText[]={
"TTGO T-Beam (new version 1.0), I2C not working after powerup, assuming 0.9\" OLED@21,22", "TTGO T-Beam (new version 1.0), I2C not working after powerup, assuming 0.9\" OLED@21,22",
"TTGO LORA32 v2.1_1.6 (0.9\" OLED@21,22)", "TTGO LORA32 v2.1_1.6 (0.9\" OLED@21,22)",
"TTGO LORA v1.0 (0.9\" OLED@4,15)", "TTGO LORA v1.0 (0.9\" OLED@4,15)",
"Heltec v1/v2 (0.9\"OLED@4,15)", "Heltec v1/v2 (0.9\"OLED@4,15)",
"TTGO T-Beam (V0.7), 0.9\" OLED@21,22", "TTGO T-Beam (old version), 0.9\" OLED@21,22",
"TTGO T-Beam (V0.7), SPI TFT@4,21,22", "TTGO T-Beam (old version), SPI TFT@4,21,22",
"TTGO T-Beam (V1.0), 0.9\" OLED@21,22", "TTGO T-Beam (new version 1.0), 0.9\" OLED@21,22",
"TTGO T-Beam (V1.0), SPI TFT@4,13,14", "TTGO T-Beam (new version 1.0), SPI TFT@4,13,14",
"TTGO T-Beam (V1.1), 0.9\" OLED@21,22",
"TTGO T-Beam (V1.1), SPI TFT@4,13,14",
}; };
int getKeyPressEvent(); /* in RX_FSK.ino */ int getKeyPressEvent(); /* in RX_FSK.ino */
@@ -113,7 +108,6 @@ void Sonde::defaultConfig() {
//config.button2_pin = 255; //config.button2_pin = 255;
config.button2_axp = 1; config.button2_axp = 1;
config.gps_rxd = 34; config.gps_rxd = 34;
config.gps_txd = 12;
// Check for I2C-Display@21,22 // Check for I2C-Display@21,22
#define SSD1306_ADDRESS 0x3c #define SSD1306_ADDRESS 0x3c
Wire.begin(21, 22); Wire.begin(21, 22);
@@ -352,24 +346,6 @@ void Sonde::setConfig(const char *cfg) {
config.tcpfeed.highrate = atoi(val); config.tcpfeed.highrate = atoi(val);
} else if(strcmp(cfg,"tcp.idformat")==0) { } else if(strcmp(cfg,"tcp.idformat")==0) {
config.tcpfeed.idformat = atoi(val); config.tcpfeed.idformat = atoi(val);
} else if(strcmp(cfg, "sondehub.active")==0) {
config.sondehub.active = atoi(val);
} else if(strcmp(cfg,"sondehub.chase")==0) {
config.sondehub.chase = atoi(val);
} else if(strcmp(cfg, "sondehub.host")==0) {
strncpy(config.sondehub.host, val, 63);
} else if(strcmp(cfg, "sondehub.callsign")==0) {
strncpy(config.sondehub.callsign, val, 63);
} else if(strcmp(cfg, "sondehub.lat")==0) {
strncpy(config.sondehub.lat, val, 19);
} else if(strcmp(cfg, "sondehub.lon")==0) {
strncpy(config.sondehub.lon, val, 19);
} else if(strcmp(cfg, "sondehub.alt")==0) {
strncpy(config.sondehub.alt, val, 19);
} else if(strcmp(cfg, "sondehub.antenna")==0) {
strncpy(config.sondehub.antenna, val, 63);
} else if(strcmp(cfg, "sondehub.email")==0) {
strncpy(config.sondehub.email, val, 63);
} else { } else {
Serial.printf("Invalid config option '%s'=%s \n", cfg, val); Serial.printf("Invalid config option '%s'=%s \n", cfg, val);
} }
@@ -453,7 +429,6 @@ void Sonde::setup() {
break; break;
case STYPE_DFM06: case STYPE_DFM06:
case STYPE_DFM09: case STYPE_DFM09:
case STYPE_DFM:
dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type==STYPE_DFM06?0:1 ); dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type==STYPE_DFM06?0:1 );
break; break;
case STYPE_RS92: case STYPE_RS92:
@@ -490,7 +465,6 @@ void Sonde::receive() {
case STYPE_M20: case STYPE_M20:
res = m20.receive(); res = m20.receive();
break; break;
case STYPE_DFM:
case STYPE_DFM06: case STYPE_DFM06:
case STYPE_DFM09: case STYPE_DFM09:
res = dfm.receive(); res = dfm.receive();
@@ -586,7 +560,6 @@ rxloop:
case STYPE_M20: case STYPE_M20:
m20.waitRXcomplete(); m20.waitRXcomplete();
break; break;
case STYPE_DFM:
case STYPE_DFM06: case STYPE_DFM06:
case STYPE_DFM09: case STYPE_DFM09:
dfm.waitRXcomplete(); dfm.waitRXcomplete();
@@ -46,23 +46,16 @@ extern const char *RXstr[];
// 01000000 => goto sonde -1 // 01000000 => goto sonde -1
// 01000001 => goto sonde +1 // 01000001 => goto sonde +1
#define NSondeTypes 7 #define NSondeTypes 6
enum SondeType { STYPE_DFM06, STYPE_DFM09, STYPE_RS41, STYPE_RS92, STYPE_M10, STYPE_M20, STYPE_DFM }; enum SondeType { STYPE_DFM06, STYPE_DFM09, STYPE_RS41, STYPE_RS92, STYPE_M10, STYPE_M20 };
extern const char *sondeTypeStr[NSondeTypes]; extern const char *sondeTypeStr[NSondeTypes];
extern const char sondeTypeChar[NSondeTypes];
extern const char *manufacturer_string[NSondeTypes];
#define TYPE_IS_DFM(t) ( (t)==STYPE_DFM || (t)==STYPE_DFM09 || (t)==STYPE_DFM06 )
#define TYPE_IS_METEO(t) ( (t)==STYPE_M10 || (t)==STYPE_M20 )
typedef struct st_sondeinfo { typedef struct st_sondeinfo {
// receiver configuration // receiver configuration
bool active; bool active;
SondeType type; SondeType type;
int8_t subtype; /* 0 for none/unknown, hex type for dfm, maybe add 1/2 for M10/M20 as well?*/
float freq; float freq;
// decoded ID // decoded ID
char typestr[5]; // decoded type (use type if *typestr==0)
char id[10]; char id[10];
char ser[12]; char ser[12];
bool validID; bool validID;
@@ -76,8 +69,8 @@ typedef struct st_sondeinfo {
int durvolminute; // duree vol minute int durvolminute; // duree vol minute
int durvolseconde; // duree vol seconde int durvolseconde; // duree vol seconde
float alt; // altitude float alt; // altitude
float vs; // vertical speed in m/s float vs; // vertical speed
float hs; // horizontal speed in m/s float hs; // horizontal speed
float dir; // 0..360 float dir; // 0..360
uint8_t sats; // number of sats uint8_t sats; // number of sats
uint8_t validPos; // bit pattern for validity of above 7 fields; 0x80: position is old uint8_t validPos; // bit pattern for validity of above 7 fields; 0x80: position is old
@@ -98,11 +91,6 @@ typedef struct st_sondeinfo {
// shut down timers, currently only for RS41; -1=disabled // shut down timers, currently only for RS41; -1=disabled
int16_t launchKT, burstKT, countKT; int16_t launchKT, burstKT, countKT;
uint16_t crefKT; // frame number in which countKT was last sent uint16_t crefKT; // frame number in which countKT was last sent
// sonde specific extra data, NULL if unused or not yet initialized, currently used for RS41 subframe data (calibration)
void *extra;
float temperature = -300.0; // platinum resistor temperature
float tempRHSensor = -300.0; // temperature of relative humidity sensor
float relativeHumidity = -1.0; // relative humidity
} SondeInfo; } SondeInfo;
// rxStat: 3=undef[empty] 1=timeout[.] 2=errro[E] 0=ok[|] 4=no valid position[°] // rxStat: 3=undef[empty] 1=timeout[.] 2=errro[E] 0=ok[|] 4=no valid position[°]
@@ -161,18 +149,6 @@ struct st_kisstnc {
int idformat; int idformat;
}; };
//SondeHub
struct st_sondehub {
int active;
int chase;
char host[64];
char callsign[64];
char lat[20];
char lon[20];
char alt[20];
char antenna[64];
char email[64];
};
typedef struct st_rdzconfig { typedef struct st_rdzconfig {
// hardware configuration // hardware configuration
@@ -194,7 +170,6 @@ typedef struct st_rdzconfig {
char gps_lon[20]; // QTH no gps longitude char gps_lon[20]; // QTH no gps longitude
int gps_alt; // QTH no gps altitude int gps_alt; // QTH no gps altitude
float gps_Lat,gps_Lon, gps_Alt; // QTH gps OM lat, lon, alt float gps_Lat,gps_Lon, gps_Alt; // QTH gps OM lat, lon, alt
bool gps_Valid;
// software configuration // software configuration
int debug; // show port and config options after reboot int debug; // show port and config options after reboot
int wifi; // connect to known WLAN 0=skip int wifi; // connect to known WLAN 0=skip
@@ -230,7 +205,6 @@ typedef struct st_rdzconfig {
struct st_feedinfo udpfeed; // target for AXUDP messages struct st_feedinfo udpfeed; // target for AXUDP messages
struct st_feedinfo tcpfeed; // target for APRS-IS TCP connections struct st_feedinfo tcpfeed; // target for APRS-IS TCP connections
struct st_kisstnc kisstnc; // target for KISS TNC (via TCP, mainly for APRSdroid) struct st_kisstnc kisstnc; // target for KISS TNC (via TCP, mainly for APRSdroid)
struct st_sondehub sondehub;
} RDZConfig; } RDZConfig;
@@ -317,7 +317,7 @@ char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym) {
char comm[100]; char comm[100];
snprintf(comm, 100, "Clb=%.1fm/s %.3fMHz Type=%s", s->vs, s->freq, sondeTypeStr[s->type]); snprintf(comm, 100, "Clb=%.1fm/s %.3fMHz Type=%s", s->vs, s->freq, sondeTypeStr[s->type]);
strcat(b, comm); strcat(b, comm);
if(s->type==STYPE_M20||s->type==STYPE_M10||s->type==STYPE_DFM||s->type==STYPE_DFM06||s->type==STYPE_DFM09) { if(s->type==STYPE_M10||s->type==STYPE_DFM06||s->type==STYPE_DFM09) {
snprintf(comm, 100, " ser=%s", s->ser); snprintf(comm, 100, " ser=%s", s->ser);
strcat(b, comm); strcat(b, comm);
} }
@@ -136,13 +136,9 @@ void geteph() {
int len=0; int len=0;
while(dclient.connected()) { while(dclient.connected()) {
while(dclient.available()) { while(dclient.available()) {
int c = dclient.read(); char c = dclient.read();
if(c==-1) { fh.write(c);
Serial.println("dclient.read() returned -1 inspite of available() being true?!"); len++;
} else {
fh.write(c);
len++;
}
} }
} }
Serial.printf("fetched %d bytes\n", len); Serial.printf("fetched %d bytes\n", len);
-657
View File
@@ -1,657 +0,0 @@
/* DFM decoder functions */
#include "DFM.h"
#include "SX1278FSK.h"
#include "Sonde.h"
#define DFM_DEBUG 1
#if DFM_DEBUG
#define DFM_DBG(x) x
#else
#define DFM_DBG(x)
#endif
#define DFM_FRAMELEN 33
// single data structure, search restarts after decoder change
static struct st_dfmstat {
int idcnt0;
int idcnt1;
int lastfrid;
int lastfrcnt;
uint8_t start[50];
uint16_t dat[50*2];
uint8_t cnt[50*2];
uint8_t nameregok;
uint8_t nameregtop;
} dfmstate;
int DFM::setup(float frequency, int type)
{
stype = type;
#if DFM_DEBUG
Serial.printf("Setup sx1278 for DFM sonde (type=%d)\n", stype);
#endif
if(sx1278.ON()!=0) {
DFM_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
DFM_DBG(Serial.println("Setting FSM mode FAILED"));
return 1;
}
if(sx1278.setBitrate(2500)!=0) {
DFM_DBG(Serial.println("Setting bitrate 2500bit/s FAILED"));
return 1;
}
#if DFM_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.dfm.agcbw)!=0) {
DFM_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.dfm.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.dfm.rxbw)!=0) {
DFM_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.dfm.rxbw));
return 1;
}
if(type == STYPE_DFM09 || type == STYPE_DFM06) {
// packet mode, old version, misses some frames because chip enables rx too late after
// one frame was recevied. TODO: check if this can be fixed by changing parameters
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
DFM_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
//char header[] = "0110.0101 0110.0110 1010.0101 1010.1010";
const char *SYNC=(stype==STYPE_DFM09)?"\x9A\x99\x5A\x55":"\x65\x66\xA5\xAA";
if(sx1278.setSyncConf(0x53, 4, (const uint8_t *)SYNC)!=0) {
DFM_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
//if(sx1278.setPreambleDetect(0xA8)!=0) {
if(sx1278.setPreambleDetect(0xAA)!=0) {
DFM_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// Packet config 1: fixed len, mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x28, 0x40)!=0) {
DFM_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
sx1278.setPayloadLength(33); // Expect 33 bytes (7+13+13 bytes)
} else {
// continuous mode
// Enable auto-AFC, auto-AGC, RX Trigger by preamble ????
if(sx1278.setRxConf(0x1E)!=0) {
DFM_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// working with continuous RX
const char *SYNC="\xAA\xAA";
if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
DFM_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
//if(sx1278.setPreambleDetect(0x9F)!=0) {
DFM_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
DFM_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
sx1278.setPayloadLength(0); // infinite for now...
}
Serial.print("DFM: setting RX frequency to ");
Serial.println(frequency);
int retval = sx1278.setFrequency(frequency);
sx1278.clearIRQFlags();
// Do this only once in setup in continous mode
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
memset((void *)&dfmstate, 0, sizeof(dfmstate));
DFM_DBG(Serial.println("Setting SX1278 config for DFM finished\n"); Serial.println());
return retval;
}
#define bitpick(value,bitpos) (((value)>>(7-(bitpos)))&0x01)
// Input: str: packed data, MSB first
void DFM::deinterleave(uint8_t *str, int L, uint8_t *block) {
int i, j;
for (j = 0; j < B; j++) { // L = 7 (CFG), 13 (DAT1, DAT2)
for (i = 0; i < L; i++) {
block[B*i+j] = bitpick( str[(L*j+i)/8], (L*j+i)&7 )?0:1;
}
}
}
uint32_t DFM::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
val |= (bits[j] << (len-1-j));
}
return val;
}
// Error correction for hamming code
// returns 0: ok >0: 1 error was corrected -1: uncorrectable error
int DFM::check(uint8_t code[8]) {
int i, j;
uint32_t synval = 0;
uint8_t syndrom[4];
int ret=0;
for (i = 0; i < 4; i++) {
syndrom[i] = 0;
for (j = 0; j < 8; j++) {
syndrom[i] ^= H[i][j] & code[j];
}
}
synval = bits2val(syndrom, 4);
if (synval) {
ret = -1;
for (j = 0; j < 8; j++) { // 1-bit-error
if (synval == He[j]) { // reicht auf databits zu pruefen, d.h.
ret = j+1; // (systematischer Code) He[0..3]
break;
}
}
}
else ret = 0;
if (ret > 0) code[ret-1] ^= 0x1;
return ret;
}
// Extended (8,4) Hamming code
// Return number of corrected bits, -1 if uncorrectable error
int DFM::hamming(uint8_t *ham, int L, uint8_t *sym) {
int i, j;
int ret = 0; // DFM: length L = 7 or 13
for (i = 0; i < L; i++) { // L bytes (4bit data, 4bit parity)
if (use_ecc) {
int res = check(ham+8*i);
if(ret>=0 && res>=0) ret += res; else ret=-1;
}
// systematic Hamming code: copy bits 0..3
for (j = 0; j < 4; j++) {
sym[4*i+j] = ham[8*i+j];
}
}
return ret;
}
DFM::DFM() {
}
void DFM::printRaw(const char *label, int len, int ret, const uint8_t *data)
{
Serial.print(label); Serial.print("(");
Serial.print(ret);
Serial.print("):");
int i;
for(i=0; i<len/2; i++) {
char str[10];
snprintf(str, 10, "%02X", data[i]);
Serial.print(str);
}
Serial.print(data[i]&0x0F, HEX);
Serial.print(" ");
}
const char* typestr[16]={
"", "", "", "", "", "", // 00..05
"DFM6", // 06 => DFM6
"PS15", // 07 => PS15 (untested)
"", "",
"DFM9", // 0A => DFM9
"DF17", // 0B => DFM17?
"DF9P", // 0C => DFM9P or DFM17 test
"DF17", // 0D => DFM17
"", ""
};
#define DFMIDTHRESHOLD 2
/* inspired by oe5dxl's finddnmae in sondeudp.c of dxlaprs */
void DFM::finddfname(uint8_t *b)
{
uint8_t st;
uint32_t thres;
uint32_t i;
uint8_t ix;
uint16_t d;
st = b[0]; /* frame start byte */
ix = b[3]; /* hi/lo part of ser; (LSB due to our bitsToBytes...) */
d = (b[1]<<8) + b[2]; /* data byte */
/* find highest channel number single frame serial,
(2 frame serial will make a single serial too) */
if(dfmstate.idcnt0 < DFMIDTHRESHOLD && dfmstate.idcnt1 < DFMIDTHRESHOLD) {
uint32_t v = (st<<20) | (d<<4) | ix;
if ( st > (dfmstate.lastfrid>>20) ) {
dfmstate.lastfrid = v;
Serial.print(" MAXCH:"); Serial.print(st);
dfmstate.lastfrcnt = 0;
} else if ( st == (dfmstate.lastfrid>>20) ) {
/* same id found */
if (v == dfmstate.lastfrid) {
++dfmstate.lastfrcnt;
thres = DFMIDTHRESHOLD * 2;
/* may be a 2 frame serial so increase safety level */
if (ix <= 1) thres *= 2;
/* may be not a dfm6 so increase safety level */
if ( (st>>4) != 6) thres *= 2;
if (dfmstate.lastfrcnt >= thres) {
/* id found */
if (dfmstate.lastfrcnt == thres) {
uint32_t id = ((st&0x0F)<<20) | (d<<4) | ix;
uint32_t chkid = id;
int i;
/* check validity */
for(i=0; i<6; i++) {
if((chkid&0x0f)>9) { break; /* not ok */ }
chkid >>= 4;
}
if(i==6) {
snprintf(sonde.si()->id, 10, "D%x ", id);
sonde.si()->validID = true;
strncpy(sonde.si()->typestr, typestr[ (st>>4)&0x0F ], 5);
return;
}
dfmstate.lastfrcnt = 0;
Serial.print(" NOT NUMERIC SERIAL");
}
//anonym->idtime = osic_time();
} else {
Serial.print(" MAXCHCNT/SECLVL:");
Serial.print(dfmstate.lastfrcnt);
Serial.print("/");
Serial.print(thres);
}
} else {
dfmstate.lastfrid = v; /* not stable ser */
dfmstate.lastfrcnt = 0UL;
}
}
} /*find highest channel number single frame serial */
i = 0;
while (i<dfmstate.nameregtop && dfmstate.start[i]!=st) i++;
Serial.printf(" %02x:i=%d,top=%d", st, i, dfmstate.nameregtop);
if (i<dfmstate.nameregtop) {
if (ix<=1UL && (dfmstate.cnt[2*i+ix]==0 || dfmstate.dat[2*i+ix]==d)) {
dfmstate.dat[2*i+ix] = d;
if(dfmstate.cnt[2*i+ix] < 255) dfmstate.cnt[2*i+ix]++;
Serial.print(" ID:");
Serial.print(st, HEX);
Serial.print("[");
Serial.print(ix);
Serial.print("] CNT:");
Serial.print(dfmstate.cnt[2*i]);
Serial.print(",");
Serial.print(dfmstate.cnt[2*i+1]);
Serial.print(",st=");
Serial.print(st);
Serial.print(",lastfrid=");
Serial.print(dfmstate.lastfrid>>20);
if( (dfmstate.cnt[2*i]>DFMIDTHRESHOLD && dfmstate.cnt[2*i+1]>DFMIDTHRESHOLD) ||
(dfmstate.cnt[2*i]>0 && dfmstate.cnt[2*i+1]>0 && st == (dfmstate.lastfrid>>20) && (st>>4)>6) ) {
if(dfmstate.idcnt0 <= 1) {
dfmstate.idcnt0 = dfmstate.cnt[2*i];
dfmstate.idcnt1 = dfmstate.cnt[2*i+1];
dfmstate.nameregok = i;
// generate id.....
snprintf(sonde.si()->id, 10, "D%d", ((dfmstate.dat[2*i]<<16)|dfmstate.dat[2*i+1])%100000000);
Serial.print("\nNEW AUTOID:");
Serial.println(sonde.si()->id);
sonde.si()->validID = true;
strncpy(sonde.si()->typestr, typestr[ (st>>4)&0x0F ], 5);
}
if(dfmstate.nameregok==i) {
Serial.print(" ID OK");
// idtime = .... /* TODO */
}
}
} else {
/* data changed so not ser */
dfmstate.cnt[2*i] = 0;
dfmstate.cnt[2*i+1] = 0;
if(dfmstate.nameregok == i) { /* found id wrong */
dfmstate.idcnt0 = 0;
dfmstate.idcnt1 = 0;
}
}
} else if (ix<=1) { /* add new entry for possible ID */
dfmstate.start[dfmstate.nameregtop] = st;
dfmstate.cnt[2*dfmstate.nameregtop] = 0;
dfmstate.cnt[2*dfmstate.nameregtop+1] = 0;
dfmstate.cnt[2*dfmstate.nameregtop+ix] = 1;
dfmstate.dat[2*dfmstate.nameregtop+ix] = d;
if(dfmstate.nameregtop<49) dfmstate.nameregtop++;
}
}
void DFM::decodeCFG(uint8_t *cfg)
{
#if 1
// new ID
finddfname(cfg);
// new aprs ID (dxlaprs, autorx) is now "D" + serial (8 digits) by consensus
memcpy(sonde.si()->ser, sonde.si()->id+1, 9);
#else
// old ID
static int lowid, highid, idgood=0, type=0;
if((cfg[0]>>4)==0x06 && type==0) { // DFM-6 ID
lowid = ((cfg[0]&0x0F)<<20) | (cfg[1]<<12) | (cfg[2]<<4) | (cfg[3]&0x0f);
Serial.print("DFM-06 ID: "); Serial.print(lowid, HEX);
snprintf(sonde.si()->id, 10, "%x", lowid);
sonde.si()->validID = true;
}
if((cfg[0]>>4)==0x0A) { // DMF-9 ID
type=9;
if(cfg[3]==1) {
lowid = (cfg[1]<<8) | cfg[2];
idgood |= 1;
} else {
highid = (cfg[1]<<8) | cfg[2];
idgood |= 2;
}
if(idgood==3) {
uint32_t dfmid = (highid<<16) | lowid;
Serial.print("DFM-09 ID: "); Serial.print(dfmid);
snprintf(sonde.si()->ser, 10, "%d", dfmid);
// dxlAPRS sonde number (DF6 (why??) and 5 last digits of serial number as hex number
snprintf(sonde.si()->id, 9, "DF6%05X", dfmid&0xfffff);
sonde.si()->validID = true;
}
}
#endif
}
static int bitCount(int x) {
int m4 = 0x1 | (0x1<<8) | (0x1<<16) | (0x1<<24);
int m1 = 0xFF;
int s4 = (x&m4) + ((x>>1)&m4) + ((x>>2)&m4) + ((x>>3)&m4) + ((x>>4)&m4) + ((x>>5)&m4) + ((x>>6)&m4) + ((x>>7)&m4);
int s1 = (s4&m1) + ((s4>>8)&m1) + ((s4>>16)&m1) + ((s4>>24)&m1);
return s1;
}
static uint16_t MON[]={0,0,31,59,90,120,151,181,212,243,273,304,334};
void DFM::decodeDAT(uint8_t *dat)
{
Serial.print(" DAT["); Serial.print(dat[6]); Serial.print("]: ");
switch(dat[6]) {
case 0:
Serial.print("Packet counter: "); Serial.print(dat[3]);
sonde.si()->frame = dat[3];
break;
case 1:
{
int val = (((uint16_t)dat[4])<<8) + (uint16_t)dat[5];
Serial.print("UTC-msec: "); Serial.print(val);
sonde.si()->sec = val/1000;
uint32_t tmp = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
sonde.si()->sats = bitCount(tmp); // maybe!?!?!?
}
break;
case 2:
{
float lat, vh;
lat = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
vh = ((uint16_t)dat[4]<<8) + dat[5];
Serial.print("GPS-lat: "); Serial.print(lat*0.0000001);
Serial.print(", hor-V: "); Serial.print(vh*0.01);
sonde.si()->lat = lat*0.0000001;
sonde.si()->hs = vh*0.01;
sonde.si()->validPos |= 0x11;
}
break;
case 3:
{
float lon, dir;
lon = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + (uint32_t)dat[3];
dir = ((uint16_t)dat[4]<<8) + dat[5];
Serial.print("GPS-lon: "); Serial.print(lon*0.0000001);
Serial.print(", dir: "); Serial.print(dir*0.01);
sonde.si()->lon = lon*0.0000001;
sonde.si()->dir = dir*0.01;
sonde.si()->validPos |= 0x42;
}
break;
case 4:
{
float alt, vv;
alt = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + dat[3];
vv = (int16_t)( ((int16_t)dat[4]<<8) | dat[5] );
Serial.print("GPS-height: "); Serial.print(alt*0.01);
Serial.print(", vv: "); Serial.print(vv*0.01);
sonde.si()->alt = alt*0.01;
sonde.si()->vs = vv*0.01;
sonde.si()->validPos |= 0x0C;
}
break;
case 8:
{
int y = (dat[0]<<4) + (dat[1]>>4);
int m = dat[1]&0x0F;
int d = dat[2]>>3;
int h = ((dat[2]&0x07)<<2) + (dat[3]>>6);
int mi = (dat[3]&0x3F);
char buf[100];
snprintf(buf, 100, "%04d-%02d-%02d %02d:%02dz", y, m, d, h, mi);
Serial.print("Date: "); Serial.print(buf);
// convert to unix time
int tt = (y-1970)*365 + (y-1969)/4; // days since 1970
if(m<=12) { tt += MON[m]; if((y%4)==0 && m>2) tt++; }
tt = (tt+d-1)*(60*60*24) + h*3600 + mi*60;
sonde.si()->time = tt;
}
break;
default:
Serial.print("(?)");
break;
}
}
void DFM::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
//Serial.print(bits[i]?"1":"0");
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static int haveNewFrame = 0;
int DFM::processDFMdata(uint8_t dt) {
static uint8_t data[1024];
static uint32_t rxdata = 0;
static uint8_t rxbitc = 0;
static uint8_t rxbyte = 0;
static uint8_t rxsearching = 1;
static uint8_t rxp;
static int rssi=0, fei=0, afc=0;
static uint8_t invers = 0;
for(int i=0; i<8; i++) {
uint8_t d = (dt&0x80)?1:0;
dt <<= 1;
rxdata = (rxdata<<1) | d;
if( (rxbitc&1)==0 ) {
// "bit1"
rxbyte = (rxbyte<<1) | d;
} else {
// "bit2" ==> 01 or 10 => 1, otherweise => 0
// not here: (10=>1, 01=>0)!!! rxbyte = rxbyte ^ d;
}
//
if(rxsearching) {
if( rxdata == 0x6566A5AA || rxdata == 0x9A995A55 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
rxbyte = 0;
rssi=sx1278.getRSSI();
fei=sx1278.getFEI();
afc=sx1278.getAFC();
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
invers = (rxdata == 0x6566A5AA)?1:0;
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
if(invers) rxbyte=~rxbyte;
data[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
if(rxp>=DFM_FRAMELEN) {
rxsearching = true;
//Serial.println("Got a DFM frame!");
Serial.print("[RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.print(afc); Serial.print("] ");
decodeFrameDFM(data);
haveNewFrame = 1;
}
}
}
}
return 0;
}
int DFM::receive() {
if( stype == STYPE_DFM ) {
return receiveNew();
} else {
return receiveOld();
}
}
int DFM::receiveNew() {
int rxframes = 4;
// tentative continuous RX version...
unsigned long t0 = millis();
while( ( millis() - t0 ) < 1000 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
// new: (maybe clear only overflow??) TODO
sx1278.clearIRQFlags();
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: payload ready()");
// does not make much sence? (from m10): TODO
// ??????? sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
processDFMdata(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
#if 0
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
#endif
if(haveNewFrame) {
//Serial.printf("DFM::receive(): new frame complete after %ldms\n", millis()-t0);
haveNewFrame = 0;
rxframes--;
if(rxframes==0) return RX_OK;
}
delay(2);
}
}
return RX_TIMEOUT;
}
int DFM::receiveOld() {
byte data[1000]; // pending data from previous mode may write more than 33 bytes. TODO.
for(int i=0; i<2; i++) {
sx1278.setPayloadLength(33); // Expect 33 bytes (7+13+13 bytes)
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
//int t = millis();
int e = sx1278.receivePacketTimeout(1000, data);
//Serial.printf("rxPTO done after %d ms", (int)(millis()-t));
if(e) { return RX_TIMEOUT; } //if timeout... return 1
if(!(stype==STYPE_DFM09)) { for(int i=0; i<33; i++) { data[i]^=0xFF; } }
decodeFrameDFM(data);
}
return RX_OK;
}
int DFM::decodeFrameDFM(uint8_t *data) {
deinterleave(data, 7, hamming_conf);
deinterleave(data+7, 13, hamming_dat1);
deinterleave(data+20, 13, hamming_dat2);
#if 0
Serial.print("RAWCFG:");
for(int i=0; i<7*8; i++) {
Serial.print(hamming_conf[i]?"1":"0");
}
#endif
int ret0 = hamming(hamming_conf, 7, block_conf);
int ret1 = hamming(hamming_dat1, 13, block_dat1);
int ret2 = hamming(hamming_dat2, 13, block_dat2);
byte byte_conf[4], byte_dat1[7], byte_dat2[7];
bitsToBytes(block_conf, byte_conf, 7);
bitsToBytes(block_dat1, byte_dat1, 13);
bitsToBytes(block_dat2, byte_dat2, 13);
printRaw("CFG", 7, ret0, byte_conf);
printRaw("DAT", 13, ret1, byte_dat1);
printRaw("DAT", 13, ret2, byte_dat2);
decodeCFG(byte_conf);
decodeDAT(byte_dat1);
decodeDAT(byte_dat2);
Serial.println("");
return RX_OK;
}
// moved to a single function in Sonde(). This function can be used for additional
// processing here, that takes too long for doing in the RX task loop
int DFM::waitRXcomplete() {
#if 0
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult < 0 && millis()-t0 < 2000) { delay(50); }
if( rxtask.receiveResult<0 || rxtask.receiveResult==RX_TIMEOUT) {
res = RX_TIMEOUT;
} else if ( rxtask.receiveResult ==0) {
res = RX_OK;
} else {
res = RX_ERROR;
}
rxtask.receiveResult = -1;
Serial.printf("waitRXcomplete returning %d\n", res);
return res;
#endif
return 0;
}
DFM dfm = DFM();