new master version

Merging all recent devel updates into master:

    Fix stupid bug that caused RS92 to not work with last devel version
    python3 compatibility
    Experimental: Modified DFM handling (DFM17 vs DFM09P for 0xC), untested
    Add support for RS41 short measurement frame 0x7F (#353)
    fix #342: remove bug in code for right-justified text on OLED display
    fix GD5 (untested)
    handle display off timeouts > 127 seconds correctly
    gps from apk: 0/0 as invalid pos
    set dhcp dns name to mdns name; preparation of configurable map link (dl2mf merge)
    sync w/ DL2MF: web interface enhancements
    fatal bug--
master
Hansi, dl9rdz 2023-07-09 12:10:53 +02:00
rodzic dad746846e
commit 6cf330b143
17 zmienionych plików z 278 dodań i 122 usunięć

Wyświetl plik

@ -1,9 +1,15 @@
language: c
dist: focal
os: linux
env:
global:
- python /home/travis/.arduino15/packages/esp32/tools/esptool_py/3.0.0/esptool.py
- ESP32TOOLS=/home/travis/.arduino15/packages/esp32/hardware/esp32/1.0.6/tools
- MKSPIFFS=/home/travis/.arduino15/packages/esp32/tools/mkspiffs/0.2.3/mkspiffs
before_install:
- sudo apt-get install python-is-python3
- pip uninstall pyserial
- pip install pyserial
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16"
- sleep 3
- export DISPLAY=:1.0

Wyświetl plik

@ -19,14 +19,18 @@
#include "src/Display.h"
#include "src/Scanner.h"
#include "src/geteph.h"
#if FEATURE_RS92
#include "src/rs92gps.h"
#endif
#include "src/aprs.h"
#include "src/ShFreqImport.h"
#include "src/RS41.h"
#include "src/DFM.h"
#include "src/json.h"
#if FEATURE_CHASEMAPPER
#include "src/Chasemapper.h"
#endif
//#include "NimBLEDevice.h"
#if FEATURE_MQTT
#include "src/mqtt.h"
@ -67,6 +71,9 @@ WiFiClient client;
/* Sonde.h: enum SondeType { STYPE_DFM,, STYPE_RS41, STYPE_RS92, STYPE_M10M20, STYPE_M10, STYPE_M20, STYPE_MP3H }; */
const char *sondeTypeStrSH[NSondeTypes] = { "DFM", "RS41", "RS92", "Mxx"/*never sent*/, "M10", "M20", "MRZ" };
#if 0
// not used any more
const char *dfmSubtypeStrSH[16] = { NULL, NULL, NULL, NULL, NULL, NULL,
"DFM06", // 0x06
"PS15", // 0x07
@ -77,6 +84,7 @@ const char *dfmSubtypeStrSH[16] = { NULL, NULL, NULL, NULL, NULL, NULL,
"DFM17", // 0x0D
NULL, NULL
};
#endif
// Times in ms, i.e. station: 10 minutes, mobile: 20 seconds
#define APRS_STATION_UPDATE_TIME (10*60*1000)
@ -108,7 +116,7 @@ extern float calcLatLonDist(float lat1, float lon1, float lat2, float lon2);
// KISS over TCP for communicating with APRSdroid
WiFiServer tncserver(14580);
WiFiClient tncclient;
// JSON over TCP for communicating with my kotlin andoird test stuff
// JSON over TCP for communicating with the rdzSonde (rdzwx-go) Android app
WiFiServer rdzserver(14570);
WiFiClient rdzclient;
// APRS over TCP for radiosondy.info etc
@ -308,7 +316,10 @@ void HTMLBODYEND(char *ptr) {
strcat(ptr, "</div></form></body></html>");
}
void HTMLSAVEBUTTON(char *ptr) {
strcat(ptr, "</div><div class=\"footer\"><input type=\"submit\" class=\"save\" value=\"Save changes\"/>");
strcat(ptr, "</div><div class=\"footer\"><input type=\"submit\" class=\"save\" value=\"Save changes\"/>"
"<span class=\"ttgoinfo\">rdzTTGOserver ");
strcat(ptr, version_id);
strcat(ptr, "</span>");
}
const char *createQRGForm() {
@ -419,7 +430,8 @@ void setupWifiList() {
const char *createWIFIForm() {
char *ptr = message;
char tmp[4];
strcpy(ptr, HTMLHEAD); strcat(ptr, "</head>");
strcpy(ptr, HTMLHEAD);
strcat(ptr, "<script src=\"rdz.js\"></script></head>");
HTMLBODY(ptr, "wifi.html");
strcat(ptr, "<table><tr><th>Nr</th><th>SSID</th><th>Password</th></tr>");
for (int i = 0; i < MAX_WIFI; i++) {
@ -430,7 +442,7 @@ const char *createWIFIForm() {
i + 1, i < nNetworks ? networks[i].id.c_str() : "",
i + 1, i < nNetworks ? networks[i].pw.c_str() : "");
}
strcat(ptr, "</table>");
strcat(ptr, "</table><script>footer()</script>");
//</div><div class=\"footer\"><input type=\"submit\" class=\"update\" value=\"Update\"/>");
HTMLSAVEBUTTON(ptr);
HTMLBODYEND(ptr);
@ -438,6 +450,8 @@ const char *createWIFIForm() {
return message;
}
#if 0
// moved to map.html (active warning is still TODO
const char *createSondeHubMap() {
SondeInfo *s = &sonde.sondeList[0];
char *ptr = message;
@ -460,6 +474,7 @@ const char *createSondeHubMap() {
HTMLBODYEND(ptr);
return message;
}
#endif
const char *handleWIFIPost(AsyncWebServerRequest *request) {
char label[10];
@ -504,7 +519,7 @@ void addSondeStatus(char *ptr, int i)
{
struct tm ts;
SondeInfo *s = &sonde.sondeList[i];
strcat(ptr, "<table>");
strcat(ptr, "<table class=\"stat\">");
sprintf(ptr + strlen(ptr), "<tr><td id=\"sfreq\">%3.3f MHz, Type: %s</td><tr><td>ID: %s", s->freq, sondeTypeLongStr[sonde.realType(s)],
s->d.validID ? s->d.id : "<?""?>");
if (s->d.validID && (TYPE_IS_DFM(s->type) || TYPE_IS_METEO(s->type) || s->type == STYPE_MP3H) ) {
@ -525,13 +540,15 @@ void addSondeStatus(char *ptr, int i)
sprintf(ptr + strlen(ptr), "<a target=\"_empty\" href=\"https://www.openstreetmap.org/?mlat=%.6f&mlon=%.6f&zoom=14\">OSM</a> - ", s->d.lat, s->d.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->d.lat, s->d.lon);
strcat(ptr, "</table><p/>\n");
strcat(ptr, "</table>\n");
}
const char *createStatusForm() {
char *ptr = message;
strcpy(ptr, HTMLHEAD);
strcat(ptr, "<meta http-equiv=\"refresh\" content=\"5\"></head><body>");
strcat(ptr, "<meta http-equiv=\"refresh\" content=\"5\"></head>");
HTMLBODY(ptr, "status.html");
strcat(ptr, "<div class=\"content\">");
for (int i = 0; i < sonde.config.maxsonde; i++) {
int snum = (i + sonde.currentSonde) % sonde.config.maxsonde;
@ -539,7 +556,12 @@ const char *createStatusForm() {
addSondeStatus(ptr, snum);
}
}
strcat(ptr, "</body></html>");
strcat(ptr, "</div><div class=\"footer\"><span></span>"
"<span class=\"ttgoinfo\">rdzTTGOserver ");
strcat(ptr, version_id);
strcat(ptr, "</span>");
HTMLBODYEND(ptr);
Serial.printf("Status form: size=%d bytes\n", strlen(message));
return message;
}
@ -716,7 +738,8 @@ const int N_CONFIG = (sizeof(config_list) / sizeof(struct st_configitems));
const char *createConfigForm() {
char *ptr = message;
strcpy(ptr, HTMLHEAD); strcat(ptr, "</head>");
strcpy(ptr, HTMLHEAD);
strcat(ptr, "<script src=\"rdz.js\"></script></head>");
HTMLBODY(ptr, "config.html");
strcat(ptr, "<div id=\"cfgtab\"></div>");
strcat(ptr, "<script src=\"cfg.js\"></script>");
@ -760,6 +783,7 @@ const char *createConfigForm() {
strcat(ptr, "\");\n");
}
strcat(ptr, "configTable();\n </script>");
strcat(ptr, "<script>footer()</script>");
HTMLSAVEBUTTON(ptr);
HTMLBODYEND(ptr);
Serial.printf("Config form: size=%d bytes\n", strlen(message));
@ -827,7 +851,8 @@ const char *ctrllabel[] = {"Receiver/next freq. (short keypress)", "Scanner (dou
const char *createControlForm() {
char *ptr = message;
strcpy(ptr, HTMLHEAD); strcat(ptr, "</head>");
strcpy(ptr, HTMLHEAD);
strcat(ptr, "</head>");
HTMLBODY(ptr, "control.html");
for (int i = 0; i < 9; i++) {
strcat(ptr, "<input class=\"ctlbtn\" type=\"submit\" name=\"");
@ -839,6 +864,10 @@ const char *createControlForm() {
strcat(ptr, "<p></p>");
}
}
strcat(ptr, "</div><div class=\"footer\"><span></span>"
"<span class=\"ttgoinfo\">rdzTTGOserver ");
strcat(ptr, version_id);
strcat(ptr, "</span>");
HTMLBODYEND(ptr);
Serial.printf("Control form: size=%d bytes\n", strlen(message));
return message;
@ -1179,13 +1208,14 @@ void SetupAsyncServer() {
request->send(200, "text/html", createWIFIForm());
});
server.on("/map.html", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(200, "text/html", createSondeHubMap());
});
server.on("/map.html", HTTP_POST, [](AsyncWebServerRequest * request) {
handleWIFIPost(request);
request->send(200, "text/html", createSondeHubMap());
});
// server.on("/map.html", HTTP_GET, [](AsyncWebServerRequest * request) {
// request->send(200, "text/html", createSondeHubMap());
// });
// server.on("/map.html", HTTP_POST, [](AsyncWebServerRequest * request) {
// handleWIFIPost(request);
// request->send(200, "text/html", createSondeHubMap());
// });
server.on("/config.html", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(200, "text/html", createConfigForm());
@ -1477,6 +1507,7 @@ void gpsTask(void *parameter) {
gpsPos.course = lastCourse;
}
}
if(gpsPos.lon == 0 && gpsPos.lat == 0) gpsPos.valid = false;
}
gpsPos.hdop = nmea.getHDOP();
gpsPos.sat = nmea.getNumSatellites();
@ -1855,12 +1886,17 @@ void heap_caps_alloc_failed_hook(size_t requested_size, uint32_t caps, const cha
void setup()
{
char buf[12];
// Open serial communications and wait for port to open:
Serial.begin(/*921600 */115200);
for (int i = 0; i < 39; i++) {
int v = gpio_get_level((gpio_num_t)i);
Serial.printf("%d:%d ", i, v);
}
//NimBLEDevice::init("NimBLE-Arduino");
//NimBLEServer* pServer = NimBLEDevice::createServer();;
Serial.println("");
#ifdef ESP_MEM_DEBUG
esp_err_t error = heap_caps_register_failed_alloc_callback(heap_caps_alloc_failed_hook);
@ -1868,27 +1904,6 @@ void setup()
axpSemaphore = xSemaphoreCreateBinary();
xSemaphoreGive(axpSemaphore);
#if 0
delay(2000);
// temporary test
volatile uint32_t *ioport = portOutputRegister(digitalPinToPort(4));
uint32_t portmask = digitalPinToBitMask(4);
int t = millis();
for (int i = 0; i < 10000000; i++) {
digitalWrite(4, LOW);
digitalWrite(4, HIGH);
}
int res = millis() - t;
Serial.printf("Duration w/ digitalWriteo: %d\n", res);
t = millis();
for (int i = 0; i < 10000000; i++) {
*ioport |= portmask;
*ioport &= ~portmask;
}
res = millis() - t;
Serial.printf("Duration w/ fast io: %d\n", res);
#endif
for (int i = 0; i < 39; i++) {
Serial.printf("%d:%d ", i, initlevels[i]);
}
@ -1905,6 +1920,7 @@ void setup()
Serial.println("Reading initial configuration");
setupConfigData(); // configuration must be read first due to OLED ports!!!
WiFi.setHostname(sonde.config.mdnsname);
// NOT TTGO v1 (fingerprint 64) or Heltec v1/v2 board (fingerprint 4)
// and NOT TTGO Lora32 v2.1_1.6 (fingerprint 31/63)
@ -2245,6 +2261,7 @@ void parseGpsJson(char *data) {
value = NULL;
}
}
if(gpsPos.lat == 0 && gpsPos.lon == 0) gpsPos.valid = false;
Serial.printf("Parse result: lat=%f, lon=%f, alt=%d, valid=%d\n", gpsPos.lat, gpsPos.lon, gpsPos.alt, gpsPos.valid);
}
@ -2956,6 +2973,7 @@ void loopWifiScan() {
sonde.setIP(localIPstr.c_str(), false);
sonde.updateDisplayIP();
wifi_state = WIFI_CONNECTED;
#if FEATURE_RS92
bool hasRS92 = false;
for (int i = 0; i < MAXSONDE; i++) {
if (sonde.sondeList[i].type == STYPE_RS92) hasRS92 = true;
@ -2965,6 +2983,7 @@ void loopWifiScan() {
if (ephstate == EPH_PENDING) ephstate = EPH_ERROR;
get_eph("/brdc");
}
#endif
delay(3000);
}
enableNetwork(true);
@ -3274,7 +3293,7 @@ void aprs_station_update() {
lon = sonde.config.rxlon;
if (isnan(lat) || isnan(lon)) return;
} else {
if (gpsPos.valid && gpsPos.lat != 0 && gpsPos.lon != 0) {
if (gpsPos.valid) {
lat = gpsPos.lat;
lon = gpsPos.lon;
} else {
@ -3360,7 +3379,7 @@ void sondehub_station_update(WiFiClient * client, struct st_sondehub * conf) {
// We send GPS position: (a) in CHASE mode, (b) in AUTO mode if no fixed location has been specified in config
if (chase == SH_LOC_CHASE) {
if (gpsPos.valid && gpsPos.lat != 0 && gpsPos.lon != 0) {
if (gpsPos.valid) {
sprintf(w,
"\"uploader_position\": [%.6f,%.6f,%d],"
"\"mobile\": true",
@ -3635,11 +3654,14 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
}
/* if there is a subtype (DFM only) */
if ( TYPE_IS_DFM(s->type) && s->d.subtype > 0 && s->d.subtype < 16 ) {
const char *t = dfmSubtypeStrSH[s->d.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->d.subtype); // Unknown subtype
if ( TYPE_IS_DFM(s->type) && s->d.subtype > 0 ) {
if( (s->d.subtype&0xF) != DFM_UNK) {
const char *t = dfmSubtypeLong[s->d.subtype&0xF];
sprintf(w, "\"subtype\": \"%s\",", t);
}
else {
sprintf(w, "\"subtype\": \"DFMx%X\",", s->d.subtype>>4); // Unknown subtype
}
w += strlen(w);
} else if ( s->type == STYPE_RS41 ) {
char buf[11];
@ -3687,7 +3709,7 @@ void sondehub_send_data(WiFiClient * client, SondeInfo * s, struct st_sondehub *
// We send GPS position: (a) in CHASE mode, (b) in AUTO mode if no fixed location has been specified in config
if (chase == SH_LOC_CHASE) {
if (gpsPos.valid && gpsPos.lat != 0 && gpsPos.lon != 0) {
if (gpsPos.valid) {
sprintf(w, "\"uploader_position\": [%.6f,%.6f,%d]", gpsPos.lat, gpsPos.lon, gpsPos.alt);
} else {
sprintf(w, "\"uploader_position\": [null,null,null]");

Wyświetl plik

@ -9,16 +9,15 @@
</head>
<body>
<div class="wrapper"><div class="header">
<h2>rdzTTGOSonde Server</h2>
<div class="topnav" id="myTopnav">
<a href="#" onclick="selTab(event,'QRG')" class="tablinks" id="defaultTab">QRG</a>
<a href="#" onclick="selTab(event,'WiFi')" class="tablinks">WiFi</a>
<a href="#" onclick="selTab(event,'Data')" class="tablinks">Data</a>
<a href="#" onclick="document.location.href='livemap.html'" class="tablinks">LiveMap</a>
<a href="#" onclick="selTab(event,'Map')" class="tablinks">Map</a>
<a href="#" onclick="selTab(event,'Config')" class="tablinks">Config</a>
<a href="#" onclick="selTab(event,'Control')" class="tablinks">Control</a>
<a href="#" onclick="selTab(event,'About')" class="tablinks">About</a>
<a href="#qrg" onclick="selTab(event,'QRG')" class="tablinks" id="defaultTab">QRG</a>
<a href="#data" onclick="selTab(event,'Data')" class="tablinks">Data</a>
<a href="#map" onclick="selTab(event,'Map')" class="tablinks">Map</a>
<a href="#livemap" onclick="document.location.href='livemap.html'" class="tablinks">LiveMap</a>
<a href="#ctrl" onclick="selTab(event,'Control')" class="tablinks">Control</a>
<a href="#config" onclick="selTab(event,'Config')" class="tablinks">Config</a>
<a href="#wifi" onclick="selTab(event,'WiFi')" class="tablinks">WiFi</a>
<a href="#about" onclick="selTab(event,'About')" class="tablinks">About</a>
<a href="javascript:void(0);" class="icon" onclick="myFunction()">
<span class="hamburger"></span>
</a>
@ -52,7 +51,7 @@
<div id="About" class="tabcontent">
<div class="tci">
%VERSION_NAME%<br>
Copyright &copy; 2019-2021 by Hansi Reiser, DL9RDZ<br>
Copyright &copy; 2019-2022 by Hansi Reiser, DL9RDZ<br>
(version %VERSION_ID%)<br><br>
<a href="/upd.html">Check for update (requires TTGO internet connection via WiFi)</a><br><br>
@ -76,6 +75,7 @@
See <a href="https://www.gnu.org/licenses/gpl-2.0.txt">https://www.gnu.org/licenses/gpl-2.0.txt</a>
for details.
</div>
<div class="footer"><span></span><span class="ttgoinfo">rdzTTGOserver %VERSION_ID%</span></div>
</div>
</div>

Wyświetl plik

@ -0,0 +1,32 @@
<html>
<head>
<script>
maptype = "SH"; // TODO: Get from config
const maps = {
"WS": ["https://www.wettersonde.net/map.php", (s)=>"?sonde="+s, (lat,lon)=>""],
"SH": ["https://sondehub.org/", (s)=>s, (lat,lon)=>'#!mz=8&mc=' + lat + ',' + lon],
"RS": ["https://radiosondy.info/", (s)=>"sonde.php?sondenumber="+s, (lat,lon)=>""],
"OW": ["https://v2.openwx.de/start.php", (s)=>"?sonde="+s, (lat,lon)=>"?mode=mobile"],
};
data = ["T4420541",63.5,-20.0];
document.addEventListener("DOMContentLoaded", function(){
fetch('live.json')
.then((response) => response.json())
.then((data) => {
console.log(data.gps.lat, data.gps.lon, data.sonde.ser);
if( (data.sonde.ser||'')=='' && !(data.gps.lat===undefined) ) {
urlarg = maps[maptype][2](data.gps.lat, data.gps.lon);
} else {
urlarg = maps[maptype][1](data.sonde.ser||'');
}
iftxt='<iframe src="' + maps[maptype][0] + urlarg + '" style="border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%"></iframe>';
document.getElementsByTagName('body')[0].innerHTML = iftxt;
})
})
</script>
</head>
<body>
</body>
</html>

Wyświetl plik

@ -3,10 +3,19 @@ stypes.set('4', 'RS41');
stypes.set('R', 'RS92');
stypes.set('D', 'DFM');
stypes.set('M', 'M10/M20');
//stypes.set('2', 'M10/M20');
stypes.set('3', 'MP3H');
/* (no longer) Used by qrg.html in RX_FSK.ino */
function footer() {
document.addEventListener("DOMContentLoaded", function(){
var form = document.querySelector(".wrapper");
form.addEventListener("input", function() {
document.querySelector(".save").disabled = false;
});
document.querySelector(".save").disabled = true;
});
}
/* Used by qrg.html in RX_FSK.ino */
function prep() {
var stlist=document.querySelectorAll("input.stype");
for(txt of stlist){
@ -28,16 +37,18 @@ function prep() {
function qrgTable() {
var tab=document.getElementById("divTable");
var table = "<table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>";
var table = "<table><tr><th>Ch</th><th>Active</th><th>Frequency</th><th>Decoder</th><th>Launchsite</th></tr>";
for(i=0; i<qrgs.length; i++) {
var ck = "";
if(qrgs[i][0]) ck="checked";
table += "<tr><td>" + (i+1) + "</td><td><input name=\"A" + (i+1) + "\" type=\"checkbox\" " + ck + "/></td>";
table += "<td><input name=\"F" + (i+1) + "\" type=\"text\" width=12 value=\"" + qrgs[i][1] + "\"></td>";
table += "<td><input name=\"S" + (i+1) + "\" type=\"text\" value=\"" + qrgs[i][2] +"\"></td>";
table += "<td><input class=\"stype\" name=\"T" + (i+1) + "\" value=\"" + qrgs[i][3] + "\"></td></tr>";
table += "<tr><td class=\"ch\">" + (i+1) + "</td><td class=\"act\"><input name=\"A" + (i+1) + "\" type=\"checkbox\" " + ck + "/></td>";
table += "<td><input name=\"F" + (i+1) + "\" type=\"text\" size=7 value=\"" + qrgs[i][1] + "\"></td>";
table += "<td><input class=\"stype\" name=\"T" + (i+1) + "\" value=\"" + qrgs[i][3] + "\"></td>";
table += "<td><input name=\"S" + (i+1) + "\" type=\"text\" value=\"" + qrgs[i][2] +"\"></td></tr>";
}
table += "</table>";
tab.innerHTML = table;
prep();
footer();
}

Wyświetl plik

@ -13,6 +13,9 @@ body, html {
th.cfg {
padding:5pt
}
table.stat {
margin:0px 0px 5px 0px;
}
.hamburger {
position: relative;
@ -43,8 +46,18 @@ th.cfg {
flex-grow: 1; border: none; margin: 0; padding: 0;
}
.footer {
background-color: #333;
display: flex;
justify-content: space-between;
}
td.ch {
text-align: right;
padding: 0px 8px;
}
td.act {
text-align: center;
}
table, th, td {
border: 1px solid black;
border-collapse: collapse;
@ -71,7 +84,6 @@ td#sfreq {
.tabcontent {
display: none;
flex: 1;
padding: 6px 12px;
border-top: none;
flex-direction: column;
overflow: auto;
@ -90,24 +102,16 @@ h1{
p{
font-size: 1.5rem;
}
.canberemoved_button {
display: inline-block;
background-color: #008CBA;
border: none;
border-radius: 4px;
color: white;
padding: 16px 40px;
text-decoration: none;
font-size: 30px;
margin: 2px;
cursor: pointer;
}
.button2 {
background-color: #f44336;
}
:disabled.save {
opacity: 0.5;
}
.save {
background-color: #0F3376;
border: black;
background-color: #CC1111; /* 0F33C6; */
border: white;
border-width: 1;
color: white;
padding: 8px 30px;
@ -117,6 +121,14 @@ p{
font-size: 14px;
margin: 0
}
.ttgoinfo {
color: white;
padding: 8px 10px;
display: block;
font-size: 14px;
margin: 0
}
.ctlbtn {
background-color: #ccc;
border: black;

Wyświetl plik

@ -16,6 +16,13 @@
#define MAXIDAGE 1800
const char *dfmSubtypeLong[] = { "", "DFMxx", "DFM06", "DFM06P", "PS15",
"DFM09", "DFM09P", "DFM17", "DFM17P"};
const char *dfmSubtypeShort[] = { "", "DFMx", "DFM6", "DF6P", "PS15",
"DFM9", "DF9P", "DF17", "D17P"};
/*
* observed DAT patterns for DFM-9:
* A: 0+1; 2+3; 4+5; 6+7; 8+0 => keep frame in shadowFrame
@ -44,7 +51,10 @@ static struct st_dfmstat {
uint8_t nameregtop;
uint8_t lastdat;
uint8_t cycledone; // 0=no; 1=OK, 2=partially/with errors
float meas[5+2];
float meas[9];
uint16_t measok; // Bit-mask showing which meas entries have been received
uint8_t ptu_chan; // always the max channel. used as subtype before, but 0xC can be DFM09 (with P) or DFM17 (w/o P)
uint8_t sensP; // P channel in data (0xC DMF09 (but not 0xC DFM17), 0xD DFM17P, 0x8 DFM6P (but not PS15) (0 or 1)
} dfmstate;
decoderSetupCfg DFMSetupCfg {
@ -280,8 +290,12 @@ void DFM::finddfname(uint8_t *b)
snprintf(sd->id, 10, "D%x ", id);
memcpy(sd->ser, sd->id+1, 9);
sd->validID = true;
sd->subtype = (st>>4)&0x0F;
strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
//sd->subtype = (st>>4)&0x0F;
//strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
// Subtype is set later, as we need more data to distingish 0xC DFM09P and DFM17
sd->subtype = 0;
dfmstate.ptu_chan = (st>>4)&0x0F;
strcpy(sd->typestr, "DFM");
return;
}
dfmstate.lastfrcnt = 0;
@ -332,8 +346,12 @@ void DFM::finddfname(uint8_t *b)
Serial.println(sd->id);
memcpy(sd->ser, sd->id+1, 9);
sd->validID = true;
sd->subtype = (st>>4)&0x0F;
strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
//sd->subtype = (st>>4)&0x0F;
//strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
// Subtype is set later, as we need more data to distingish 0xC DFM09P and DFM17
sd->subtype = 0;
dfmstate.ptu_chan = (st>>4)&0x0F;
strcpy(sd->typestr, "DFM");
}
if(dfmstate.nameregok==i) {
Serial.print(" ID OK");
@ -361,13 +379,13 @@ void DFM::finddfname(uint8_t *b)
static float get_Temp() {
SondeData *si = &(sonde.si()->d);
if(!si->validID) { // type not yet known, so don't try to decode
if(!si->subtype) { // type not yet known, so don't try to decode
return NAN;
}
float f = dfmstate.meas[0],
f1 = dfmstate.meas[3],
f2 = dfmstate.meas[4];
if(si->subtype >= 0x0C) {
if(dfmstate.sensP) {
f = dfmstate.meas[1];
f1 = dfmstate.meas[5];
f2 = dfmstate.meas[6];
@ -377,7 +395,8 @@ static float get_Temp() {
float BB0 = 3260.0; // B/Kelvin, fit -55C..+40C
float T0 = 25 + 273.15; // t0=25C
float R0 = 5.0e3; // R0=R25=5k
float Rf = 220e3; // Rf = 220k
float Rf = 220e3; // Rf = 220k, for DFM17: 332k
if( si->subtype==DFM_17 || si->subtype==DFM_17P ) Rf = 332e3;
float g = f2/Rf;
float R = (f-f1) / g; // meas[0,3,4] > 0 ?
float T = 0; // T/Kelvin
@ -398,16 +417,41 @@ void DFM::decodeCFG(uint8_t *cfg)
finddfname(cfg);
// get meas
uint8_t conf_id = (*cfg)>>4;
if(conf_id<=6) {
if(conf_id<=8) {
uint32_t val = (cfg[1]<<12) | (cfg[2]<<4) | cfg[3];
uint8_t exp = cfg[0] & 0xF;
dfmstate.meas[conf_id] = val / (float)(1<<exp);
dfmstate.measok |= (1 << conf_id);
Serial.printf("meas %d is %f (%d,%d)\n", conf_id, dfmstate.meas[conf_id], val, exp);
}
// get type (if we have an ID, but no type yet, and (needed only for 0xC), meas[6])
if( si->validID && si->subtype==0 && (dfmstate.measok & (1<<6)) ) {
switch(dfmstate.ptu_chan) {
case 0x6: si->subtype = DFM_06; break;
case 0x7: case 0x8:
si->subtype = DFM_06P; dfmstate.sensP = 1; break; // (TODO: OR PS15)
case 0xA: si->subtype = DFM_09; break;
case 0xB: si->subtype = DFM_17; break;
case 0xC: // DFM-09P or DFM-17
if(dfmstate.meas[6]<220e3) { si->subtype = DFM_09P; dfmstate.sensP = 1; }
else si->subtype = DFM_17;
break;
case 0xD: si->subtype = DFM_17P; dfmstate.sensP = 1; break;
default: si->subtype = DFM_UNK;
}
if( si->subtype == DFM_UNK ) {
snprintf(si->typestr, 5, "DFx%x", dfmstate.ptu_chan);
si->subtype |= (dfmstate.ptu_chan<<4);
} else {
strcpy(si->typestr, dfmSubtypeShort[si->subtype]);
}
}
// get batt
if(si->validID && si->subtype>=0x0A) {
if(si->validID && dfmstate.ptu_chan>=0x0A && si->subtype>0 ) {
// otherwise don't try, as we might not have the right type yet...
int cid = (si->subtype >= 0x0C) ? 0x7 : 0x5;
int cid = (dfmstate.sensP) ? 0x7 : 0x5;
if(conf_id == cid) {
uint16_t val = cfg[1]<<8 | cfg[2];
si->batteryVoltage = val / 1000.0;

Wyświetl plik

@ -20,6 +20,11 @@
#define DFM_NORMAL 0
#define DFM_INVERSE 1
enum DFMSubtype { DFM_UNDEF, DFM_UNK, DFM_06, DFM_06P, DFM_PS15, DFM_09, DFM_09P, DFM_17, DFM_17P };
extern const char *dfmSubtypeLong[];
extern const char *dfmSubtypeShort[];
/* Main class */
class DFM : public DecoderBase
{

Wyświetl plik

@ -318,7 +318,7 @@ void U8x8Display::drawString(uint16_t x, uint16_t y, const char *s, int16_t widt
}
if(width<0) {
int l = strlen(buf);
memset(buf, ' ', width-l);
memset(buf, ' ', -width-l);
utf2latin15(s, buf+l, 50-l);
}
u8x8->drawString(x, y, buf);
@ -1554,12 +1554,12 @@ void Display::drawGPS(DispEntry *de) {
// equirectangular approximation is good enough
if( !VALIDPOS(sonde.si()->d.validPos) ) {
snprintf(buf, 16, "no pos ");
if(de->extra && *de->extra=='5') buf[5]=0;
if( de->extra[1]=='5') buf[5]=0;
} else if( disp.gpsDist < 0 ) {
snprintf(buf, 16, "no gps ");
if(de->extra && *de->extra=='5') buf[5]=0;
if( de->extra[1]=='5') buf[5]=0;
} else {
if(de->extra && *de->extra=='5') { // 5-character version: ****m / ***km / **e6m
if( de->extra[1]=='5') { // 5-character version: ****m / ***km / **e6m
if(disp.gpsDist>999999) snprintf(buf, 16, "%de6m ", (int)(disp.gpsDist/1000000));
if(disp.gpsDist>9999) snprintf(buf, 16, "%dkm ", (int)(disp.gpsDist/1000));
else snprintf(buf, 16, "%dm ", (int)disp.gpsDist);

Wyświetl plik

@ -163,7 +163,7 @@ public:
DispInfo *layouts;
int nLayouts;
static RawDisplay *rdis;
char dispstate;
uint16_t dispstate;
Display();
void init();

Wyświetl plik

@ -790,6 +790,7 @@ int RS41::decode41(byte *data, int maxlen)
posrs41(data+p, len, 0);
break;
case 'z': // 0x7a is character z - 7A-MEAS temperature and humidity frame
case '\x7f': //0x7f - short MEAS, no pressure
{
uint32_t tempMeasMain = getint24(data, 560, p+0);
uint32_t tempMeasRef1 = getint24(data, 560, p+3);
@ -800,15 +801,23 @@ int RS41::decode41(byte *data, int maxlen)
uint32_t tempHumiMain = getint24(data, 560, p+18);
uint32_t tempHumiRef1 = getint24(data, 560, p+21);
uint32_t tempHumiRef2 = getint24(data, 560, p+24);
uint32_t pressureMain = getint24(data, 560, p+27);
uint32_t pressureRef1 = getint24(data, 560, p+30);
uint32_t pressureRef2 = getint24(data, 560, p+33);
int16_t ptraw = getint16(data, 560, p+38);
uint32_t pressureMain;
uint32_t pressureRef1;
uint32_t pressureRef2;
int16_t ptraw;
if (typ == 'z') {
pressureMain = getint24(data, 560, p+27);
pressureRef1 = getint24(data, 560, p+30);
pressureRef2 = getint24(data, 560, p+33);
ptraw = getint16(data, 560, p+38);
}
#if 0
Serial.printf( "External temp: tempMeasMain = %ld, tempMeasRef1 = %ld, tempMeasRef2 = %ld\n", tempMeasMain, tempMeasRef1, tempMeasRef2 );
Serial.printf( "Rel Humidity: humidityMain = %ld, humidityRef1 = %ld, humidityRef2 = %ld\n", humidityMain, humidityRef1, humidityRef2 );
Serial.printf( "Humid sensor: tempHumiMain = %ld, tempHumiRef1 = %ld, tempHumiRef2 = %ld\n", tempHumiMain, tempHumiRef1, tempHumiRef2 );
Serial.printf( "Pressure sens: pressureMain = %ld, pressureRef1 = %ld, pressureRef2 = %ld\n", pressureMain, pressureRef1, pressureRef2 );
if (typ == 'z') {
Serial.printf( "Pressure sens: pressureMain = %ld, pressureRef1 = %ld, pressureRef2 = %ld\n", pressureMain, pressureRef1, pressureRef2 );
}
#endif
struct subframeBuffer *calibration = (struct subframeBuffer *)(sonde.si()->extra);
// temp: 0xF8==bits 3..7 : we need refResistorlow/high, taylorT, polyT
@ -818,7 +827,7 @@ int RS41::decode41(byte *data, int maxlen)
bool validHumidity = calibration!=NULL && (calibration->valid & 0x7FE2001FFFF8) == 0x7FE2001FFFF8;
// pressure: bits 33 and 37..42 (variant; x25..x2a: matrixP) /// CALIB_P is 0x7E200000000)
bool validPressure = calibration!=NULL && (calibration->valid & CALIB_P)==CALIB_P && calibration->value.names.variant[7]=='P';
bool validPressure = calibration!=NULL && (calibration->valid & CALIB_P)==CALIB_P && calibration->value.names.variant[7]=='P' && (typ == 'z');
if ( validPressure ) {
si->pressure = GetRAP( pressureMain, pressureRef1, pressureRef2, ptraw );

Wyświetl plik

@ -1,9 +1,12 @@
#include <U8x8lib.h>
#include <U8g2lib.h>
#include "../features.h"
#include "Sonde.h"
#include "RS41.h"
#if FEATURE_RS92
#include "RS92.h"
#endif
#include "DFM.h"
#include "M10M20.h"
#include "MP3H.h"
@ -431,7 +434,9 @@ void Sonde::setup() {
dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type );
break;
case STYPE_RS92:
#if FEATURE_RS92
rs92.setup( sondeList[rxtask.currentSonde].freq * 1000000);
#endif
break;
case STYPE_M10:
case STYPE_M20:
@ -462,7 +467,9 @@ void Sonde::receive() {
res = rs41.receive();
break;
case STYPE_RS92:
#if FEATURE_RS92
res = rs92.receive();
#endif
break;
case STYPE_M10:
case STYPE_M20:
@ -562,7 +569,9 @@ rxloop:
rs41.waitRXcomplete();
break;
case STYPE_RS92:
#if FEATURE_RS92
rs92.waitRXcomplete();
#endif
break;
case STYPE_M10:
case STYPE_M20:

Wyświetl plik

@ -1,19 +1,22 @@
#include "json.h"
#include "RS41.h"
#include "DFM.h"
extern const char *sondeTypeStrSH[];
extern const char *dfmSubtypeStrSH[];
//extern const char *dfmSubtypeStrSH[];
static char typestr[11];
const char *getType(SondeInfo *si) {
if( si->type == STYPE_RS41 ) {
if ( RS41::getSubtype(typestr, 11, si) == 0 ) return typestr;
} else if ( TYPE_IS_DFM(si->type) && si->d.subtype > 0 && si->d.subtype < 16 ) {
const char *t = dfmSubtypeStrSH[si->d.subtype];
if(t) return t;
sprintf(typestr, "DFMx%X", si->d.subtype);
return typestr;
} else if ( TYPE_IS_DFM(si->type) && si->d.subtype > 0 ) {
const char *t = dfmSubtypeLong[si->d.subtype & 0xf];
if( (si->d.subtype & 0xf) == DFM_UNK) {
sprintf(typestr, "DFMx%X", si->d.subtype>>4);
return typestr;
}
return t;
}
return sondeTypeStrSH[sonde.realType(si)];
}
@ -96,7 +99,7 @@ int sonde2json(char *buf, int maxlen, SondeInfo *si)
// add only if available
if(s->batteryVoltage > 0) {
n = snprintf(buf, maxlen, ",\"bat\": %.1f", s->batteryVoltage);
n = snprintf(buf, maxlen, ",\"batt\": %.1f", s->batteryVoltage);
if(n>=maxlen) return -1;
buf += n; maxlen -= n;
}

Wyświetl plik

@ -6,11 +6,11 @@
#include "RS41.h"
#include "json.h"
TimerHandle_t mqttReconnectTimer;
extern const char *version_name;
extern const char *version_id;
TimerHandle_t mqttReconnectTimer;
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
@ -51,12 +51,14 @@ void MQTT::publishUptime()
mqttClient.connect(); // ensure we've got connection
Serial.println("[MQTT] writing");
//char payload[128];
//snprintf(payload, 12, "%lu", millis());
//snprintf(payload, 124, "{\"uptime\": %lu," "\"user\": \"%s\"", millis(), username );
char payload[128];
snprintf(payload, 128, "{\"uptime\": %ld, \"user\": \"%s\", \"rxlat\": %.5f, \"rxlon\": %.5f, \"ver\": \"%s\", \"sub\": \"%s\"}",
millis(), username, sonde.config.rxlat, sonde.config.rxlon, version_name, version_id);
char payload[256];
// maybe TODO: Use dynamic position if GPS is available?
// rxlat, rxlon only if not empty
snprintf(payload, 256, "{\"uptime\": %lu, \"user\": \"%s\", ", millis(), username);
if( !isnan(sonde.config.rxlat) && !isnan(sonde.config.rxlon) ) {
snprintf(payload, 256, "%s\"rxlat\": %.5f, \"rxlon\": %.5f, ", payload, sonde.config.rxlat, sonde.config.rxlon);
}
snprintf(payload, 256, "%s\"SW\": \"%s\", \"VER\": \"%s\"}", payload, version_name, version_id);
Serial.println(payload);
char topic[128];
snprintf(topic, 128, "%s%s", this->prefix, "uptime");

Wyświetl plik

@ -1,4 +1,4 @@
const char *version_name = "rdzTTGOsonde";
const char *version_id = "master_v0.9.2";
const char *version_id = "master_v0.9.3";
const int SPIFFS_MAJOR=2;
const int SPIFFS_MINOR=16;
const int SPIFFS_MINOR=17;

Wyświetl plik

@ -26,6 +26,7 @@ lib_deps_external =
[env:ttgo-lora32]
platform = https://github.com/platformio/platform-espressif32.git#v3.3.2
#platform = https://github.com/platformio/platform-espressif32.git#v4.4.0
board = ttgo-lora32-v1
framework = arduino
monitor_speed = 115200

Wyświetl plik

@ -13,7 +13,7 @@ import subprocess
#spiffs, data, spiffs, 0x290000,0x170000,
MKSPIFFS = os.environ['MKSPIFFS']
print "mkspiffs is "+MKSPIFFS
print("mkspiffs is "+MKSPIFFS)
OFFSET_BOOTLOADER = 0x1000
OFFSET_PARTITIONS = 0x8000
@ -33,7 +33,7 @@ partition = esp32tools + "/partitions/default.csv"
if os.path.isfile("RX_FSK/partitions.csv"):
partition = "RX_FSK/partitions.csv"
with open(partition, 'rb') as csvfile:
with open(partition, 'rt') as csvfile:
partreader = csv.reader(csvfile, delimiter=',')
for row in partreader:
if row[0] == "otadata":
@ -44,9 +44,9 @@ with open(partition, 'rb') as csvfile:
OFFSET_SPIFFS = int(row[3],16)
SIZE_SPIFFS = int(row[4],16)
print "bootapp0: "+hex(OFFSET_BOOTAPP0)
print "app0: "+hex(OFFSET_APPLICATION)
print "spiffs: "+hex(OFFSET_SPIFFS)+" size "+hex(SIZE_SPIFFS)
print("bootapp0: "+hex(OFFSET_BOOTAPP0))
print("app0: "+hex(OFFSET_APPLICATION))
print("spiffs: "+hex(OFFSET_SPIFFS)+" size "+hex(SIZE_SPIFFS))
# create binary partition
file_part = "/tmp/partition.bin"