Compare commits

..

No commits in common. "0.8.8" and "1.0.0" have entirely different histories.
0.8.8 ... 1.0.0

99 changed files with 15891 additions and 8468 deletions

BIN
Display.pdf Executable file → Normal file

Binary file not shown.

147
README.md
View File

@ -1,9 +1,10 @@
RadioSonde Version 0.8.8
RadioSonde Version 1.0.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/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/TTGO2.jpg" width="50%">
<img src="http://xavier.debert.free.fr/RS/TTGO4.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/Web3.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%">
@ -11,66 +12,121 @@
Projet basé sur le travail de DL9RDZ
====================================
Pour TTGO LORA 32 esp32 pico D4
Décodage de RadioSonde RS41 and DFM06/09 et M10
Pour TTGO LORA 32 esp32 pico D4 <br>
Décodage de RadioSonde RS41/92 and DFM06/09/17 et M10+/20 et MP3H
Attention à la version de votre TTGO!
vous devez modifier dans config.txt, le port de l'écran OLED
- TTGO v1: SDA=4 SCL=15, RST=16
- TTGO v2: SDA=21 SCL=22, RST=16
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 <br>
- puis pour le GPS:<br>
Lilygo esp32 GPS pin 34 Rx, 12 Tx<br>
- puis Buzzer<br>
par defaut 4 si gps mettre 2 <br>
- puis led<br>
par defaut 25 <br>
## Version en production 0.8.5 devel 0.8.7
## 1ère Mise en route
## Wifi configuration
## 0.8.0
travail de refonte et réécriture du code
Au démarrage, si aucune connexion possible du wifi paramètré, il monte un Wifi AP<br>
le SSID et mot de passe par défaut est: Radiosonde<br>
en mode AP, il doit être en 192.168.4.1<br>
Une fois connécté au Wifi du TTGO<br>
ouvrir une page web sur http://192.168.4.1<br>
ou aussi la possibilité de mettre http://radiosonde.local<br>
## Version en production 1.0.0 devel 1.0.1
## 1.0.0
refonte du system OS, et des pages Web<br>
toujours avec la gestion:<br>
- du buzzer<br>
- du db ou smetre<br>
- de la vbat nouvelle gestion(plus necessaire calibrage)<br>
- suppression du mode telemetry export csv<br>
- suppression de la bousole<br>
- importation des RadioSonde Thank's DL9RDZ<br>
- gestion update OTA<br>
## 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.5
## 0.8.0
Evolution majeur du système
affichage du pourcentage de la batterie en mode scanning
création d'une fenetre Batterie, Boussole
suppresion lib et code TFT
création Azimute, elevation correction de Bugs majeur , mineur
Ajout fonction Smetre, Buzzer, QTH, Gps on off ...
mise à jour OTA
trop de modification pour toutes les expliciter!
travail de refonte et réécriture du code
## 0.8.7
correction bug Buzzer Off->On->Off
Add GainLNA RX SX1278FSK on Web config paramètre
Add update OTA Os + DataWeb
correction bugs sondmap.html
correction text upgrade Os et DataWeb
correction texte boussole S et N
correction bugs distance 4928Km si lat et lon =0 erroné
correction bugs fonction Vbat
Add Telemetry width export data.csv
Suppression µSD incompatible avec pin SX1278FSK et SPI
Add transfert Telemetry To µSD on put SD automatic
## 0.8.8
Add M10+
-------------------------------------------------------------------------------
## Les Boutons optionnel à ajouter(souder)
sur les GPIO 1002 et 1004
sur les GPIO 1002 et 1004 <br>
attention:
+5V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms
+3.3V--[ SW ]---GPIO----[ R1 ]---/ R1=10 ou 12KOhms
- appuie court <1.5 seconds
- appuie double court 0.5 seconds
- appuie moyen 2-4 seconds
- 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
le SSID et mot de passe par défaut est: Radiosonde
en mode AP, il doit être en 192.168.4.1,
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é.
@ -108,4 +164,3 @@ voir Setup.md pour l'installation!
73
Xavier

3874
RX_FSK/RX_FSK.ino Normal file

File diff suppressed because it is too large Load Diff

BIN
RX_FSK/data.tar Normal file

Binary file not shown.

1
RX_FSK/data/GPSRESET Normal file
View File

@ -0,0 +1 @@
+

177
RX_FSK/data/cfg.js Normal file
View File

@ -0,0 +1,177 @@
var cfgs = [
[ "", "General configuration", "" ],
[ "wifi", "Wifi mode (0=off, 1=client, 2=AP, 3=client or AP autoselect on startup)" ],
[ "mdnsname", "Network mDNS name"],
[ "ephftp", "FTP server for ephemeris data (RS92 decoder)"],
[ "debug", "Debug mode (0/1)" ],
[ "maxsonde", "Maximum number of QRG entries (must be &leq; 50)" ],
[ "rxlat", "Receiver fixed latitude"],
[ "rxlon", "Receiver fixed longitude"],
[ "rxalt", "Receiver fixed altitude"],
[ "", "OLED display configuration", "" ],
[ "screenfile", "Screen config (0=automatic; 1-2=OLED predefined; other=custom)" ],
[ "display", "Display screens (scan, default, ...)" ],
[ "dispsaver", "Display saver (0=never/1=always/2=ifnorx [+10*n: after n sec.])" ],
[ "dispcontrast", "OLED contrast (-1=use default; 0..255=set contrast)" ],
[ "norx_timeout", "No-RX-timeout in seconds (-1=disabled)"],
[ "", "Spectrum display configuration", "" ],
[ "spectrum", "Show spectrum on start (-1=no, 0=forever, >0=time [sec])" ],
[ "startfreq", "Start frequency (MHz, default 400)" ],
[ "channelbw", "Bandwidth (kHz)" ],
[ "marker", "Spectrum MHz marker" ], // maybe remove, assume always ==1?
[ "noisefloor", "Spectrum noisefloor" ],
[ "", "Receiver configuration", "" ],
[ "freqofs", "RX frequency offset (Hz)"],
[ "rs41.agcbw", "RS41 AGC bandwidth"],
[ "rs41.rxbw", "RS41 RX bandwidth"],
[ "rs92.rxbw", "RS92 RX (and AGC) bandwidth"],
[ "rs92.alt2d", "RS92 2D fix default altitude"],
[ "dfm.agcbw", "DFM AGC bandwidth"],
[ "dfm.rxbw", "DFM RX bandwidth"],
[ "m10m20.agcbw", "M10/M20 AGC bandwidth"],
[ "m10m20.rxbw", "M10/M20 RX bandwidth"],
[ "mp3h.agcbw", "MP3H AGC bandwidth"],
[ "mp3h.rxbw", "MP3H RX bandwidth"],
[ "", "KISS TNC/AXUDP/AXTCP data feed configuration", ""],
[ "call", "Call"],
[ "passcode", "Passcode"],
[ "kisstnc.active", "KISS TNC (port 14590) (needs reboot)"],
[ "axudp.active", "AXUDP active"],
[ "axudp.host", "AXUDP host"],
[ "axudp.port", "AXUDP port"],
[ "axudp.highrate", "Rate limit"],
[ "tcp.active", "APRS TCP active"],
[ "tcp.host", "APRS TCP host"],
[ "tcp.port", "APRS TCP port"],
[ "tcp.highrate", "Rate limit"],
[ "tcp.objcall", "APRS object call"],
[ "tcp.beaconsym", "APRS tracker symbol"],
[ "tcp.chase", "APRS location reporting (0=off, 1=fixed, 2=chase/GPS, 3=auto)"],
[ "tcp.comment", "APRS location comment"],
[ "", "MQTT data feed configuration", ""],
[ "mqtt.active", "MQTT active (needs reboot)"],
[ "mqtt.id", "MQTT client ID"],
[ "mqtt.host", "MQTT server hostname"],
[ "mqtt.port", "MQTT port"],
[ "mqtt.username", "MQTT username"],
[ "mqtt.password", "MQTT password"],
[ "mqtt.prefix", "MQTT prefix"],
[ "", "Chasemapper settings", ""],
[ "cm.active", "Chasemapper active (0=disabled, 1=active)"],
[ "cm.host", "Chasemapper UDP host"],
[ "cm.port", "Chasemapper UDP port"],
[ "", "SondeHub settings", ""],
[ "sondehub.active", "SondeHub reporting (0=disabled, 1=active)"],
[ "sondehub.chase", "SondeHub location reporting (0=off, 1=fixed, 2=chase/GPS, 3=auto)"],
[ "sondehub.host", "SondeHub host (DO NOT CHANGE)"],
[ "sondehub.callsign", "Callsign"],
[ "sondehub.antenna", "Antenna (optional, visisble on SondeHub tracker)"],
[ "sondehub.email", "SondeHub email (optional, only used to contact in case of upload errors)"],
[ "", "SondeHub frequency import", "" ],
[ "sondehub.fiactive", "SondeHub frequency import active (0=disabled, 1=active)" ],
[ "sondehub.fiinterval", "Import frequency (minutes, &geq; 5)" ],
[ "sondehub.fimaxdist", "Import maximum distance (km, &leq; 700)" ],
[ "sondehub.fimaxage", "Import maximum age (hours, &leq; 48)" ],
[ "", "Hardware configuration (requires reboot)", ""],
[ "disptype", "Display type (0=OLED/SSD1306, 2=OLED/SH1106, 5=ST7789)"],
[ "oled_sda", "OLED SDA/TFT SDA"],
[ "oled_scl", "OLED SCL/TFT CLK"],
[ "oled_rst", "OLED RST/TFT RST (needs reboot)"],
[ "button_pin", "Button input port"],
[ "button2_pin", "Button 2 input port"],
[ "button2_axp", "Use AXP192 PWR as Button 2"],
[ "touch_thresh", "Touch button threshold<br>(0 for calib mode)"],
[ "power_pout", "Power control port"],
[ "led_pout", "LED output port (25)"],
["buzzerOn", "Buzzer (0=Disabled, 1 Enable)"],
["buzzerPort", "Buzzer Port (4)"],
["buzzerFreq", "Buzzer Frequency (700)"],
["dbsmetre", "dB = 0 / Smetre =1)"],
["degdec", "Lat,Lon Decimal =0 ou Degres =1)"],
[ "gps_rxd", "GPS RXD pin (-1 to disable)"],
[ "gps_txd", "GPS TXD pin (not really needed)"],
[ "batt_adc", "Battery measurement pin"],
[ "sx1278_ss", "SX1278 SS"],
[ "sx1278_miso", "SX1278 MISO"],
[ "sx1278_mosi", "SX1278 MOSI"],
[ "sx1278_sck", "SX1278 SCK"],
];
function mkcfg(id, key, label, value) {
var s = "<tr style=\"visibility: collapse;\" class=\"cfgpanel\"><td>" + label + "</td><td><input name=\"" + key + "\" type=\"text\" value=\"" + value + "\"/></td></tr>\n";
return s;
}
function mkcfgbtn(id, key, label, value) {
var touch = "";
var v = value;
if(v != -1 && (v&128)) {
touch = " checked";
v = v & 127;
}
var s = "<tr style=\"visibility:collapse\" class=\"cfgpanel\"><td>" + label + "</td><td><input name=\"" + key + "\" type=\"text\" size=\"3\" value=\"" + v + "\"/>";
s += "<input type=\"checkbox\" name=\"" + key + "#\" "+touch+"> Touch</td></tr>\n";
return s;
}
function mksep(id,label,url) {
return "<tr class=\"cfgheader\"><th class=\"cfg\" align=\"left\" colspan=\"2\">"+label+" <a href=\""+url+"\" target=\”_blank\">[wiki]</a></th></tr>\n";
}
function rowdisp(id,disp) {
var matches = document.querySelectorAll("tr."+id);
matches.forEach(function(e) { if(disp) e.hidden=true; else e.removeAttribute('hidden');});
hid=id; nid="N"+id;
if(!disp) { hid=nid; nid=id; }
document.querySelector("span."+hid).hidden=true;
document.querySelector("span."+nid).removeAttribute('hidden');
}
function configTable() {
// iterate over cfgs
var tab = "<table width=\"100%\"><tr><th>Option</th><th>Value</th></tr>\n";
var id=0;
for(i=0; i<cfgs.length; i++) {
var key = cfgs[i][0];
var lbl = cfgs[i][1];
if(key) {
if(key=="button_pin" || key=="button2_pin") {
tab += mkcfgbtn("s"+id, key, lbl, cf.get(key));
} else if (key=="display") {
tab += mkcfg("s"+id, key, lbl, cf.get(key));
tab += "<tr style=\"visibility:collapse\" class=\"cfgpanel\"><td>"+scr+"</td><td></td></tr>"
} else {
tab += mkcfg("s"+id, key, lbl, cf.get(key));
}
} else {
id++;
tab += mksep("s"+id, lbl, cfgs[i][2]);
}
}
tab += "</table>";
var cfgdiv = document.getElementById("cfgtab");
cfgdiv.innerHTML = tab;
// enable collapse / expand of items below a header
var acc = document.getElementsByClassName("cfgheader");
for(i=0; i<acc.length; i++) {
acc[i].firstChild.innerHTML = "[+] " + acc[i].firstChild.innerHTML;
acc[i].addEventListener("click", function(e) {
if(e.target.nodeName=="A") return;
achar = "[+]";
if(this.classList.toggle("active")) achar = "[\u2212]";
this.firstChild.innerHTML = achar + this.firstChild.innerHTML.substring(3);
var panel = this;
console.log(panel);
while( panel = panel.nextElementSibling) {
console.log(panel);
if ( panel.className!="cfgpanel") { break; }
if(panel.style.visibility==="collapse") {
panel.style.visibility="visible";
} else {
console.log("none");
panel.style.visibility="collapse";
}
}
});
}
acc[0].click();
}

View File

@ -2,8 +2,8 @@
# Hardware depending settings
#-------------------------------#
# pin: 255=disabled; x=button x+128=touch button
#button_pin=2
#button2_pin=4
#button_pin=130
#button2_pin=142
# No specification in config file: try autodetection (gpio4 pin level at startup)
#button_pin=0
#button2_pin=255
@ -22,15 +22,12 @@
#oled_sda=21
#oled_scl=22
#oled_rst=16
rxlat=
rxlon=
rxalt=
oled_orient=1
gpsOn=0
gps_rxd=-1
gps_txd=-1
gps_lat=43.59169
gps_lon=7.10071
gps_alt=123
# Show AFC value (for RS41 only, maybe also DFM, but useful for RS92 or M10)
# showafc=1
#gps_rxd=-1
#gps_txd=-1
# Frequency correction, in Hz
# freqofs=0
#-------------------------------#
@ -42,35 +39,39 @@ debug=0
wifi=3
# TCP/IP KISS TNC in port 14590 for APRSdroid (0=disabled, 1=enabled)
kisstnc.active = 1
mdnsname=radiosonde
# which screens file to use (0: automated selection based on display type and orientation, i>0: screens${i}.txt
# predefined: 1: for OLED, 2: for ILI9225; 3: for ILI9225 (portrait mode); 4: for ILI9431; 5: for ILI9431 (portrait mode)
# screenfile=2
# display configuration. List of "displays"
# first entry: "Scanner" display
# second entry: default "Receiver" display
# additional entries: alternative receiver display, activated by button
display=0,1,2,3,4,5,6
display=0,1,2,3,4,5,6,7
# turn off display: 0=never, 1=always, 2=if no RX; (+n*10: after n seconds)
dispsaver=0
# set OLED contrast (0..255) or leave at factory default (-1)
dispcontrast=-1
# set to -1 to disable (used for "N" values in timers in screens.txt). Value in seconds
norx_timeout=20
vbatmax=1.86
vbatmin=1.64
telemetryOn=0
buzzerOn=0
buzzerPort=12
#Add F4IYT
buzzerOn=1
buzzerPort=25
buzzerFreq=700
dbsmetre=1
gainLNA=0
#-------------------------------#
# Spectrum display settings
#-------------------------------#
startfreq=400
channelbw=10
spectrum=30 #10
spectrum=-1 #10
noisefloor=-125
marker=1
#-------------------------------#
# APRS settings
#-------------------------------#
call=FR2013
passcode=14003
call=N0CALL
passcode=12345
#-------------------------------#
# Sonde specific settings: bandwidth
# valid values: 3100, 3900, 5200, 6300, 7800, 10400, 12500,
@ -85,26 +86,65 @@ rs92.alt2d=480
dfm.agcbw=20800
dfm.rxbw=10400
#-------------------------------#
# ftp server for RINEX data (for RS92)
# YYYY/DDD/brdcDDD0.YYn.gz is appended
# s1: igs.bkg.bund.de/IGS/BRDC/
# s2: www.ngs.noaa.gov/cors/rinex/
#-------------------------------#
#ephftp=www.ngs.noaa.gov/cors/rinex/
#ephftp=igs.bkg.bund.de/IGS/BRDC/
ephftp=gssc.esa.int/gnss/data/daily/
#-------------------------------#
# axudp for sending to aprsmap
#-------------------------------#
# local use only, do not feed to public services
# data not sanitized / quality checked, outliers not filtered out
axudp.active=1
axudp.host=fra1od.fr.to
axudp.port=14580
axudp.host=192.168.42.20
axudp.port=9002
axudp.symbol=/O
axudp.highrate=1
axudp.idformat=0
#-------------------------------#
# maybe some time in the future
# connect to some aprs server
#-------------------------------#
# currently simply not implemented, no need to put anything here anyway
tcp.active=0
tcp.host=radiosondy.info
tcp.port=14590
tcp.symbol=/O
tcp.highrate=20
tcp.idformat=0
# send beacon (possibly with different call or SSID)
tcp.chase=0
tcp.objcall=
tcp.beaconsym=/`/(
tcp.comment=
#-------------------------------#
# mqtt settings
#-------------------------------#
# data not sanitized / quality checked, outliers not filtered out
mqtt.active=0
mqtt.id=rdz_sonde_server
mqtt.ip=192.168.1.5
mqtt.port=1883
mqtt.username=
mqtt.password=
mqtt.prefix=rdz_sonde_server/
#-------------------------------#
# Sondehub v2 settings
#-------------------------------#
# Sondehub v2 DB settings
sondehub.active=0
sondehub.chase=3
sondehub.host=api.v2.sondehub.org
sondehub.callsign=CHANGEME_RDZTTGO
sondehub.antenna=
sondehub.email=
#-------------------------------#
# Sondehub freq import settings
#-------------------------------#
shfimp.active=0
shfimp.interval=60
shfimp.maxdist=150
shfimp.maxage=6
#-------------------------------#
# EOF
#-------------------------------#

View File

@ -23,11 +23,11 @@
<button class="tablinks fa fa-dashboard" onclick="selTab(event,'QRG'); w3_close()" id="defaultTab"> QRG</button><br>
<button class="tablinks fa fa-wifi" onclick="selTab(event,'WiFi'); w3_close()"> WiFi</button><br>
<button class="tablinks fa fa-database" onclick="selTab(event,'Data'); w3_close()"> Data</button><br>
<button class="tablinks fa fa-map-marker" onclick="selTab(event,'SondeMap'); w3_close()"> URL SondeMap</button><br>
<button class="tablinks fa fa-map-marker" onclick="selTab(event,'Map'); w3_close()"> Map</button><br>
<button class="tablinks fa fa-map-marker" onclick="document.location.href='livemap.html'"> Livemap</button><br>
<button class="tablinks fa fa-gears" onclick="selTab(event,'Config'); w3_close()"> Config</button><br>
<button class="tablinks fa fa-sliders" onclick="selTab(event,'Control'); w3_close()"> Control</button><br>
<button class="tablinks fa fa-download" onclick="selTab(event,'Update'); w3_close()"> Update</button><br>
<button class="tablinks fa fa-file" onclick="selTab(event,'Telemetry'); w3_close()"> Telemetry</button><br>
<button class="tablinks fa fa-support" onclick="selTab(event,'About'); w3_close()"> About</button><br><br><br><br>
</nav>
@ -46,8 +46,12 @@
<iframe src="status.html" style="border:none;" width="100%%" height="100%%"></iframe>
</div>
<div id="SondeMap" class="tabcontent" data-src="sondemap.html">
<iframe src="sondemap.html" style="border:none;" width="98%%" height="98%%"></iframe>
<div id="Map" class="tabcontent" data-src="map.html">
<iframe src="map.html" style="border:none;" width="98%%" height="98%%"></iframe>
</div>
<div id="LiveMap" class="tabcontent" data-src="livemap.html">
<iframe src="livemap.html" style="border:none;" width="98%%" height="98%%"></iframe>
</div>
<div id="Config" class="tabcontent">
@ -69,21 +73,29 @@
<button onclick="javascript:window.open('/download','_self');">Telemetry</button>
</div>
<div id="About" class="tabcontent">
<h3>About</h3>
%VERSION_NAME%<br>
Copyright &copy; 2019 by Hansi Reiser, DL9RDZ<br>
(version %VERSION_ID%)<br>
with mods by <a href="https://www.dl2mf.de/" target="_blank">Meinhard Guenther, DL2MF</a><br>
CopyLeft 2020-2022 Modifier par <a href="http://openpmr.fr.nf/">Xav, FRS2013</a> & Vigor<br>
<a href="#Update">Check for update (requires TTGO internet connection via WiFi)</a><br><br>
<br>
(version %VERSION_ID%)
<br>
Original project by Hansi<br>
Copyright &copy; 2019-2022 by Hansi Reiser, DL9RDZ<br>
<br><br>
Autodetect info: %AUTODETECT_INFO%<br>
<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>
for details
RS92 RINEX eph state: %EPHSTATE%<br>
<br>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.<br>
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 style="background-color: #008CBA;"><br>
<h2>by FRS2013</h2>
<div class="footer"><span></span><span class="ttgoinfo">RadioSonde %VERSION_ID%</span></div>
</div>
<script>
function selTab(evt, id) {

18
RX_FSK/data/livemap.html Normal file
View File

@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<title>RadioSonde LiveMap</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<link rel="stylesheet" type="text/css" href="style_map.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css" />
<script>var mapcenter=[%MAPCENTER%];</script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://unpkg.com/leaflet@1.8.0/dist/leaflet.js"></script>
<script src="https://unpkg.com/leaflet.marker.slideto@0.2.0/Leaflet.Marker.SlideTo.js"></script>
<script src="livemap.js"></script>
</head>
<body>
<div id="map"></div>
</body>
</html>

530
RX_FSK/data/livemap.js Normal file
View File

@ -0,0 +1,530 @@
try {
var check = $(document);
} catch (e) {
document.addEventListener("DOMContentLoaded", function(event) {
document.getElementById('map').innerHTML = '<br /><br />In order to use this functionality, there must be an internet connection.<br /><br/><a href="livemap.html">retry</a><br /><br/><a href="index.html">go back</a>';
});
}
$(document).ready(function(){
var map = L.map('map', { attributionControl: false, zoomControl: false });
map.on('mousedown touchstart',function () { follow=false; });
L.control.scale().addTo(map);
L.control.attribution({prefix:false}).addTo(map);
var osmlight = L.tileLayer('https://{s}.tile.openstreetmap.de/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
});
var osmdark = L.tileLayer('https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
maxZoom: 19
});
var opentopo = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
maxZoom: 17,
attribution: 'Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, <a href="http://viewfinderpanoramas.org">SRTM</a><br />Map style: &copy; <a href="https://opentopomap.org">OpenTopoMap</a> (<a href="https://creativecommons.org/licenses/by-sa/3.0/">CC-BY-SA</a>)'
});
var esri = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye,<br />Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
maxZoom: 21
});
var basemap;
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
map.addLayer(osmdark);
basemap='osmdark';
} else {
map.addLayer(osmlight);
basemap='osmlight';
}
basemap_change = function () {
if (basemap == 'osmlight') {
map.removeLayer(osmlight);
map.addLayer(opentopo);
basemap = 'opentopo';
} else if (basemap == 'opentopo') {
map.removeLayer(opentopo);
map.addLayer(esri);
basemap = 'esri';
} else if (basemap == 'esri') {
map.removeLayer(esri);
map.addLayer(osmdark);
basemap = 'osmdark';
} else {
map.removeLayer(osmdark);
map.addLayer(osmlight);
basemap = 'osmlight';
}
};
if(mapcenter) map.setView(mapcenter, 5);
else map.setView([51.163361,10.447683], 5); // Mitte DE
var reddot = '<span class="ldot rbg"></span>';
var yellowdot = '<span class="ldot ybg"></span>';
var greendot = '<span class="ldot gbg"></span>';
var lastframe = 0;
$('#map .leaflet-control-container').append(L.DomUtil.create('div', 'leaflet-top leaflet-center leaflet-header'));
var header = '';
header += '<div id="sonde_main"><b>rdzTTGOSonde LiveMap</b><br />🎈 <b><span id="sonde_id"></span> - <span id="sonde_freq"></span> MHz - <span id="sonde_type"></span></b></div>';
header += '<div id="sonde_detail"><span id="sonde_alt"></span>m | <span id="sonde_climb"></span>m/s | <span id="sonde_speed"></span>km/h | <span id="sonde_dir"></span>°<br /><span id="sonde_time"></span> | -<span id="sonde_rssi"></span>dBm</div>';
header += '<div id="sonde_status"><span id="sonde_statbar"></span></div>';
header += '<div id="settings"><br /><b>Prediction-Settings</b><br />';
header += '<label for="burst">Burst at:</label><input type="text" id="burst" maxlength="5" value="..." /> m<br />';
header += '<label for="overwrite_descend">Descending:</label><input type="text" id="overwrite_descend" maxlength="2" value="..." /> m/s<br />';
header += '<label for="overwrite_descend_till">Use this descending until:</label><input type="text" id="overwrite_descend_till" maxlength="5" value="..." /> m<br />';
header += '<small>after the transmitted descend will be used</small>';
header += '<div id="submit"><input type="button" value="save" onclick="settings_save();"/>&nbsp;&nbsp;&nbsp;<input type="button" id="submit" value="reset" onclick="settings_reset();"/></div>';
header += '</div>';
$('.leaflet-header').append(header);
$('#map .leaflet-control-container').append(L.DomUtil.create('div', 'leaflet-bottom leaflet-center leaflet-footer'));
var footer = '';
footer += '<div id="gps_main"><b>Direction: </b><span class="gps_dir">...</span>°<br /><b>Distance: </b><span class="gps_dist">...</span>m</div>';
$('.leaflet-footer').append(footer);
var statbar = '';
headtxt = function(data,stat) {
var staticon = (stat == '1')?greendot:yellowdot;
statbar = staticon + statbar;
if ((statbar.length) > 10*greendot.length) { statbar = statbar.substring(0,10*greendot.length); }
if (data.id && data.vframe != lastframe ) {
lastframe = data.vframe;
$('#sonde_id').html(data.id);
$('#sonde_alt').html(data.alt);
$('#sonde_climb').html(data.climb);
$('#sonde_speed').html( mr(data.speed * 3.6 * 10) / 10 );
$('#sonde_dir').html(data.dir);
$('#sonde_time').html(new Date(data.time * 1000).toISOString());
$('#sonde_rssi').html(data.rssi / 2 );
$('#sonde_detail').show();
} else {
if (!data.id) {
$('#sonde_id').html(data.launchsite.trim());
// $('#sonde_detail').hide();
}
}
$('#sonde_freq').html(data.freq);
$('#sonde_type').html(data.type);
$('#sonde_statbar').html('&nbsp;'+statbar);
};
map.addControl(new L.Control.Button([ { position: 'topleft', text: '🔙', href: 'index.html' } ]));
L.control.zoom({ position:'topleft' }).addTo(map);
map.addControl(new L.Control.Button([ { position: 'topleft', text: '🗺️', href: 'javascript:basemap_change();' } ]));
map.addControl(new L.Control.Button([ { position: 'topright', id: "status", text: '', href: 'javascript:get_data();' } ]));
map.addControl(new L.Control.Button([
{ position:'topright', text: '🎈', href: 'javascript:show(marker[last_id],\'marker\');' },
{ text: '〰️', href: 'javascript:show_line();' },
{ text: '💥', href: 'javascript:show(marker_burst[last_id],\'burst\');' },
{ text: '🎯', href: 'javascript:show(marker_landing[last_id],\'landing\');' }
]));
map.addControl(new L.Control.Button([ { position:'topright', text: '⚙️', href: 'javascript:show_settings();' } ]));
show = function(e,p) {
if (p == 'landing') { get_predict(last_data); }
if (e) {
map.closePopup();
map.setView(map._layers[e._leaflet_id].getLatLng());
map._layers[e._leaflet_id].openPopup();
follow = p;
}
};
getTwoBounds = function (a,b) {
var sW = new L.LatLng((a._southWest.lat > b._southWest.lat)?b._southWest.lat:a._southWest.lat, (a._southWest.lng > b._southWest.lng)?b._southWest.lng:a._southWest.lng);
var nE = new L.LatLng((a._northEast.lat < b._northEast.lat)?b._northEast.lat:a._northEast.lat, (a._northEast.lng < b._northEast.lng)?b._northEast.lng:a._northEast.lng);
return new L.LatLngBounds(sW, nE);
};
show_line = function() {
$('.i_position, .i_landing').remove();
map.closePopup();
if (line[last_id]._latlngs.length != 0 && line_predict[last_id]._latlngs.length != 0) {
map.fitBounds(getTwoBounds(line[last_id].getBounds(),line_predict[last_id].getBounds()));
} else if (line[last_id]._latlngs.length != 0) {
map.fitBounds(line[last_id].getBounds());
} else if (line_predict[last_id]._latlngs.length != 0) {
map.fitBounds(line_predict[last_id].getBounds());
}
};
last_data = false;
last_id = false;
follow = 'marker';
marker_landing = [];
icon_landing = L.divIcon({className: 'leaflet-landing'});
dots_predict = [];
line_predict = [];
marker_burst = [];
icon_burst = L.divIcon({className: 'leaflet-burst'});
marker = [];
dots = [];
line = [];
draw = function(data) {
var stat;
if (data.id) {
last_id = data.id;
// data.res: 0: ok 1: no rx (timeout), 2: crc err, >2 some other error
if ((data.lat && data.lon && data.alt) && (lastframe != 0)) {
var location = [data.lat,data.lon,data.alt];
if (!marker[data.id]) {
map.setView(location, 14);
marker[data.id] = L.marker(location).addTo(map)
.bindPopup(poptxt('position',data),{closeOnClick:false, autoPan:false}).openPopup();
get_predict(data);
} else {
marker[data.id].slideTo(location, {
duration: 500,
keepAtCenter: (follow=='marker')?true:false
})
.setPopupContent(poptxt('position',data));
}
if (!dots[data.id]) { dots[data.id] = []; }
dots[data.id].push(location);
if (!line[data.id]) {
line[data.id] = L.polyline(dots[data.id]).addTo(map);
} else {
line[data.id].setLatLngs(dots[data.id]);
}
}
if (data.res == 0) {
storage_write(data);
$('#status').html(greendot);
stat = 1;
} else {
$('#status').html(yellowdot);
stat = 0;
}
headtxt(data,stat);
last_data = data;
} else {
$('#status').html(yellowdot);
headtxt(data,0);
}
};
marker_gps = false;
icon_gps = L.divIcon({className: 'leaflet-gps'});
circ_gps = false;
gps = function(e) {
gps_location = [e.lat,e.lon];
gps_accuracy = e.hdop*2;
if (last_data && last_data.lat != '0.000000') {
if ($('.leaflet-footer').css('display') == 'none') { $('.leaflet-footer').show(); }
var distance = Math.round(map.distance(gps_location,[last_data.lat, last_data.lon]));
distance = (distance > 1000)?(distance / 1000) + 'k':distance;
$('.leaflet-footer .gps_dist').html(distance);
$('.leaflet-footer .gps_dir').html( bearing(gps_location,[last_data.lat, last_data.lon]) );
}
if (!marker_gps) {
map.addControl(new L.Control.Button([{ position: 'topleft', text: '🛰️', href: 'javascript:show(marker_gps,\'gps\');' }]));
marker_gps = L.marker(gps_location,{icon:icon_gps}).addTo(map)
.bindPopup(poptxt('gps',e),{closeOnClick:false, autoPan:false});
circ_gps = L.circle(gps_location, gps_accuracy).addTo(map);
} else {
marker_gps.slideTo(gps_location, {
duration: 500,
keepAtCenter: (follow=='gps')?true:false
})
.setPopupContent(poptxt('gps',e));
circ_gps.slideTo(gps_location, { duration: 500 });
circ_gps.setRadius(gps_accuracy);
}
};
get_data = function() {
$('#status').html(reddot);
$.ajax({url: 'live.json', success: (function( data ) {
if (typeof data != "object") { data = $.parseJSON(data); }
if (data.sonde) {
draw(data.sonde);
} else {
setTimeout(function() {$('#status').html(yellowdot);},100);
}
if (data.gps) {
gps(data.gps);
}
}),
timeout: 1000}
);
};
storage = (typeof(Storage) !== "undefined")?true:false;
settings_std = {
burst: 32500,
overwrite_descend: 6,
overwrite_descend_till: 12000
};
settings_read = function() {
if (storage) {
if (sessionStorage.settings) {
return JSON.parse(sessionStorage.settings);
} else {
settings_write(settings_std);
return settings_std;
}
} else {
return settings_std;
}
return false;
};
settings_write = function (data) {
if (storage) {
sessionStorage.settings = JSON.stringify(data);
settings = data;
}
};
settings = settings_read();
settings_save = function() {
settings.burst = parseInt($('#settings #burst').val());
settings.overwrite_descend = parseInt($('#settings #overwrite_descend').val());
settings.overwrite_descend_till = parseInt($('#settings #overwrite_descend_till').val());
if (Number.isInteger(settings.burst) && Number.isInteger(settings.overwrite_descend) && Number.isInteger(settings.overwrite_descend_till)) {
settings_write(settings);
$("#settings").slideUp();
get_predict(last_data);
} else {
alert('Error: only numeric values allowed!');
}
};
settings_reset = function() {
if (confirm('Reset to default?')) {
settings_write(settings_std);
show_settings();
}
};
show_settings = function() {
$('#settings #burst').val(settings.burst);
$('#settings #overwrite_descend').val(settings.overwrite_descend);
$('#settings #overwrite_descend_till').val(settings.overwrite_descend_till);
$("#settings").slideToggle();
};
predictor = false;
get_predict = function(data) {
if (!data) { return; }
var ascent = (data.climb > 0)? data.climb : 15;
var descent = (data.climb > 0)? settings.overwrite_descend : data.climb * -1;
var burst;
if (data.climb > 0) {
burst = (data.alt > settings.burst )?data.alt + 100 : settings.burst;
} else {
burst = parseInt(data.alt) + 7;
if (data.alt > settings.overwrite_descend_till ) { descent = settings.overwrite_descend; }
}
var m = new Date();
var datetime = m.getUTCFullYear() + "-" + az(m.getUTCMonth()+1) + "-" + az(m.getUTCDate()) + "T" +
az(m.getUTCHours()) + ":" + az(m.getUTCMinutes()) + ":" + az(m.getUTCSeconds()) + "Z";
var url = 'https://api.v2.sondehub.org/tawhiri';
url += '?launch_latitude='+data.lat + '&launch_longitude='+tawhiri_lon(data.lon);
url += '&launch_altitude='+data.alt + '&launch_datetime='+datetime;
url += '&ascent_rate='+ascent + '&burst_altitude=' + burst + '&descent_rate='+descent;
$.getJSON(url, function( prediction ) {
draw_predict(prediction,data);
});
};
draw_predict = function(prediction,data) {
var ascending = prediction.prediction[0].trajectory;
var highest = ascending[ascending.length-1];
var highest_location = [highest.latitude,sanitize_lon(highest.longitude)];
var descending = prediction.prediction[1].trajectory;
var landing = descending[descending.length-1];
var landing_location = [landing.latitude,sanitize_lon(landing.longitude)];
if (!marker_landing[data.id]) {
marker_landing[data.id] = L.marker(landing_location,{icon: icon_landing}).addTo(map)
.bindPopup(poptxt('landing',landing),{closeOnClick:false, autoPan:false});
} else {
marker_landing[data.id].slideTo(landing_location, {
duration: 500,
keepAtCenter: (follow=='landing')?true:false
})
.setPopupContent(poptxt('landing',landing));
}
dots_predict[data.id]=[];
if (data.climb > 0) {
ascending.forEach(p => dots_predict[data.id].push([p.latitude,sanitize_lon(p.longitude)]));
if (!marker_burst[data.id]) {
marker_burst[data.id] = L.marker(highest_location,{icon:icon_burst}).addTo(map).bindPopup(poptxt('burst',highest),{closeOnClick:false, autoPan:false});
} else {
marker_burst[data.id].slideTo(highest_location, {
duration: 500,
keepAtCenter: (follow=='burst')?true:false
}).setPopupContent(poptxt('burst',highest));
}
}
descending.forEach(p => dots_predict[data.id].push([p.latitude,sanitize_lon(p.longitude)]));
if (!line_predict[data.id]) {
line_predict[data.id] = L.polyline(dots_predict[data.id],{color: 'yellow'}).addTo(map);
} else {
line_predict[data.id].setLatLngs(dots_predict[data.id]);
}
if (data.climb > 0) {
predictor_time = 5 * 60; // ascending, every 5 min
} else if (data.climb < 0 && data.alt > 5000) {
predictor_time = 2 * 60; // descending, above 5km, every 2 min
} else {
predictor_time = 30; // descending, below 5km, every 30 sec
}
clearTimeout(predictor);
predictor = setTimeout(function() {get_predict(last_data);}, predictor_time*1000);
};
sanitize_lon = function(lon) {
if (lon > 180) { return lon - 360; }
return lon;
}
tawhiri_lon = function(lon) {
if (lon < 0) { return lon + 360; }
return lon;
}
poptxt = function(t,i) {
var lat_input = (i.id)?i.lat:i.latitude;
var lon_input = sanitize_lon((i.id)?i.lon:i.longitude);
var lat = Math.round(lat_input * 1000000) / 1000000;
var lon = Math.round(lon_input * 1000000) / 1000000;
var add =
'<br /><b>Position:</b> '+lat+', '+lon+'<br />'+
'<b>Open:</b> <a href="https://www.google.de/maps/?q='+lat+', '+lon+'" target="_blank">GMaps</a> | <a href="https://www.openstreetmap.org/?mlat='+lat+'&mlon='+lon+'&zoom=15" target="_blank">OSM</a> | <a href="geo://'+lat+','+lon+'">GeoApp</a>';
if (t == 'position') { return '<div class="i_position"><b>🎈 '+i.id+'</b>'+add+'</div>'; }
if (t == 'burst') { return '<div class="i_burst"><b>💥 Predicted Burst:</b><br />'+fd(i.datetime)+' in '+mr(i.altitude)+'m'+add+'</div>'; }
if (t == 'highest') { return '<div class="i_burst"><b>💥 Burst:</b> '+mr(i.altitude)+'m'+add+'</div>';}
if (t == 'landing') { return '<div class="i_landing"><b>🎯 Predicted Landing:</b><br />'+fd(i.datetime)+' at '+mr(i.altitude)+'m'+add+'</div>'; }
if (t == 'gps') { return '<div class="i_gps">Position: '+(i.lat)+','+(i.lon)+'<br />Altitude: '+i.alt+'m<br />Speed: '+mr(i.speed * 3.6 * 10)/10+'km/h '+i.dir+'°<br />Sat: '+i.sat+' Hdop:'+(i.hdop/10)+'</div>'; }
};
fd = function(date) {
var d = new Date(Date.parse(date));
return az(d.getUTCHours()) +':'+ az(d.getUTCMinutes())+' UTC';
};
az = function(n) { return (n<10)?'0'+n:n; };
mr = function(n) { return Math.round(n); };
storage = (typeof(Storage) !== "undefined")?true:false;
storage_write = function (data) {
if (storage) {
if (sessionStorage.sonde) {
storage_data = JSON.parse(sessionStorage.sonde);
} else {
storage_data = [];
}
if (JSON.stringify(data) != JSON.stringify(storage_data[storage_data.length - 1])) {
storage_data.push(data);
sessionStorage.sonde = JSON.stringify(storage_data);
}
}
};
storage_read = function() {
if (storage) {
if (sessionStorage.sonde) {
storage_data = JSON.parse(sessionStorage.sonde);
return storage_data;
}
}
return false;
};
storage_remove = function() {
sessionStorage.removeItem('sonde');
};
session_storage = storage_read();
if (session_storage) {
session_storage.forEach(function(d) {
dots.push([d.lat,d.lon,d.alt]);
session_storage_last = d;
});
draw(session_storage_last);
}
setInterval(get_data,1000);
});
L.Control.Button = L.Control.extend({
onAdd: function (map) {
var container = L.DomUtil.create('div', 'leaflet-bar leaflet-control');
options = this.options;
Object.keys(options).forEach(function(key) {
this.link = L.DomUtil.create('a', '', container);
this.link.text = options[key].text;
this.link.href = options[key].href;
this.link.id = options[key].id;
});
this.options.position = this.options[0].position;
return container;
}
});
// https://github.com/makinacorpus/Leaflet.GeometryUtil/blob/master/src/leaflet.geometryutil.js#L682
// modified to fit
function bearing(latlng1, latlng2) {
var rad = Math.PI / 180,
lat1 = latlng1[0] * rad,
lat2 = latlng2[0] * rad,
lon1 = latlng1[1] * rad,
lon2 = latlng2[1] * rad,
y = Math.sin(lon2 - lon1) * Math.cos(lat2),
x = Math.cos(lat1) * Math.sin(lat2) -
Math.sin(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1);
var bearing = ((Math.atan2(y, x) * 180 / Math.PI) + 360) % 360;
bearing = bearing < 0 ? bearing-360 : bearing;
return Math.round(bearing);
}

25
RX_FSK/data/map.html Normal file
View File

@ -0,0 +1,25 @@
<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/#!mt=Mapnik", (lat,lon)=>'&mz=7&qm=12h&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"],
};
const data = {"gps_lat":45.62172,"gps_lon":7.02026};
urlarg = + maps[maptype][1] + data['gps_lat']+","+data['gps_lon'];
iftxt='<iframe src="' + maps[maptype][0] + urlarg +'" style="border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%"></iframe>';
document.write(iftxt);
document.getElementsByTagName('body')[0].innerHTML = iftxt;
</script>
</head>
<body>
</body>
</html>

6
RX_FSK/data/networks.txt Executable file
View File

@ -0,0 +1,6 @@
Radiosonde
Radiosonde
SSIDMobile
123456
SSIDBox
123456

26
RX_FSK/data/qrg.txt Normal file
View File

@ -0,0 +1,26 @@
# Frequency in Mhz (format nnn.nnn)
# Type (4=RS41, R=RS92, D=DFM (automated normal/inverted), M=M10/M20, 3=MP3H)
# (older versions: 6=DFM6, 9=DFM9, now both treated the same as D)
# (older versions: M=M10, 2=M20, now both treaded the same: automated M10/M20 decoding)
# +: active, -: not active
#
400.000 9 + Test(FR)
402.000 2 + Nimes(FR)
402.800 4 + Cuneo(IT)
403.000 6 - Ajactio(FR)
403.200 M - OHP(FR)
403.010 6 - Canjuers(FR)
404.200 4 - Rome(IT)
404.789 9 - Frejus1(FR)
404.800 4 - Milan(IT)
405.000 R - Frejus2(FR)
405.789 9 - Pegomas(FR)
403.700 4 - Cuneo2(IT)
400.000 D -
400.000 D -
400.000 D -
400.000 D -
400.000 D -
400.000 D -
400.000 D -
# end

54
RX_FSK/data/rdz.js Normal file
View File

@ -0,0 +1,54 @@
let stypes=new Map();
stypes.set('4', 'RS41');
stypes.set('R', 'RS92');
stypes.set('D', 'DFM');
stypes.set('M', 'M10/M20');
stypes.set('3', 'MP3H');
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){
var val=txt.getAttribute('value'); var nam=txt.getAttribute('name');
if(val=='2') { val='M'; }
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);
}
}
function qrgTable() {
var tab=document.getElementById("divTable");
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 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();
}

460
RX_FSK/data/screens.txt Normal file
View File

@ -0,0 +1,460 @@
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg) [not yet implemented, maybe some day in future]
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
#
# Default configuration for "Scanner" display:
# - view timer disabled; rx timer=0; norx timer = 0
# => after 1 second immediately an action is triggered
# (norx: go to next sonde; rx: go to default receiver display)
# - key1 actions: D,0,F,W
# => Button press activates default receiver view, double press does nothing
# Mid press activates Spectrum display, long press activates Wifi scan
# - key2 has no function
@Scanner
timer=-1,0,0
key1action=D,#,F,W
key2action=#,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0=S
7,5=n
############
# Default configuration for "Legacy" display:
# - view timer=-1, rx timer=-1 (disabled); norx timer=20 (or -1 for "old" behaviour)
# => norx timer fires after not receiving a singla for 20 seconds
# - key1 actions: +,0,F,W
# => Button1 press: next sonde; double press => @Scanner display
# => Mid press activates Spectrum display, long press activates Wifi scan
# - key2 actions: 2,#,#,#
# => BUtton2 activates display 2 (@Field)
# - timer actions: #,#,0
# (norx timer: if no signal for >20 seconds: go back to scanner mode)
#
@Legacy
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,5=f MHz
1,8=c
0,0=t
1,0=is
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
6,0=R
6,7=Q
############
# Configuratoon for "Field" display (display 2)
# similar to @Legacy, but no norx timer, and Key2 goes to display 4
@Field
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
# Configuration for "Field2" display (display 3)
# similar to @Field
@Field2
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
1,12=t
0,9=f
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
#############
# Configuration for "GPS" display
# not yet the final version, just for testing
@GPSDIST
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
5,9=gC
5,13=gB
6,7=Q
7,0=gV
7,2=xd=
7,4=gD
7,12=gI°
############
# Scan display for large 2" TFT dispaly
@ScannerTFT
timer=-1,0,0
key1action=D,#,F,W
key2action=#,#,#,#
timeaction=#,D,+
fonts=5,6
0,0=XScan
0,8,-3=S#:
0,9,5=T
3,0=F MHz
5,0,16=S
7,5=n
############
@MainTFT
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
color=FFD700
0,0=Is
color=0000FF
0,11,-5.5=f
1,1,6=c
1,12.5,-4=t
color=00ff00
2,0=L
4,0=O
color=FFA500
2,9.5,-7=A
3,9.5,-7=vm/s
color=AA5522
4,9.5,-7=hkkm/h
color=FFFFFF
6,2=r
7,0=xd=
7,2,6=gD
7,12=gI
############
@PeilungTFT
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
color=ffff00,000033
color=bbbbbb,000000
0,2=xN Top:
0,8=xCourse Top:
color=ffff00,000033
1,0=g0NCS,48,ffff00,000044,6,33ff33,5,eeaa00
1,8=g0CCS,48,ffff00,000044,6,55ff55,5,eeaa00
color=ffffff,000000
6,0=xDirection:
6,8,4=gI
7,0=xCOG:
7,4,4=gC
7,8=xturn:
7,12,4=gB
############
@GPSdataTFT
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=xOn-board GPS:
1,0,8=gA
2,0,8=gO
3,0,8=gH
4,0,8=gC
5,0=xGPS vs Sonde:
6,0,8=gD
7,0,8=gI
7,8,8=gB
############
@BatteryTFT
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=xBattery status:
0,14=bS
1,0=xBatt:
1,5,5=bVV
2,0,16=bCmA(charging)
3,0,16=bDmA(discharging)
4.4,0=xUSB:
4.4,5,5=bUV
5.4,0,10=bImA
6.4,0=xTemp:
6.4,5,5=bT C
############
@BatteryOLED
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
0,12=bS
1,0=xBatt:
1,6=bVV
2,0=bCmA (charge)
3,0=bDmA (disch.)
4,0=xUSB:
4,5=bUV
5,5=bImA
6,0=xTemp:
6,5=bT C
### Alternative display layouts based on https://gist.github.com/bazjo
# Scan display for large 2" TFT dispaly
@Scan.TFT.Bazjo
timer=-1,0,0
key1action=D,#,F,W
key2action=#,#,#,#
timeaction=#,D,+
scale=11,10
fonts=0,2
color=e0e0e0
#Row 1
0.5,0=XScanning...
#Row 2
3,0=xIndex
4,0=S/
3,9=xSite
4,9=S
#Row 3
6,0=xType
7,0=T
6,9=xFrequency
7,9=F
#Row 4
9,0=xWeb UI IP
10,0=N
#Row 5
#Footer
color=6C757D
15,0=xScan Mode
15,18=bVV
############
@Decode/General.TFT.Bazjo
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
scale=11,10
fonts=0,2
#Row 1
color=996A06
0,0=xSerial
0,5=t
color=FFB10B
1,0=Is
color=996A06
0,11=xFreq.
0,16=c
color=FFB10B
1,11=F
#Row 2
color=3C5C99
3,0=xLatitude
color=639AFF
4,0=L
color=3C5C99
3,11=xLongitude
color=639AFF
4,11=O
#Row 3
color=3C5C99
6,0=xHoriz. Speed
color=639AFF
7,0=Hkkm/h
color=3C5C99
6,11=xVert. Speed
color=639AFF
7,11=Vm/s
#Row 4
color=99004A
9,0=xAltitude
color=FF007B
10,0=A
color=99004A
9,11=xBearing
color=FF007B
10,11=GB
#Row 5
color=06998E
12,0=xRSSI
color=0AFFEF
13,0=R
color=06998E
12,11=xHistory
color=0AFFEF
13.5,11=Q4
#Footer
color=6C757D
15,0=xDecode Mode / General View
15,18=bVV
############
@Decode/Battery.TFT.Bazjo
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
scale=11,10
fonts=0,2
#Row 1
color=99001F
0,0=xBattery Status
0,11=xBattery Voltage
color=FF0035
1,0=BS
1,11=BVV
#Row 2
color=99001F
3,0=xCharge Current
3,11=xDischarge Current
color=FF0035
4,0=BCmA
4,11=BDmA
#Row 3
color=99001F
6,0=xUSB Voltage
6,11=xUSB Current
color=FF0035
7,0=BUV
7,11=BImA
#Row 4
color=99001F
9,0=xIC Temperature
#9,11=xKey
color=FF0035
10,0=BTC
#10,11=XValue
#Row 5
#12,0=xKey
#12,11=xKey
#13,0=XValue
#13,11=XValue
#Footer
color=99001F
15,0=xDecode Mode/Battery View
15,18=bVV

View File

@ -1,3 +1,4 @@
### screens1.txt: OLED display
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
@ -32,7 +33,10 @@
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (dfm format by x: d=>dxlaprs, a=>autorx, s=>real serial number)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
@ -40,7 +44,7 @@
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg) [not yet implemented, maybe some day in future]
# Mx telemetry value x (t temp p preassure h hyg b battery)
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
@ -54,8 +58,7 @@
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
# AA Azimut degres
# Z duree vol H:m.s
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
@ -78,6 +81,15 @@
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
@ -97,14 +109,15 @@
@Scanner
timer=-1,0,0
key1action=D,#,F,W
key2action=#,#,#,#
key2action=D,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0=S
7,0=y%
5,0,16=S
7,0=bVV
#7,0=gV
7,5=n
############
@ -124,23 +137,17 @@ timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,0=t
0,5=f MHz
1,8=c
0,0=t
1,0=is
#1,8=c
1,8=z
2,0=L
4,0=O
2,10=a
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=R
6,7=Q
7,6=gD
7,12=gI°
############
# Configuratoon for "Field" display (display 2)
@ -150,19 +157,13 @@ timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
2,0=L
2,9=xEl:
2,12=gE
4,0=O
3,10=h
4,9=v
5,9=gC
5,13=gB
0,0=Is
6,0=A
6,7=Q
7,6=gD
7,12=gI°
############
# Configuration for "Field2" display (display 3)
@ -172,21 +173,15 @@ timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,9=xEl:
2,12=gE
2,0=L
3,10=h
4,0=O
1,12=t
0,9=f
3,10=h
4,9=v
5,9=gC
5,13=gB
0,0=Is
6,0=A
6,7=Q
7,6=gD
7,12=gI°
#############
# Configuration for "GPS" display
@ -206,32 +201,11 @@ timeaction=#,#,#
4,9=v
5,9=gC
5,13=gB
6,0=xEl:
6,2=gE
6,7=Q
7,0=gW
7,0=gV
7,2=xd=
7,4=gD
7,11=gI°
############
# Boussole Oled
@BOUSSOLE
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=g0
0,10=a
1,10=h
2,9=v
3,10=gC
3,13=gB
4,9=gD
5,11=gI°
6,9=xEl:
6,11=gE
7,9=t
7,12=gI°
############
@BatteryOLED
@ -241,11 +215,53 @@ key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
2,0=xCpu:
2,7=bVV
4,0=xVbus:
4,7=bCV
6,0=xPower:
6,7=bPmW
0,12=bS
1,0=xBatt:
1,6=bVV
2,0=bCmA (charge)
3,0=bDmA (disch.)
4,0=xUSB:
4,5=bUV
5,5=bImA
6,0=xTemp:
6,5=bT C
#############
@Meteo
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=Is
0,9=f
1,12=t
2,0=xSonde
3,0=xData
2,10=A
4,0=Mt°C
4,9=Mh%rH
6,0=MphPa
6,11=MbV
#############
@GPS-Data
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xGPS-Data
1,0=xLAT :
1,6=gA
2,0=xLONG:
2,6=gO
3,0=xALT :
3,9=gH
4,0=xSonde
5,0=xAlt :
5,6=a
6,0=xDist:
6,6=gD

276
RX_FSK/data/screens2.txt Normal file
View File

@ -0,0 +1,276 @@
## screens2.txt: TFT display (landscape)
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg b batt)
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
############
# Scan display for large 2" TFT dispaly
@ScannerTFT
timer=-1,0,0
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0,16=S
7,0=bVV
7,5=n
############
@Legacy
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,0=t
0,5=f MHz
1,0=is
#1,8=c
1,8=z
2,0=L
2,10=a
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=R
6,7=Q
7,6=gD
7,12=gI°
############
@Field
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@Field2
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
1,12=t
0,9=f
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@GPSDIST
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
5,9=gC
5,13=gB
6,7=Q
7,0=gV
7,2=xd=
7,4=gD
7,12=gI°
### Alternative display layouts based on https://gist.github.com/bazjo
# Scan display for large 2" TFT dispaly
@BatteryOLED
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
0,12=bS
1,0=xBatt:
1,6=bVV
2,0=bCmA (charge)
3,0=bDmA (disch.)
4,0=xUSB:
4,5=bUV
5,5=bImA
6,0=xTemp:
6,5=bT C
############
@Meteo
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=Is
0,9=f
1,12=t
2,0=xSonde
3,0=xData
2,10=A
4,0=Mt°C
4,9=Mh%rH
6,0=MphPa
6,11=MbV
##################
@GPS-Data
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xGPS-Data
1,0=xLAT :
1,6=gA
2,0=xLONG:
2,6=gO
3,0=xALT :
3,9=gH
4,0=xSonde
5,0=xAlt :
5,6=a
6,0=xDist:
6,6=gD
#################
@TelemetryData
scale=22,13
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
fonts=5,6
color=FFD700
0,0,10.5=Is
color=0000FF
0,11,-5.5=f
1,0,4=t
1,10.5,-6=c
color=00ff00
2,0,7=L
3,0,7=O
color=FFA500
2,9.5,-7=a
2.8,9.5,-7=vm/s
color=AA5522
3.6,9.5,-7=hkkm/h
color=FFFFFF
4.4,0=xTelemetry Data:
5.3,0=Mt C
5.3,9=Mh%rH
6.5,0=MphPa
6.5,11=MbV

276
RX_FSK/data/screens3.txt Normal file
View File

@ -0,0 +1,276 @@
## screens2.txt: TFT display (landscape)
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg b batt)
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
############
# Scan display for large 2" TFT dispaly
@ScannerTFT
timer=-1,0,0
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0,16=S
7,0=bVV
7,5=n
############
@Legacy
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,0=t
0,5=f MHz
1,0=is
#1,8=c
1,8=z
2,0=L
2,10=a
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=R
6,7=Q
7,6=gD
7,12=gI°
############
@Field
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@Field2
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
1,12=t
0,9=f
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@GPSDIST
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
5,9=gC
5,13=gB
6,7=Q
7,0=gV
7,2=xd=
7,4=gD
7,12=gI°
### Alternative display layouts based on https://gist.github.com/bazjo
# Scan display for large 2" TFT dispaly
@BatteryOLED
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
0,12=bS
1,0=xBatt:
1,6=bVV
2,0=bCmA (charge)
3,0=bDmA (disch.)
4,0=xUSB:
4,5=bUV
5,5=bImA
6,0=xTemp:
6,5=bT C
############
@Meteo
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=Is
0,9=f
1,12=t
2,0=xSonde
3,0=xData
2,10=A
4,0=Mt°C
4,9=Mh%rH
6,0=MphPa
6,11=MbV
##################
@GPS-Data
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xGPS-Data
1,0=xLAT :
1,6=gA
2,0=xLONG:
2,6=gO
3,0=xALT :
3,9=gH
4,0=xSonde
5,0=xAlt :
5,6=a
6,0=xDist:
6,6=gD
#################
@TelemetryData
scale=22,13
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
fonts=5,6
color=FFD700
0,0,10.5=Is
color=0000FF
0,11,-5.5=f
1,0,4=t
1,10.5,-6=c
color=00ff00
2,0,7=L
3,0,7=O
color=FFA500
2,9.5,-7=a
2.8,9.5,-7=vm/s
color=AA5522
3.6,9.5,-7=hkkm/h
color=FFFFFF
4.4,0=xTelemetry Data:
5.3,0=Mt C
5.3,9=Mh%rH
6.5,0=MphPa
6.5,11=MbV

276
RX_FSK/data/screens4.txt Normal file
View File

@ -0,0 +1,276 @@
## screens2.txt: TFT display (landscape)
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg b batt)
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
############
# Scan display for large 2" TFT dispaly
@ScannerTFT
timer=-1,0,0
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0,16=S
7,0=bVV
7,5=n
############
@Legacy
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,0=t
0,5=f MHz
1,0=is
#1,8=c
1,8=z
2,0=L
2,10=a
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=R
6,7=Q
7,6=gD
7,12=gI°
############
@Field
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@Field2
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
1,12=t
0,9=f
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@GPSDIST
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
5,9=gC
5,13=gB
6,7=Q
7,0=gV
7,2=xd=
7,4=gD
7,12=gI°
### Alternative display layouts based on https://gist.github.com/bazjo
# Scan display for large 2" TFT dispaly
@BatteryOLED
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
0,12=bS
1,0=xBatt:
1,6=bVV
2,0=bCmA (charge)
3,0=bDmA (disch.)
4,0=xUSB:
4,5=bUV
5,5=bImA
6,0=xTemp:
6,5=bT C
############
@Meteo
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=Is
0,9=f
1,12=t
2,0=xSonde
3,0=xData
2,10=A
4,0=Mt°C
4,9=Mh%rH
6,0=MphPa
6,11=MbV
##################
@GPS-Data
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xGPS-Data
1,0=xLAT :
1,6=gA
2,0=xLONG:
2,6=gO
3,0=xALT :
3,9=gH
4,0=xSonde
5,0=xAlt :
5,6=a
6,0=xDist:
6,6=gD
#################
@TelemetryData
scale=22,13
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
fonts=5,6
color=FFD700
0,0,10.5=Is
color=0000FF
0,11,-5.5=f
1,0,4=t
1,10.5,-6=c
color=00ff00
2,0,7=L
3,0,7=O
color=FFA500
2,9.5,-7=a
2.8,9.5,-7=vm/s
color=AA5522
3.6,9.5,-7=hkkm/h
color=FFFFFF
4.4,0=xTelemetry Data:
5.3,0=Mt C
5.3,9=Mh%rH
6.5,0=MphPa
6.5,11=MbV

276
RX_FSK/data/screens5.txt Normal file
View File

@ -0,0 +1,276 @@
## screens2.txt: TFT display (landscape)
# Definition of display content and action behaviour
#
# Timer: (view timer, rx timer, norx timer)
# - value -1: timer is disabled; value>=0: timer fires after (value) seconds
# - view timer: time since current view (display mode and sonde) was started
# - rx timer: time since when sonde data has been received continuously (trigger immediatly after RX)
# - norx timer: time since when no sonde data has been received continuously
# (rx and norx timer is started after tuning a new frequency and receiving a signal or not receiving
# anything for a 1s period)
#
# Actions:
# - W: activate WiFi scan
# - F: activate frequency spectrum display
# - 0: activate "Scan:" display (this is basically just display mode 0)
# - x: (1..N): activate display mode x [deprecated]
# - >: activate next display mode
# - D: activate default receiver display (display mode specified in config)
# - +: advance to next active sonde from QRG config
# - #: no action
#
# Display content (lower/upper case: small/large font)
# line,column=content
# for ILI9225 its also possible to indicate
# line,column,width=content for text within a box of width 'width'
# line,column,-width=content for right-justified text
#
# XText : Text
# F(suffix): frequency (with suffix, e.g., " MHz")
# L latitade
# O lOngitute
# A altitude
# Hm(suffix) hor. speed m/s (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Hk(suffix) hor. speed km/h (suffix: e.g. "km/h"; no suffix=>km/h as 16x8 bitmap for SSD1306 display only)
# V(suffix) vert. speef (suffix: e.g. "m/s"; no suffix=>m/s as 16x8 bitmap for SSD1306 display only)
# Ix sonde ID (default/d: dxlaprs; s: short id, n: real serial number)
# RS41,RS92: all identical R1234567
# DFMx: ID M12345678; short ID and serial 12345678
# M10: ID ME95231F0; short ID: M95231F0; serial 9062104592
# Q signal quality statistics bar
# T type string (RS41/DFM9/DFM6/RS92)
# C afC value
# N ip address (only tiny font)
# S scan list entry info: l/empty: launch site name, #=entry nr, t=total entries, a=active entries, /: #/t
# K RS41 kill timer values: Kl launch timer, Kb burst timer, Kc kill countdown
# format: K_4: h:mm k_6: h:mm:ss k_s: sssss, nothing shown for other sonde
# Mx telemetry value x (t temp p preassure h hyg b batt)
# Gx GPS-related data
# raw data from GPS: GA, GO, GH, GC: LAtitude, lOngitude, Altutide(Height), Course over ground
# relative to sonde: GD, GI, GB: Distance, dIrection (absolute), relative Bearing
# G0 GPS circle diagram e.g. 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
# "N" (what is on top: N=north C=course)
# "C" (where does the arrow point to: C=course, S=sonde)
# "S" (what is shown by the bullet: C=course, S=sonde)
# 50: circle radius, followed by fg and bg color
# 5: bullet radius, followed by fg color
# 4: arrow width, followed by fg color
# R RSSI
# B battery(T-Beam 1.0) S=status V=Batt.Volt C=charge current D=discharge current
# U=USB volt I=USB current T=IC temp
#
# fonts=x,y can be used to select font (x=small, y=large) for all items below
# for SSD1306, x and y can be used to select one of those fonts:
# (y should be a 1x2 font (1,5,6,7), x a small font)
# u8x8_font_chroma48medium8_r, // 0 ** default small
# u8x8_font_7x14_1x2_f, // 1 ** default large
# u8x8_font_amstrad_cpc_extended_f, // 2
# u8x8_font_5x7_f, // 3
# u8x8_font_5x8_f, // 4
# u8x8_font_8x13_1x2_f, // 5
# u8x8_font_8x13B_1x2_f, // 6
# u8x8_font_7x14B_1x2_f, // 7
# u8x8_font_artossans8_r, // 8
# u8x8_font_artosserif8_r, // 9
# u8x8_font_torussansbold8_r, // 10
# u8x8_font_victoriabold8_r, // 11
# u8x8_font_victoriamedium8_r, // 12
# u8x8_font_pressstart2p_f, // 13
# u8x8_font_pcsenior_f, // 14
# u8x8_font_pxplusibmcgathin_f, // 15
# u8x8_font_pxplusibmcga_f, // 16
# u8x8_font_pxplustandynewtv_f, // 17
#
# for ILI9225, these fonts are available:
# Terminal6x8 // 0
# Terminal11x16 // 1
# Terminal12x16 // 2
# FreeMono9pt7b, // 3
# FreeMono12pt7b, // 4
# FreeSans9pt7b, // 5
# FreeSans12pt7b, // 6
# Picopixel, // 7
#
# color=rrggbb,rrggbb can be used to select color (foreground, background)
# see https://github.com/Nkawu/TFT_22_ILI9225/wiki#color-reference for example (use without "#"-sign)
#
# for TFT display, coordinates and width are multiplied by xscale,yscale and later used in pixels
# with scale=1,1 you can directly use pixel coordinates. (default: xscale=13,yscale=22 => 8 lines, 16 columns)
###########
############
# Scan display for large 2" TFT dispaly
@ScannerTFT
timer=-1,0,0
key1action=D,#,F,W
key2action=D,#,#,#
timeaction=#,D,+
0,0=XScan
0,5=S#:
0,9=T
3,0=F MHz
5,0,16=S
7,0=bVV
7,5=n
############
@Legacy
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
0,0=t
0,5=f MHz
1,0=is
#1,8=c
1,8=z
2,0=L
2,10=a
3,10=h
4,0=O
4,9=v
5,9=gC
5,13=gB
6,0=R
6,7=Q
7,6=gD
7,12=gI°
############
@Field
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@Field2
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
2,0=L
4,0=O
1,12=t
0,9=f
3,10=h
4,9=v
0,0=Is
6,0=A
6,7=Q
############
@GPSDIST
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
0,0=Is
0,9=f
1,12=t
2,0=L
4,0=O
2,10=a
3,10=h
4,9=v
5,9=gC
5,13=gB
6,7=Q
7,0=gV
7,2=xd=
7,4=gD
7,12=gI°
### Alternative display layouts based on https://gist.github.com/bazjo
# Scan display for large 2" TFT dispaly
@BatteryOLED
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xBat.Status:
0,12=bS
1,0=xBatt:
1,6=bVV
2,0=bCmA (charge)
3,0=bDmA (disch.)
4,0=xUSB:
4,5=bUV
5,5=bImA
6,0=xTemp:
6,5=bT C
############
@Meteo
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=Is
0,9=f
1,12=t
2,0=xSonde
3,0=xData
2,10=A
4,0=Mt°C
4,9=Mh%rH
6,0=MphPa
6,11=MbV
##################
@GPS-Data
timer=-1,-1,-1
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,#
fonts=0,1
0,0=xGPS-Data
1,0=xLAT :
1,6=gA
2,0=xLONG:
2,6=gO
3,0=xALT :
3,9=gH
4,0=xSonde
5,0=xAlt :
5,6=a
6,0=xDist:
6,6=gD
#################
@TelemetryData
scale=22,13
timer=-1,-1,N
key1action=+,0,F,W
key2action=>,#,#,#
timeaction=#,#,0
fonts=5,6
color=FFD700
0,0,10.5=Is
color=0000FF
0,11,-5.5=f
1,0,4=t
1,10.5,-6=c
color=00ff00
2,0,7=L
3,0,7=O
color=FFA500
2,9.5,-7=a
2.8,9.5,-7=vm/s
color=AA5522
3.6,9.5,-7=hkkm/h
color=FFFFFF
4.4,0=xTelemetry Data:
5.3,0=Mt C
5.3,9=Mh%rH
6.5,0=MphPa
6.5,11=MbV

392
RX_FSK/data/style_map.css Executable file
View File

@ -0,0 +1,392 @@
body, html {
height: 100%;
margin: 0;
font-family: Arial;
}
.active, .cfgheader:hover {
background-color: #ccc;
}
.cfgpanel {
}
th.cfg {
padding:5pt
}
table.stat {
margin:0px 0px 5px 0px;
}
.hamburger {
position: relative;
display: inline-block;
width: 1.25em;
height: 0.8em;
margin-right: 0.3em;
border-top: 0.2em solid #fff;
border-bottom: 0.2em solid #fff;
}
.hamburger:before {
content: "";
position: absolute;
top: 0.3em;
left: 0px;
width: 100%;
border-top: 0.2em solid #fff;
}
.wrapper {
height: 100%;
display: flex;
flex-direction: column;
margin: 0;
}
.tci {
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;
background-color: #ddd
}
td#caption {
text-align: center;
background-color: #aaa;
font-weight: bold;
}
td#sfreq {
background-color: #ccc;
}
.content {
display: flex;
flex: 1;
flex-direction: column;
overflow: auto;
height: 100%;
}
.tabcontent {
display: none;
flex: 1;
border-top: none;
flex-direction: column;
overflow: auto;
}
html {
font-family: Helvetica;
display: inline-block;
margin: 0px auto;
text-align: center;
}
h1{
color: #0F3376;
font-size: 24px
}
p{
font-size: 1.5rem;
}
.button2 {
background-color: #f44336;
}
:disabled.save {
opacity: 0.5;
}
.save {
background-color: #CC1111; /* 0F33C6; */
border: white;
border-width: 1;
color: white;
padding: 8px 30px;
text-align: center;
text-decoration: none;
display: block;
font-size: 14px;
margin: 0
}
.ttgoinfo {
color: white;
padding: 8px 10px;
display: block;
font-size: 14px;
margin: 0
}
.ctlbtn {
background-color: #ccc;
border: black;
border-width: 1;
color: black;
padding: 4px 30px;
text-align: center;
text-decoration: none;
display: block;
margin: 2;
font-size: 4vh;
}
.update {
margin: 0;
display: block;
}
#map {
height: 100%;
}
.leaflet-popup-content table, .leaflet-popup-content table td {
border:0;
background-color: white;
}
.leaflet-popup-content table td:nth-child(2),.leaflet-popup-content table td:nth-child(5) {
text-align: right;
padding-left: 3px;
}
.leaflet-popup-content table td:nth-child(3),.leaflet-popup-content table td:nth-child(6) {
text-align: left;
padding-right: 10px;
}
.leaflet-gps{animation:fading 1s infinite}@keyframes fading{0%{opacity:0.7}50%{opacity:1}100%{opacity:0.7}}
.leaflet-gps::after {
content: '🔵';
}
.leaflet-gps {
margin-left: -7px !important;
margin-top: -9px !important;
}
.leaflet-burst::after {
content: '💥';
}
.leaflet-burst {
margin-left: -20px !important;
margin-top: -22px !important;
font-weight: bold;
font-size: 30px;
}
.leaflet-landing::after {
content: '×';
}
.leaflet-landing {
margin-left: -13px !important;
margin-top: -30px !important;
font-weight: bold;
font-size: 40px;
}
.leaflet-header {
text-align: center;
width: 250px;
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
pointer-events: auto !important;
}
.leaflet-header #settings {
display: none;
}
.leaflet-header label {
display: block;
margin-top: 5px;
}
.leaflet-header input {
width: 80px;
margin: 0 auto;
}
.leaflet-header #submit {
margin: 3px auto;
}
.leaflet-footer {
display:none;
text-align: center;
width: 180px;
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-center {
left:0;
right:0;
margin: 0 auto;
padding: 5px;
background: #fff;
background: rgba(255, 255, 255, 0.8);
}
.leaflet-header #sonde_detail {
display:none;
}
@media screen and (max-width: 600px) {
.leaflet-control-attribution {
-moz-transform: rotate(-90deg) translateX(100%);
-ms-transform: rotate(-90deg) translateX(100%);
-o-transform: rotate(-90deg) translateX(100%);;
-webkit-transform: rotate(-90deg) translateX(100%);
transform: rotate(-90deg) translateX(100%);
-webkit-transform-origin: 100% 100%;
-moz-transform-origin: 100% 100%;
-ms-transform-origin: 100% 100%;
-o-transform-origin: 100% 100%;
transform-origin: 100% 100%;
}
}
.ldot {
height: 15px;
width: 15px;
margin-top: 8px;
margin-left: -1px;
border-radius: 50%;
display: inline-block;
}
.ybg {
background-color: orange;
background-image: -webkit-gradient(linear, left top, left bottom, from(yellow), to(orange));
background-image: linear-gradient(top, yellow, orange);
}
.gbg {
background-color: green;
background-image: -webkit-gradient(linear, left top, left bottom, from(lime), to(green));
background-image: linear-gradient(top, lime, green);
}
.rbg {
background-color: red;
background-image: -webkit-gradient(linear, left top, left bottom, from(orange), to(red));
background-image: linear-gradient(top, orange, red);
}
#sonde_statbar .ldot {
margin-right: 3px;
}
/* Add a black background color to the top navigation */
.topnav {
background-color: #333;
overflow: hidden;
}
/* Style the links inside the navigation bar */
.topnav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}
/* Change the color of links on hover */
.topnav a:hover {
background-color: #ddd;
color: black;
}
/* Add an active class to highlight the current page */
.topnav a.active {
background-color: #04AA6D;
color: white;
}
/* Hide the link that should open and close the topnav on small screens */
.topnav .icon {
display: none;
padding-bottom: 12px;
padding-top: 11px;
}
/* When the screen is less than 600 pixels wide, hide all links, except for the first one ("Home"). Show the link that contains should open and close the topnav (.icon) */
@media screen and (max-width: 600px) {
.topnav a:not(.active) {display: none;}
.topnav a.icon {
float: right;
display: block;
}
}
/* The "responsive" class is added to the topnav with JavaScript when the user clicks on the icon. This class makes the topnav look good on small screens (display the links vertically instead of horizontally) */
@media screen and (max-width: 600px) {
.topnav.responsive {position: relative;}
.topnav.responsive a.icon {
position: absolute;
right: 0;
top: 0;
}
.topnav.responsive a {
float: none;
display: block;
text-align: left;
}
}
@media (prefers-color-scheme: dark) {
body {
background-color: #333;
}
h2{
color: white;
}
table, th, td, .save, a.active {
color: white;
border: 1px solid grey;
border-collapse: collapse;
background-color: #333;
}
input, select, .tci, .warning {
color: white;
background-color: #333;
}
a:link, a:visited {
color: #D3D3D3;
}
.topnav, td#sfreq {
background-color: grey;
}
.topnav a:visited {
color: white;
}
.ctlbtn {
color: white;
background-color: grey;
}
.leaflet-center {
color: white;
background-color: grey;
}
.leaflet-bar a {
background-color: grey !important;
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
color: white !important;
background: grey !important;
}
}

24
RX_FSK/features.h Normal file
View File

@ -0,0 +1,24 @@
// Configuration flags for including/excluding fuctionality from the compiled binary
// set flag to 0 for exclude/1 for include
/* data feed to sondehubv2 */
/* needs about 4k4 code, 200b data, 200b stack, 200b heap */
#define FEATURE_SONDEHUB 1
#define FEATURE_CHASEMAPPER 1
#define FEATURE_MQTT 0
#define FEATURE_RS92 1
/* Most recent version support fonts in a dedicated flash parition "fonts".
* This is incomabtible (in terms of code and flash layout) to previous versions.
* If LEGACY_FONTS_IN_CODEBIN is sets, fonts are also included in the bin image.
* This maintains compatibility for OTA with previous versions (in which case the
* bin image fonts will be used as before).
* The code automatically uses fonts in flash partition if that exists, otherwise
* fonts in code.
* The flash partition fonts support latin15 codeset (instead of 7bit ascii).
* Also, it is easier to use different fonts :) just flash the font partition w/ something else...
* This option will likely be removed post-master1.0
*/
#define LEGACY_FONTS_IN_CODEBIN 1

View File

@ -0,0 +1,43 @@
#include "Chasemapper.h"
extern const char *sondeTypeStrSH[];
int Chasemapper::send(WiFiUDP &udp, SondeInfo *si) {
char buf[1024];
struct tm tim;
time_t t = si->d.time;
gmtime_r(&t, &tim);
uint8_t realtype = si->type;
if (TYPE_IS_METEO(realtype)) {
realtype = si->d.subtype == 1 ? STYPE_M10 : STYPE_M20;
}
sprintf(buf, "{ \"type\": \"PAYLOAD_SUMMARY\","
"\"callsign\": \"%s\","
"\"latitude\": %.5f,"
"\"longitude\": %.5f,"
"\"altitude\": %d,"
"\"speed\": %d,"
"\"heading\": %d,"
"\"time\": \"%02d:%02d:%02d\","
"\"model\": \"%s\","
"\"freq\": \"%.3f MHz\"",
si->d.ser,
si->d.lat,
si->d.lon,
(int)si->d.alt,
(int)(si->d.hs * 1.9438445), // m/s into knots
(int)si->d.dir,
tim.tm_hour, tim.tm_min, tim.tm_sec,
sondeTypeStrSH[realtype],
si->freq);
if( !isnan(si->d.temperature) ) {
sprintf(buf + strlen(buf), ", \"temp\": %.1f", si->d.temperature);
}
strcat(buf, "}");
Serial.printf("Sending chasemapper json: %s\n", buf);
udp.beginPacket(sonde.config.cm.host, sonde.config.cm.port);
udp.write((const uint8_t *)buf, strlen(buf));
udp.endPacket();
return 0;
}

13
RX_FSK/src/Chasemapper.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef _CHASEMAPPER_H
#define _CHASEMAPPER_H
#include "Sonde.h"
//#include <WiFi.h>
#include <WiFiUdp.h>
#include <time.h>
class Chasemapper {
public:
static int send(WiFiUDP &udb, SondeInfo *si);
};
#endif

693
RX_FSK/src/DFM.cpp Normal file
View File

@ -0,0 +1,693 @@
/* DFM decoder functions */
#include "DFM.h"
#include "SX1278FSK.h"
#include "Sonde.h"
#define DFM_DEBUG 0
#if DFM_DEBUG
#define DFM_DBG(x) x
#else
#define DFM_DBG(x)
#endif
#define DFM_FRAMELEN 33
#define MAXIDAGE 1800
/*
* observed DAT patterns for DFM-9:
* A: 0+1; 2+3; 4+5; 6+7; 8+0 => keep frame in shadowFrame
* B: 1+2; 3+4; 5+6; 7+8 => all good => keep date in shadowDate
* C: 0+1; 2+3; 4+5; 6+7; 8+15 => all good => keep date in shadowDate
* D: 0+1; 2+3; 4+5; 6+7; 0+1 => use shadowDate
* not seen:5+6; 7+1
* values:
* 0:packet counter; 1:utc-msec; 2:lat,vh; 3:lon,dir, 4:alt,vv, 8=utc-date(day-hh-mm)
*/
// 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];
uint16_t good;
uint32_t datesec;
uint8_t frame;
uint16_t msec;
uint8_t nameregok;
uint8_t nameregtop;
uint8_t lastdat;
uint8_t cycledone; // 0=no; 1=OK, 2=partially/with errors
float meas[5+2];
} dfmstate;
decoderSetupCfg DFMSetupCfg {
.bitrate = 2500,
// continuous mode
// Enable auto-AFC, auto-AGC, RX Trigger by preamble ????
.rx_cfg = 0x1E,
.sync_cfg = 0x70,
.sync_len = 2,
.sync_data = (const uint8_t *)"\xAA\xAA",
.preamble_cfg = 0xA8,
};
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(DecoderBase::setup(DFMSetupCfg, sonde.config.dfm.agcbw, sonde.config.dfm.rxbw) != 0) {
return 1;
}
#if 0
// This is now all done by the generic setup method in base class
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(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(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;
}
}
#endif
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( res<0 ) ret = -1;
else if ( ret >= 0 && res > 0 ) ret++;
}
// 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
"", ""
};
void DFM::killid() {
SondeData *sd = &(sonde.si()->d);
sd->validID = false;
*(sd->id) = 0;
*(sd->ser) = 0;
memset((void *)&dfmstate, 0, sizeof(dfmstate));
}
#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;
SondeData *sd = &(sonde.si()->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(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);
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(sd->id, 10, "D%d", ((dfmstate.dat[2*i]<<16)|dfmstate.dat[2*i+1])%100000000);
Serial.print("\nNEW AUTOID:");
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);
}
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++;
}
}
static float get_Temp() {
SondeData *si = &(sonde.si()->d);
if(!si->validID) { // 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) {
f = dfmstate.meas[1];
f1 = dfmstate.meas[5];
f2 = dfmstate.meas[6];
}
Serial.printf("Meas: %f %f %f\n", f, f1, f2);
// as in autorx / dfm
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 g = f2/Rf;
float R = (f-f1) / g; // meas[0,3,4] > 0 ?
float T = 0; // T/Kelvin
if (f*f1*f2 == 0) R = 0;
if (R > 0) T = 1/(1/T0 + 1/BB0 * log(R/R0));
T = T - 273.15; // Celsius
if(T<-100 || T>50) {
Serial.printf("Temperature invalid: %f\n", T);
return NAN;
}
return T;
}
void DFM::decodeCFG(uint8_t *cfg)
{
SondeData *si = &(sonde.si()->d);
// new ID
finddfname(cfg);
// get meas
uint8_t conf_id = (*cfg)>>4;
if(conf_id<=6) {
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);
Serial.printf("meas %d is %f (%d,%d)\n", conf_id, dfmstate.meas[conf_id], val, exp);
}
// get batt
if(si->validID && si->subtype>=0x0A) {
// otherwise don't try, as we might not have the right type yet...
int cid = (si->subtype >= 0x0C) ? 0x7 : 0x5;
if(conf_id == cid) {
uint16_t val = cfg[1]<<8 | cfg[2];
si->batteryVoltage = val / 1000.0;
Serial.printf("battery: %f\n", si->batteryVoltage);
}
}
}
#if 0
// not used any more
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;
}
#endif
uint16_t MON[]={0,0,31,59,90,120,151,181,212,243,273,304,334};
void DFM::decodeDAT(uint8_t *dat)
{
// TODO: Here we need to work on a shadow copy of SondeData in order to prevent concurrent changes while using data in main loop
SondeData *si = &(sonde.si()->d);
Serial.print(" DAT["); Serial.print(dat[6]); Serial.print("]: ");
// We handle this case already here, because we need to update dfmstate.datesec before the cycle complete handling
if( dat[6]==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);
Serial.printf("Date: %04d-%02d-%02d %02d:%02dz ", y, m, d, h, mi);
si->sats = dat[4];
si->validPos |= 0x40;
// 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;
// If we get a time stamp much different to the previously received one, kill the ID.
// most likely, we have a new sonde now, so wait for the new ID.
if(tt-dfmstate.datesec > MAXIDAGE) killid();
dfmstate.datesec = tt;
dfmstate.good |= 0x100;
}
else if( dat[6]>8 ) return; // we ignore those...
/* Here we update data that should be updated only after receiving a "complete" frame (mainly for consistent SondeHub exports)
* We do this (a) when there is a DAT8 block and (b) when there is wrap from DAT7 to DAT0, for these fields:
* => frame
* => vframe (only used for SondeHub as virtual frame number)
* => time (calculated with using date and msec)
* [assuming that if there is no DAT8, then the date value did not change.]
*/
if( dat[6]==8 || dat[6] < dfmstate.lastdat) { // After DAT8, or after a "warp around"
if( dfmstate.good&1 ) si->frame = dfmstate.frame;
if( (dfmstate.good&0x102)==0x102 ) {
si->time = dfmstate.datesec + dfmstate.msec/1000;
// Lets be consistent with autorx: the timestamp uses the msec value truncated to seconds,
// whereas the virtual frame number for DFM uses the msec value rounded to full seconds.
// Actually, tt is real UTC, and the transformation to GPS seconds lacks adjusting for leap seconds
si->vframe = dfmstate.datesec + (dfmstate.msec+500)/1000 - 315964800;
}
// All fields updated? 1=OK, 2=with errors
Serial.printf("Cycle done: good is %x\n", dfmstate.good);
si->temperature = get_Temp();
Serial.printf("Temp: %f\n", si->temperature);
dfmstate.cycledone = ((dfmstate.good&0x11F)==0x11F) ? 1 : 2;
dfmstate.good = 0;
dfmstate.lastdat = 0;
} else {
dfmstate.lastdat = dat[6];
}
dfmstate.good |= (1<<dat[6]);
switch(dat[6]) {
case 0:
Serial.print("Packet counter: "); Serial.print(dat[3]);
dfmstate.frame = dat[3];
break;
case 1:
{
int val = (((uint16_t)dat[4])<<8) + (uint16_t)dat[5];
Serial.print("UTC-msec: "); Serial.print(val);
dfmstate.msec = val;
//uint32_t tmp = ((uint32_t)dat[0]<<24) + ((uint32_t)dat[1]<<16) + ((uint32_t)dat[2]<<8) + ((uint32_t)dat[3]);
//si->sats = bitCount(tmp);
}
break;
case 2:
{
float lat, vh;
lat = (int32_t)(((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);
lat = lat*0.0000001;
if( lat!=0 && si->lat!=0 && abs(lat-si->lat)>.25 ) killid();
si->lat = lat;
si->hs = vh*0.01;
if(lat!=0 || vh!=0) si->validPos |= 0x11; else si->validPos &= ~0x11;
}
break;
case 3:
{
float lon, dir;
lon = (int32_t)(((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];
lon = lon*0.0000001;
if( lon!=0 && si->lon!=0 && abs(lon-si->lon)>.25 ) killid();
si->lon = lon;
si->dir = dir*0.01;
Serial.print("GPS-lon: "); Serial.print(si->lon);
Serial.print(", dir: "); Serial.print(si->dir);
if(lon != 0 || dir != 0) si->validPos |= 0x22; else si->validPos &= ~0x22;
}
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);
si->alt = alt*0.01;
si->vs = vv*0.01;
if(alt!=0 || vv != 0) si->validPos |= 0x0C; else si->validPos &= ~0x0C;
}
break;
case 8:
// handled above
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() {
int rxframes = 5; // UP TO 5 frames, stop at type 8 frame
// tentative continuous RX version...
unsigned long t0 = millis();
dfmstate.cycledone = 0;
while( ( millis() - t0 ) < 1300 ) {
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(haveNewFrame) {
//Serial.printf("DFM::receive(): new frame complete after %ldms\n", millis()-t0);
haveNewFrame = 0;
rxframes--;
// OK: All DAT frames (0/1/2/3/4/8) have been received in the last cycle
if(dfmstate.cycledone==1) return RX_OK;
if(dfmstate.cycledone>1 || rxframes==0) return RX_ERROR;
}
delay(2);
}
}
return rxframes == 5 ? RX_TIMEOUT : RX_ERROR;
}
int DFM::decodeFrameDFM(uint8_t *data) {
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);
//Serial.printf("Hamming returns %d %d %d -- %d\n", ret0, ret1, ret2, ret0|ret1|ret2);
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);
if (ret0>=0) decodeCFG(byte_conf);
if (ret1>=0 && ret1<=4) decodeDAT(byte_dat1);
if (ret2>=0 && ret2<=4) decodeDAT(byte_dat2);
Serial.println("");
// Consistent with autorx: If more than 4 corrected bit errors in DAT block, assume it is possibly corrupt and
// don't treat it as a correct frame (ttgo display shows data anyway, but it is not sent to external sites)
if(ret1>4 || ret2>4) return RX_ERROR;
return (ret0|ret1|ret2)>=0 ? RX_OK : RX_ERROR;
}
// 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() {
return 0;
}
DFM dfm = DFM();

13
libraries/SondeLib/DFM.h → RX_FSK/src/DFM.h Executable file → Normal file
View File

@ -15,24 +15,31 @@
#ifndef inttypes_h
#include <inttypes.h>
#endif
#include "DecoderBase.h"
#define DFM_NORMAL 0
#define DFM_INVERSE 1
/* Main class */
class DFM
class DFM : public DecoderBase
{
private:
int inverse=0;
int stype;
char *stypename=NULL;
void deinterleave(uint8_t *str, int L, uint8_t *block);
uint32_t bits2val(const uint8_t *bits, int len);
int check(uint8_t code[8]);
int hamming(uint8_t *ham, int L, uint8_t *sym);
void printRaw(const char *prefix, int len, int ret, const uint8_t* data);
void finddfname(uint8_t *cfg);
void decodeCFG(uint8_t *cfg);
void decodeDAT(uint8_t *dat);
void bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int processDFMdata(uint8_t dt);
int decodeFrameDFM(uint8_t *data);
void killid();
#define B 8
#define S 4
@ -55,7 +62,7 @@ private:
public:
DFM();
// main decoder API
int setup(float frequency, int inverse);
int setup(float frequency, int type);
int receive();
int waitRXcomplete();

View File

@ -0,0 +1,51 @@
#include "DecoderBase.h"
#include "SX1278FSK.h"
#include "Sonde.h"
#define DECODERBASE_DEBUG 1
#if DECODERBASE_DEBUG
#define DBG(x) x
#else
#define DBG(x)
#endif
int DecoderBase::setup(decoderSetupCfg &setupcfg, uint16_t agcbw, uint16_t rxbw) {
if(sx1278.setFSK()!=0) {
DBG(Serial.println("Setting FSK mode FAILED"));
return 1;
}
if(sx1278.setBitrate(setupcfg.bitrate)!=0) {
DBG(Serial.println("Setting bitrate FAILED"));
return 1;
}
#if DECODERBASE_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(agcbw)!=0) {
DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", agcbw));
return 1;
}
if(sx1278.setRxBandwidth(rxbw)!=0) {
DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", rxbw));
return 1;
}
if(sx1278.setRxConf(setupcfg.rx_cfg)!=0) {
DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
if(sx1278.setSyncConf(setupcfg.sync_cfg, setupcfg.sync_len, setupcfg.sync_data)!=0) {
DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(setupcfg.preamble_cfg)!=0) {
DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
return 0;
}

38
RX_FSK/src/DecoderBase.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef DECODER_BASE_H
#define DECODER_BASE_H
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
#include "Sonde.h"
typedef struct _decoderSetupCfg {
uint16_t bitrate;
//uint16_t agcbw;
//uint16_t rxbw;
uint8_t rx_cfg;
uint8_t sync_cfg;
uint8_t sync_len;
const uint8_t *sync_data;
uint8_t preamble_cfg;
} decoderSetupCfg;
/* Generic base class for all sonde decoders */
class DecoderBase
{
protected:
public:
int setup(decoderSetupCfg &setupcfg, uint16_t agcbw, uint16_t rxbw);
virtual int setup(float frequency, int type=0) = 0;
virtual int receive() = 0;
virtual int waitRXcomplete() = 0;
};
#endif

View File

1329
libraries/SondeLib/Display.cpp → RX_FSK/src/Display.cpp Executable file → Normal file

File diff suppressed because it is too large Load Diff

76
libraries/SondeLib/Display.h → RX_FSK/src/Display.h Executable file → Normal file
View File

@ -1,14 +1,26 @@
#ifndef Display_h
#define Display_h
#define FONT_LARGE 1
#define FONT_SMALL 0
#include <SPI.h>
//#include <TFT22_ILI9225.h>
#include "gfxfont.h"
#include <Arduino_GFX_Library.h>
#include <U8x8lib.h>
#include <SPIFFS.h>
struct GpsPos {
double lat;
double lon;
int alt;
int course;
float speed;
int sat;
int accuracy;
int hdop;
int valid;
};
extern struct GpsPos gpsPos;
#define WIDTH_AUTO 9999
struct DispEntry {
@ -36,44 +48,59 @@ struct StatInfo {
uint8_t size;
};
struct CircleInfo { // 3,5=g0NCS,50,ff0000,000033,5,ffff00,4,ffffff
char type;
char top,bul,arr; // what to point to with top, bullet, array
uint16_t fgcol, bgcol;
uint8_t radius;
uint8_t brad;
uint16_t bcol;
uint8_t awidth;
uint16_t acol;
};
// Now starting towards supporting different Display types / libraries
class RawDisplay {
public:
virtual void begin() = 0;
virtual void clear() = 0;
virtual void setContrast(uint8_t contrast) = 0;
virtual void setFont(uint8_t fontindex) = 0;
virtual void getDispSize(uint8_t *height, uint8_t *width, uint8_t *lineskip, uint8_t *colskip) = 0;
virtual void drawString(uint8_t x, uint8_t y, const char *s, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0 ) = 0;
virtual void drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr) = 0;
virtual void drawString(uint16_t x, uint16_t y, const char *s, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0 ) = 0;
virtual void drawTile(uint16_t x, uint16_t y, uint8_t cnt, uint8_t *tile_ptr) = 0;
virtual void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color, bool fill) = 0;
virtual void drawBitmap(uint16_t x1, uint16_t y1, const uint16_t* bitmap, int16_t w, int16_t h) = 0;
virtual void welcome() = 0;
virtual void drawIP(uint8_t x, uint8_t y, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0 ) = 0;
virtual void drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0) = 0;
virtual void drawIP(uint16_t x, uint16_t y, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0 ) = 0;
virtual void drawQS(uint16_t x, uint16_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0) = 0;
};
class U8x8Display : public RawDisplay {
private:
U8X8 *u8x8 = NULL; // initialize later after reading config file
int _type;
uint8_t _type;
const uint8_t **fontlist;
int nfonts;
public:
U8x8Display(int type = 0) { _type = type; }
U8x8Display(uint8_t type = 0) { _type = type; }
void begin();
void clear();
void setContrast(uint8_t contrast);
void setFont(uint8_t fontindex);
void getDispSize(uint8_t *height, uint8_t *width, uint8_t *lineskip, uint8_t *colskip);
void drawString(uint8_t x, uint8_t y, const char *s, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0);
void drawTile(uint8_t x, uint8_t y, uint8_t cnt, uint8_t *tile_ptr);
void drawString(uint16_t x, uint16_t y, const char *s, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0);
void drawTile(uint16_t x, uint16_t y, uint8_t cnt, uint8_t *tile_ptr);
void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color, bool fill);
void drawBitmap(uint16_t x1, uint16_t y1, const uint16_t* bitmap, int16_t w, int16_t h);
void welcome();
void drawIP(uint8_t x, uint8_t y, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0);
void drawQS(uint8_t x, uint8_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0);
void drawIP(uint16_t x, uint16_t y, int16_t width=WIDTH_AUTO, uint16_t fg=0xffff, uint16_t bg=0);
void drawQS(uint16_t x, uint16_t y, uint8_t len, uint8_t size, uint8_t *stat, uint16_t fg=0xffff, uint16_t bg=0);
};
typedef Arduino_GFX MY_ILI9225;
class Display {
private:
void replaceLayouts(DispInfo *newlayout, int nnew);
@ -82,24 +109,19 @@ private:
int xscale=13, yscale=22;
int fontsma=0, fontlar=1;
uint16_t colfg, colbg;
static void circ(int x, int y);
static void circ(uint16_t *bm, int16_t w, int16_t x0, int16_t y0, int16_t r, uint16_t fg, boolean fill, uint16_t bg);
static int countEntries(File f);
void calcGPS();
void calcVbat();
void calcDurVol();
boolean gpsValid;
float gpsLat, gpsLon;
int gpsAlt;
int gpsDist; // -1: invalid
int gpsCourse, gpsDir, gpsBear; // 0..360; -1: invalid
int gpsDir, gpsBear; // 0..360; -1: invalid
boolean gpsCourseOld;
static const int LINEBUFLEN{ 255 };
static char lineBuf[LINEBUFLEN];
static const char *trim(char *s) {
char *ret = s;
while(*ret && isspace(*ret)) { ret++; }
int lastidx;
while(1) {
int lastidx;
lastidx = strlen(ret)-1;
if(lastidx>=0 && isspace(ret[lastidx]))
ret[lastidx] = 0;
@ -109,7 +131,8 @@ private:
return ret;
}
public:
void initFromFile();
static int getScreenIndex(int index);
void initFromFile(int index);
int layoutIdx;
DispInfo *layout;
@ -117,15 +140,13 @@ public:
DispInfo *layouts;
int nLayouts;
static RawDisplay *rdis;
uint16_t dispstate;
Display();
void init();
static char buf[17];
static void drawLat(DispEntry *de);
static void drawLon(DispEntry *de);
static void drawAz(DispEntry *de);
static void drawVBat(DispEntry *de);
static void drawDurVol(DispEntry *de);
static void drawAlt(DispEntry *de);
static void drawHS(DispEntry *de);
static void drawVS(DispEntry *de);
@ -147,17 +168,16 @@ public:
void setIP(const char *ip, bool AP);
void updateDisplayPos();
void updateDisplayPos2();
void updateDisplayAz();
void updateDisplayVBat();
void updateDisplayID();
void updateDisplayRSSI();
void updateStat();
void updateDisplayRXConfig();
void updateDisplayIP();
void updateDisplay();
void updateDisplayVBatt();
void dispsavectlON();
void dispsavectlOFF(int rxactive);
void setLayout(int layout);
void setContrast();
};
extern Display disp;

652
RX_FSK/src/M10M20.cpp Normal file
View File

@ -0,0 +1,652 @@
/* M10 and M20 decoder functions */
#include "M10M20.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#include <SPIFFS.h>
#define M10M20_DEBUG 1
#if M10M20_DEBUG
#define M10M20_DBG(x) x
#else
#define M10M20_DBG(x)
#endif
static byte data1[512];
static byte *dataptr=data1;
static uint8_t rxbitc;
static uint16_t rxbyte;
static int rxp=0;
static int haveNewFrame = 0;
//static int lastFrame = 0;
static int headerDetected = 0;
decoderSetupCfg m10m20SetupCfg = {
.bitrate = 9600,
//// Disable auto-AFC, auto-AGC, RX Trigger by preamble
.rx_cfg = 0x00,
.sync_cfg = 0x70,
.sync_len = 1,
.sync_data = (const uint8_t *)"\x66\x66",
// Preamble detection off (+ size 1 byte, maximum tolerance; should not matter for "off"?)
.preamble_cfg = 0x00 | 0x00 | 0x1F,
};
int M10M20::setup(float frequency, int /*type*/)
{
M10M20_DBG(Serial.println("Setup sx1278 for M10/M20 sonde"));;
if(sx1278.ON()!=0) {
M10M20_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
// setFSK: switches to FSK standby mode
if(sx1278.setFSK()!=0) {
M10M20_DBG(Serial.println("Setting FSK mode FAILED"));
return 1;
}
Serial.print("M10/M20: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// Test: maybe fix issue after spectrum display?
sx1278.writeRegister(REG_PLL_HOP, 0);
if(sx1278.setAFCBandwidth(sonde.config.m10m20.agcbw)!=0) {
M10M20_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.m10m20.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.m10m20.rxbw)!=0) {
M10M20_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.m10m20.rxbw));
return 1;
}
/// TODO: Maybe do this conditionally? -- maybe skip if afc if agcbw set to 0 or -1?
//// Step 1: Tentative AFC mode
sx1278.clearIRQFlags();
// preamble detector + AFC + AGC on
// wait for preamble interrupt within 2sec
sx1278.setBitrate(4800);
// DetectorOn=1, Preamble detector size 01, preamble tol 0x0A (10)
sx1278.setPreambleDetect(0x80 | 0x20 | 0x0A);
// Manual start RX, Enable Auto-AFC, Auto-AGC, RX Trigger (AGC+AFC)by preamble
sx1278.setRxConf(0x20 | 0x10 | 0x08 | 0x06);
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
M10M20_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
// enable RX
sx1278.setPayloadLength(0);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
unsigned long t0 = millis();
M10M20_DBG(Serial.printf("M10M20::setup() AFC preamble search start at %ld\n",t0));
while( millis() - t0 < 1000 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS1);
if(value & 2) {
int32_t afc = sx1278.getAFC();
int16_t rssi = sx1278.getRSSI();
Serial.printf("M10M20::setup: preamble: AFC is %d, RSSI is %.1f\n", afc, rssi/2.0);
sonde.sondeList[rxtask.currentSonde].rssi = rssi;
sonde.sondeList[rxtask.currentSonde].afc = afc;
break;
}
yield();
}
if( millis() - t0 >= 1000) {
Serial.println("Preamble scan for AFC: TIMEOUT\n");
return 1; // no preamble, so we may fail fast....
}
//// Step 2: Real reception
if( DecoderBase::setup(m10m20SetupCfg, sonde.config.m10m20.agcbw, sonde.config.m10m20.rxbw)!=0 ) {
return 1;
}
#if 0
// Now all done in DecoderBase::setup
// FSK standby mode, seems like otherweise baudrate cannot be changed?
sx1278.setFSK();
if(sx1278.setBitrate(9600)!=0) {
M10M20_DBG(Serial.println("Setting bitrate 9600bit/s FAILED"));
return 1;
}
M10M20_DBG(Serial.printf("Exact bitrate is %f\n", sx1278.getBitrate()));
// Probably not necessary, as this was set before
if(sx1278.setAFCBandwidth(sonde.config.m10m20.agcbw)!=0) {
M10M20_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.m10m20.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.m10m20.rxbw)!=0) {
M10M20_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.m10m20.rxbw));
return 1;
}
///// Enable auto-AFC, auto-AGC, RX Trigger by preamble
//if(sx1278.setRxConf(0x1E)!=0) {
// Disable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x00)!=0) {
M10M20_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// version 1, working with continuous RX
const char *SYNC="\x66\x66";
if(sx1278.setSyncConf(0x70, 1, (const uint8_t *)SYNC)!=0) {
M10M20_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
// Preamble detection off (+ size 1 byte, maximum tolerance; should not matter for "off"?)
if(sx1278.setPreambleDetect(0x00 | 0x00 | 0x1F)!=0) {
M10M20_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
M10M20_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
sx1278.setRxConf(0x20);
uint16_t afc = sx1278.getRawAFC();
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
delay(50);
sx1278.setRawAFC(afc);
delay(50);
Serial.printf("after RX_MODE: AFC is %d\n", sx1278.getAFC());
#if M10M20_DEBUG
M10M20_DBG(Serial.println("Setting SX1278 config for M10 finished\n"); Serial.println());
#endif
return res;
}
M10M20::M10M20() {
}
#define M10_FRAMELEN 101
#define M10_CRCPOS 99
#define M20_FRAMELEN 88
#define M20_CRCPOSB 22
void M10M20::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X ", data[i]);
Serial.print(buf);
}
Serial.println();
}
static uint16_t update_checkM10M20(int c, uint8_t b) {
int c0, c1, t, t6, t7, s;
c1 = c & 0xFF;
// B
b = (b >> 1) | ((b & 1) << 7);
b ^= (b >> 2) & 0xFF;
// A1
t6 = ( c & 1) ^ ((c >> 2) & 1) ^ ((c >> 4) & 1);
t7 = ((c >> 1) & 1) ^ ((c >> 3) & 1) ^ ((c >> 5) & 1);
t = (c & 0x3F) | (t6 << 6) | (t7 << 7);
// A2
s = (c >> 7) & 0xFF;
s ^= (s >> 2) & 0xFF;
c0 = b ^ t ^ s;
return ((c1 << 8) | c0) & 0xFFFF;
}
static uint16_t crc_M10M20(int len, uint8_t *msg) {
uint16_t cs = 0;
for (int i = 0; i < len; i++) {
cs = update_checkM10M20(cs, msg[i]);
}
return cs;
}
static bool checkM10M20crc(int crcpos, uint8_t *msg) {
uint16_t cs, cs1;
cs = crc_M10M20(crcpos, msg);
cs1 = (msg[crcpos] << 8) | msg[crcpos+1];
return (cs1 == cs);
}
typedef uint32_t SET256[8];
static SET256 sondeudp_VARSET = {0x03BBBBF0UL,0x80600000UL,0x06A001A0UL,
0x0000001CUL,0x00000000UL,0x00000000UL,0x00000000UL,
0x00000000UL};
// VARSET=SET256{4..9,11..13,15..17,19..21,23..25,53..54,63,69,71,72,85,87,89,90,98..100};
static SET256 sondeudp_VARSETM20 = {0xF3E27F54UL,0x0000000FUL,0x00000030UL,
0x00000000UL, 0x00444C39UL, 0x53445A00UL, 0x00000000UL,
0x00000000UL};
// VARSET=SET256{2,4,6,8..10,11..14,17,21..25,28..35,68,69}; (* known as variable *)
static uint8_t fixcnt[M10_FRAMELEN];
static uint8_t fixbytes[M10_FRAMELEN];
static int32_t getint32(uint8_t *data) {
return (int32_t)( data[3]|(data[2]<<8)|(data[1]<<16)|(data[0]<<24) );
}
static int32_t getint24(uint8_t *data) {
return (int32_t)(data[2]|(data[1]<<8)|(data[0]<<16) );
}
static int32_t getint24_r(uint8_t *data) {
return (int32_t)(data[0]|(data[1]<<8)|(data[2]<<16) );
}
static int16_t getint16(uint8_t *data) {
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) {
nr = nr%10;
return '0'+nr;
}
static char hex(uint8_t nr) {
nr = nr&0x0f;
if(nr<10) return '0'+nr;
else return 'A'+nr-10;
}
const static float DEGMUL = 1.0/0xB60B60;
#define VMUL 0.005
#define VMUL_M20 0.01
#ifndef PI
#define PI (3.1415926535897932384626433832795)
#endif
#define RAD (PI/180)
// ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m10dop-alternativ)
int M10M20::decodeframeM10(uint8_t *data) {
int repairstep = 16;
int repl = 0;
bool crcok;
// error correction, inspired by oe5dxl's sondeudp
do {
crcok = checkM10M20crc(M10_CRCPOS, data);
if(crcok || repairstep==0) break;
repl = 0;
for(int i=0; i<M10_CRCPOS; i++) {
if( ((sondeudp_VARSET[i/32]&(1<<(i%32))) == 0) && (fixcnt[i]>=repairstep) ) {
repl++;
data[i] = fixbytes[i];
}
}
repairstep >>= 1;
} while(true);
if(crcok) {
for(int i=0; i<M10_CRCPOS; i++) {
if(fixbytes[i]==data[i] &&fixcnt[i]<255) fixcnt[i]++;
else { fixcnt[i]=0; fixbytes[i]=data[i]; }
}
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
Serial.printf(" repair: %d/%d\n", repl, repairstep);
if(!crcok) return 2;
if(data[1]==0x9F && data[2]==0x20) {
Serial.println("Decoding...");
//SondeInfo *si = sonde.si();
SondeData *si = &(sonde.si()->d);
// Its a M10
// getid...
char ids[12];
ids[0] = 'M';
ids[1] = 'E';
ids[2] = hex(data[95]/16);
ids[3] = hex(data[95]);
ids[4] = hex(data[93]);
uint32_t id = data[96] + data[97]*256;
ids[5] = hex(id/4096);
ids[6] = hex(id/256);
ids[7] = hex(id/16);
ids[8] = hex(id);
ids[9] = 0;
strncpy(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] = '-';
ids[4] = dez(data[93]);
ids[5] = '-';
ids[6] = dez(id>>13);
id &= 0x1fff;
ids[7] = dez(id/1000);
ids[8] = dez((id/100)%10);
ids[9] = dez((id/10)%10);
ids[10] = dez(id%10);
ids[11] = 0;
strncpy(si->ser, ids, 12);
si->validID = true;
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
// position data
si->lat = getint32(data+14) * DEGMUL;
si->lon = getint32(data+18) * DEGMUL;
si->alt = getint32(data+22) * 0.001;
float ve = getint16(data+4)*VMUL;
float vn = getint16(data+6)*VMUL;
si->vs = getint16(data+8) * VMUL;
si->hs = sqrt(ve*ve+vn*vn);
si->sats = data[30];
float dir = atan2(ve, vn)*(1.0/RAD);
if(dir<0) dir+=360;
si->dir = dir;
si->validPos = 0x3f;
// m10 temp
float T = NAN;
{
const float p0 = 1.07303516e-03, p1 = 2.41296733e-04, p2 = 2.26744154e-06, p3 = 6.52855181e-08;
const float Rs[3] = { 12.1e3 , 36.5e3 , 475.0e3 };
const float Rp[3] = { 1e20 , 330.0e3 , 2000.0e3 };
uint8_t sct = data[62];
float rt = getint16_r(data+63) & (0xFFF);
if(rt!=0 && sct<3) {
rt = (4095-rt)/rt - (Rs[sct]/Rp[sct]);
if(rt>0) {
rt = Rs[sct] / rt;
if(rt>0) {
rt = log(rt);
rt = 1/( p0 + p1*rt + p2*rt*rt + p3*rt*rt*rt ) - 273.15;
if(rt>-99 && rt<50) { T = rt; }
}
}
}
si->temperature = T;
}
// m10 battery
uint16_t batADC = (uint16_t)getint16_r(data+0x45);
si->batteryVoltage = 2.709 * batADC * 2.5/1023.0;
// m10 humidity
{
float cRHc55 = ((float)(uint32_t)getint24_r(data+0x35)) / (uint32_t)getint24_r(data+0x32);
float TH = -273.15;
const float huRs = 22.1e3;
const float p0 = 4.42606809e-03, p1 = -6.58184309e-04, p2 = 8.95735557e-05, p3 = -2.84347503e-06;
float R = huRs / ( (4095.0/getint16_r(data+0x59)) - 1 );
if(R>0) TH += 1/( p0 + p1*log(R) + p2*log(R)*log(R) + p3*log(R)*log(R)*log(R) );
//float Tc = T;
float rh = (cRHc55-0.8955)/0.002;
const float T0=0.0, T1=-30.0;
//float T = Tc;
if(T<T0) rh += T0 - T/5.5;
if(T<T1) rh *= 1.0 + (T1-T)/75.0;
if(rh<0.0) rh=0.0;
if(rh>100.0) rh=100.0;
si->relativeHumidity = rh;
}
Serial.printf("hum: %.2f batt: %.2f\n", si->relativeHumidity, si->batteryVoltage);
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
si->time = (gpstime/1000) + 86382 + gpsweek*604800 + 315878400UL;
// consistent with autorx, vframe is based on GPS time without the -18 seconds adjustment
// for the GPS time / UTC time difference (included in 86382 above)
si->vframe = si->time - 315964800 + 18;
si->validTime = true;
} else {
Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]);
return 0;
}
return 1;
}
static uint32_t rxdata;
static bool rxsearching=true;
static bool isM20=false;
// search for
// //101001100110011010011010011001100110100110101010100110101001
// //1010011001100110100110100110 0110.0110 1001.1010 1010.1001 1010.1001 => 0x669AA9A9
void M10M20::processM10data(uint8_t dt)
{
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
rxbyte = rxbyte ^ d;
}
//
if(rxsearching) {
if( rxdata == 0xcccca64c || rxdata == 0x333359b3 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
isM20 = false;
headerDetected = 1;
#if 1
int rssi=sx1278.getRSSI();
int fei=sx1278.getFEI();
int afc=sx1278.getAFC();
Serial.print("SYNC!!! Test: RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
#endif
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
//Serial.printf("%03x ",rxbyte);
dataptr[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
// detect type of sonde:
// 64 9F 20 => M10
// 64 49 0x => M10 (?) -- not used here
// 45 20 7x => M20
if(rxp==2 && dataptr[0]==0x45 && dataptr[1]==0x20) { isM20 = true; }
if(isM20) {
memcpy(sonde.si()->d.typestr, "M20 ", 5);
sonde.si()->d.subtype = 2;
if(rxp>=M20_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM20(dataptr);
}
} else {
memcpy(sonde.si()->d.typestr, "M10 ", 5);
sonde.si()->d.subtype = 1;
if(rxp>=M10_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM10(dataptr);
}
}
}
}
}
}
int M10M20::receive() {
unsigned long t0 = millis();
Serial.printf("M10M20::receive() start at %ld\n",t0);
while( millis() - t0 < 1100 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: ready()");
sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
//Serial.printf("%02x:",data);
processM10data(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
if(haveNewFrame) {
Serial.printf("M10M20::receive(): new frame complete after %ldms\n", millis()-t0);
printRaw(dataptr, M10_FRAMELEN);
int retval = haveNewFrame==1 ? RX_OK: RX_ERROR;
haveNewFrame = 0;
return retval;
}
delay(2);
}
}
int32_t afc = sx1278.getAFC();
int16_t rssi = sx1278.getRSSI();
Serial.printf("receive: AFC is %d, RSSI is %.1f\n", afc, rssi/2.0);
Serial.printf("M10M20::receive() timed out\n");
return RX_TIMEOUT; // TODO RX_OK;
}
#define M10MAXLEN (240)
int M10M20::waitRXcomplete() {
return 0;
}
// ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m20dop-alternativ)
int M10M20::decodeframeM20(uint8_t *data) {
int repairstep = 16;
int frl;
int repl = 0;
bool crcok = false;
bool crcbok = false;
//SondeInfo *si = sonde.si();
SondeData *si = &(sonde.si()->d);
// error correction, inspired by oe5dxl's sondeudp
// check first block
uint8_t s[200];
s[0] = 0x16;
for(int i=1; i<=M20_CRCPOSB-1; i++) { s[i] = data[i+1]; }
crcbok = (crc_M10M20(M20_CRCPOSB-1, s) ==
((data[M20_CRCPOSB] << 8) | data[M20_CRCPOSB+1]));
frl = data[0] + 1; // frame len? (0x45+1 => 70)
if(frl>M20_FRAMELEN) { frl = M20_FRAMELEN; }
do {
crcok = checkM10M20crc(frl-2, data);
if(crcok || repairstep == 0) break;
repl = 0;
for(int i=crcbok?M20_CRCPOSB+2:0; i<frl-2; i++) {
if( ((sondeudp_VARSETM20[i/32]&(1<<(i%32))) == 0) && (fixcnt[i]>=repairstep) ) {
repl++;
data[i] = fixbytes[i];
}
}
repairstep >>= 1;
} while(true);
if(crcbok) {
int oklen = crcok ? frl-2 : 21;
for(int i=0; i<oklen; i++) {
if(fixbytes[i]==data[i]) { if(fixcnt[i]<255) fixcnt[i]++; }
else { fixcnt[i]=0; fixbytes[i]=data[i]; }
}
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
Serial.printf(" repair: %d/%d\n", repl, repairstep);
if(!crcok) return 2;
Serial.println("Decoding...");
// Its a M20
// getid...
// TODO: Adjust ID calculation and serial number reconstruction
char ids[11]={'M','E','0','0','0','0','0','0','0','0','0'};
ids[0] = 'M';
ids[1] = 'E';
uint32_t id = data[18]; // getint16(data+18);
ids[2] = hex(id/16);
ids[3] = hex(id);
//
id = getint16_r(data+19)/4;
ids[4] = (char)((id/10000)%10+48);
ids[5] = (char)((id/1000)%10+48);
ids[6] = (char)((id/100)%10+48);
ids[7] = (char)((id/10)%10+48);
ids[8] = (char)(id%10+48);
ids[9] = 0;
strncpy(si->id, ids, 10);
// Serial: AAB-C-DDEEE
char *ser = si->ser;
uint8_t tmp = data[18] & 0x7F;
ser[0] = (tmp/12) + '0';
ser[1] = ((tmp%12 + 1) / 10 ) + '0';
ser[2] = ((tmp%12 + 1) % 10 ) + '0';
ser[3] = '-';
ser[4] = ((data[19]&0x03)<<1) + (data[18]/128) + 1 + '0';
ser[5] = '-';
ser[6] = ids[4];
ser[7] = ids[5];
ser[8] = ids[6];
ser[9] = ids[7];
ser[10] = ids[8];
ser[11] = 0;
// TODO
if(crcok) {
si->validID = true;
//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
// position data
// 0x1C 4 byte
si->lat = getint32(data+28) * 1e-6;
//0x20 4 byte
si->lon = getint32(data+32) * 1e-6;
//0x08 3 byte
si->alt = getint24(data+8) * VMUL_M20;
//0x0B 2 byte
//VMUL_M20 specific
float ve = getint16(data+11)*VMUL_M20;
//0x0D 2 byte
float vn = getint16(data+13)*VMUL_M20;
//0x18 2 byte
si->vs = getint16(data+24) * VMUL_M20;
si->hs = sqrt(ve*ve+vn*vn);
float dir = atan2(ve, vn)*(1.0/RAD);
if(dir<0) dir+=360;
si->dir = dir;
si->validPos = 0x3f;
//0x0F 3 byte
uint32_t tow = getint24(data+15);
uint16_t week = getint16(data+26);
si->time = (tow+week*604800+315964800)-18;
si->vframe =si->time - 315964800;
si->validTime = true;
}
return crcok?1:2;
}
M10M20 m10m20 = M10M20();

50
libraries/SondeLib/M10.h → RX_FSK/src/M10M20.h Executable file → Normal file
View File

@ -1,13 +1,13 @@
/*
* M10.h
* Functions for decoding Meteomodem M10 sondes with SX127x chips
* M10M20.h
* Functions for decoding Meteomodem M10M20 sondes with SX127x chips
* Copyright (C) 2019 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef M10_h
#define M10_h
#ifndef M10M20_h
#define M10M20_h
#include <stdlib.h>
#include <stdint.h>
@ -15,46 +15,16 @@
#ifndef inttypes_h
#include <inttypes.h>
#endif
#if 0
struct CONTEXTR9 {
char calibdata[512];
uint32_t calibok;
char mesok;
char posok;
char framesent;
double lat;
double lon;
double heig;
double speed;
double dir;
double climb;
double lastlat;
double laslong;
double lastalt;
double lastspeed;
double lastdir;
double lastclb;
float hrmsc;
float vrmsc;
double hp;
double hyg;
double temp;
double ozontemp;
double ozon;
uint32_t goodsats;
uint32_t timems;
uint32_t framenum;
};
#endif
#include "DecoderBase.h"
/* Main class */
class M10
class M10M20 : public DecoderBase
{
private:
void printRaw(uint8_t *data, int len);
void processM10data(uint8_t data);
int decodeframeM10(uint8_t *data);
int decodeframeM20(uint8_t *data);
#if 0
void stobyte92(uint8_t byte);
void dogps(const uint8_t *sf, int sf_len,
@ -83,14 +53,14 @@ private:
#endif
public:
M10();
int setup(float frequency);
M10M20();
int setup(float frequency, int type = 0);
int receive();
int waitRXcomplete();
//int use_ecc = 1;
};
extern M10 m10;
extern M10M20 m10m20;
#endif

559
RX_FSK/src/MP3H.cpp Normal file
View File

@ -0,0 +1,559 @@
/* MP3H decoder functions */
#include "MP3H.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#include <SPIFFS.h>
#define MP3H_DEBUG 1
#if MP3H_DEBUG
#define MP3H_DBG(x) x
#else
#define MP3H_DBG(x)
#endif
static struct st_mp3hstate {
uint32_t id1, id2;
uint8_t idok;
uint32_t gpsdate;
uint32_t gpsdatetime;
bool dateok;
} mp3hstate;
static byte data1[512];
static byte *dataptr=data1;
static uint8_t rxbitc;
static uint16_t rxbyte;
static int rxp=0;
static int haveNewFrame = 0;
//static int lastFrame = 0;
static int headerDetected = 0;
extern uint16_t MON[];
decoderSetupCfg mp3hSetupCfg = {
.bitrate = 2400,
.rx_cfg = 0x00,
.sync_cfg = 0x70,
.sync_len = 1,
.sync_data = (const uint8_t *)"\x66\x66",
.preamble_cfg = 0x00 | 0x00 | 0x1F
};
int MP3H::setup(float frequency, int /*type*/)
{
MP3H_DBG(Serial.println("Setup sx1278 for MP3H sonde"));;
if(sx1278.ON()!=0) {
MP3H_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
// setFSK: switches to FSK standby mode
if(sx1278.setFSK()!=0) {
MP3H_DBG(Serial.println("Setting FSK mode FAILED"));
return 1;
}
Serial.print("MP3H: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// Test: maybe fix issue after spectrum display?
sx1278.writeRegister(REG_PLL_HOP, 0);
if(sx1278.setAFCBandwidth(sonde.config.mp3h.agcbw)!=0) {
MP3H_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.mp3h.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.mp3h.rxbw)!=0) {
MP3H_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.mp3h.rxbw));
return 1;
}
//// Step 2: Real reception
if(DecoderBase::setup(mp3hSetupCfg, sonde.config.mp3h.agcbw, sonde.config.mp3h.rxbw)!=0) {
return 1;
}
#if 0
// Now all done in Decoderbase
// FSK standby mode, seems like otherweise baudrate cannot be changed?
sx1278.setFSK();
if(sx1278.setBitrate(2400)!=0) {
MP3H_DBG(Serial.println("Setting bitrate 2400bit/s FAILED"));
return 1;
}
MP3H_DBG(Serial.printf("Exact bitrate is %f\n", sx1278.getBitrate()));
// Probably not necessary, as this was set before
if(sx1278.setAFCBandwidth(sonde.config.mp3h.agcbw)!=0) {
MP3H_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.mp3h.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.mp3h.rxbw)!=0) {
MP3H_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.mp3h.rxbw));
return 1;
}
///// Enable auto-AFC, auto-AGC, RX Trigger by preamble
//if(sx1278.setRxConf(0x1E)!=0) {
// Disable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x00)!=0) {
MP3H_DBG(Serial.println("Setting RX Config FAILED"));
return 1;
}
// version 1, working with continuous RX
const char *SYNC="\x66\x66";
if(sx1278.setSyncConf(0x70, 1, (const uint8_t *)SYNC)!=0) {
MP3H_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
// Preamble detection off (+ size 1 byte, maximum tolerance; should not matter for "off"?)
if(sx1278.setPreambleDetect(0x00 | 0x00 | 0x1F)!=0) {
MP3H_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
MP3H_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
//sx1278.setRxConf(0x20);
uint16_t afc = sx1278.getRawAFC();
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
delay(50);
sx1278.setRawAFC(afc);
delay(50);
Serial.printf("after RX_MODE: AFC is %d\n", sx1278.getAFC());
memset((void *)&mp3hstate, 0, sizeof(mp3hstate));
#if MP3H_DEBUG
MP3H_DBG(Serial.println("Setting SX1278 config for MP3H finished\n"); Serial.println());
#endif
return res;
}
MP3H::MP3H() {
}
#define MP3H_FRAMELEN 49
// offsets from zilog
// https://github.com/rs1729/RS/blob/master/demod/mod/mp3h1mod.c
#define OFS -3
#define pos_CNT1 (OFS+ 3) // 1 nibble (0x80..0x8F ?)
#define pos_TIME (OFS+ 4) // 3*1 byte
#define pos_GPSecefX (OFS+ 8) // 4 byte
#define pos_GPSecefY (OFS+12) // 4 byte
#define pos_GPSecefZ (OFS+16) // 4 byte
#define pos_GPSecefV (OFS+20) // 3*2 byte
#define pos_GPSnSats (OFS+26) // 1 byte (num Sats ?)
#define pos_PTU1 (OFS+35) // 4 byte
#define pos_PTU2 (OFS+39) // 4 byte
#define pos_CNT2 (OFS+43) // 1 byte (0x01..0x10 ?)
#define pos_CFG (OFS+44) // 2/4 byte
#define pos_CRC (OFS+48) // 2 byte
#define crc16poly 0xA001
static bool checkMP3CRC(uint8_t *data)
{
int start = pos_CNT1;
int len = 45;
uint16_t rem = 0xffff;
for(int i=0; i<len; i++) {
rem ^= data[start+i];
for(int j=0; j<8; j++) {
if(rem&0x1) rem = (rem>>1) ^ crc16poly;
else rem = rem>>1;
}
}
uint16_t crcdat = data[pos_CRC] | (data[pos_CRC+1]<<8);
return rem == crcdat ? true : false;
}
void MP3H::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X ", data[i]);
Serial.print(buf);
}
Serial.println();
}
#ifndef PI
#define PI (3.1415926535897932384626433832795)
#endif
#define RAD (PI/180)
#define DEG (180/PI)
static uint32_t u4(uint8_t *d)
{
return d[0] | (d[1]<<8) | (d[2]<<16) | (d[3]<<24);
}
#define i4(d) ((int32_t)u4(d))
static uint16_t u2(uint8_t *d)
{
return d[0] | (d[1]<<8);
}
#define i2(d) ((int16_t)u2(d))
// defined in RS41.cpp
extern void wgs84r(double x, double y, double z, double * lat, double * long0, double * heig);
extern double atang2(double x, double y);
void calcgps(uint8_t *buf) {
//SondeInfo *si = sonde.si();
SondeData *si =&(sonde.si()->d);
double wx = i4(buf+pos_GPSecefX) * 0.01;
double wy = i4(buf+pos_GPSecefY) * 0.01;
double wz = i4(buf+pos_GPSecefZ) * 0.01;
double vx = i2(buf+pos_GPSecefV) * 0.01;
double vy = i2(buf+pos_GPSecefV+2) * 0.01;
double vz = i2(buf+pos_GPSecefV+4) * 0.01;
if(wx==0 && wy==0 && wz==0) { if(si->validPos&0x7f) { si->validPos |= 0x80; } return; }
// wgs84r
double lat, lng, alt;
wgs84r(wx, wy, wz, &lat, &lng, &alt);
if(alt<-1000 || alt>80000) { if(si->validPos&0x7f) { si->validPos |= 0x80; } return; }
si->lat = (float)(lat*DEG);
si->lon = (float)(lng*DEG);
si->alt = alt;
// speeddir
double sinlat = sin(lat);
double coslat = cos(lat);
double sinlng = sin(lng);
double coslng = cos(lng);
double vn = -vx*sinlat*coslng - vy*sinlat*sinlng + vz*coslat;
double ve = -vx*sinlng + vy*coslng;
double clb = vx*coslat*coslng + vy*coslat*sinlng + vz*sinlat;
double dir = atang2(vn, ve)/RAD;
if(dir<0.0) dir+=360.0;
si->dir = dir;
si->vs = clb;
si->hs = sqrt(vn*vn + ve*ve);
si->sats = buf[pos_GPSnSats];
Serial.printf("Pos: %f %f alt %f dir %f vs %f hs %f sats %d\n", si->lat, si->lon, si->alt, si->dir, si->vs, si->hs, si->sats);
si->validPos = 0x7f;
}
static uint32_t getgpstime(uint8_t *buf) {
return buf[pos_TIME] * 60*60 + buf[pos_TIME+1] * 60 + buf[pos_TIME+2];
}
// unix time stamp from date and time info in frame.
static void getmp3htime(uint8_t *buf) {
//SondeInfo *si = sonde.si();
SondeData *si =&(sonde.si()->d);
// gpsdate from CFG frame 15 (0 if not yet received)
uint32_t gpsdate = mp3hstate.gpsdate;
uint32_t gpstime = getgpstime(buf);
int tt = 0;
if(gpsdate) {
uint16_t year = (gpsdate%100)+2000;
gpsdate /= 100;
uint8_t month = gpsdate%100;
gpsdate /= 100;
uint8_t day = gpsdate % 100;
// year-month-day to unix time
tt = (year-1970)*365 + (year-1969)/4; // days since 1970
if(month<=12) { tt += MON[month]; if((year%4)==0 && month>2) tt++; }
tt = (tt+day-1)*(60*60*24);
if(gpstime < mp3hstate.gpsdatetime) tt += 60*60*24; // time wrapped since last date tx
Serial.printf("date: %04d-%02d-%02d t%d ", year, month, day, gpstime);
}
tt += gpstime;
si->time = tt;
si->vframe = tt - 315964800;
Serial.printf(" mp3h TIMESTAMP: %d\n", tt);
}
static uint8_t hex(uint32_t n) {
n = n % 16;
return (n<10) ? (n+'0') : (n-10+'A');
}
static void resetmp3h() {
mp3hstate.id1 = mp3hstate.id2 = 0;
mp3hstate.idok = 0;
mp3hstate.gpsdate = 0;
mp3hstate.dateok = 0;
}
// ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m10dop-alternativ)
int MP3H::decodeframeMP3H(uint8_t *data) {
printRaw(data, MP3H_FRAMELEN);
//
if(!checkMP3CRC(data)) {
// maybe add repairing frames later...
return 2;
}
// data is a frame with correct CRC
//SondeInfo *si = sonde.si();
SondeData *si =&(sonde.si()->d);
uint8_t cnt = data[pos_CNT1] & 0x0F;
uint32_t cfg = u4(data+pos_CFG);
if(cnt==15) {
// date
mp3hstate.gpsdate = cfg;
mp3hstate.gpsdatetime = getgpstime(data);
mp3hstate.dateok = true;
} else if(cnt==13) {
// id2
if(mp3hstate.id2 > 0 && mp3hstate.id2 != cfg) { resetmp3h(); }
mp3hstate.id2 = cfg;
mp3hstate.idok |= 2;
} else if(cnt==12) {
// id1
if(mp3hstate.id1 > 0 && mp3hstate.id1 != cfg) { resetmp3h(); }
mp3hstate.id1 = cfg;
mp3hstate.idok |= 1;
}
// get id
if((mp3hstate.idok&3) == 3) {
//...
//si->type = STYPE_MP3H;
uint32_t n = mp3hstate.id1*100000 + mp3hstate.id2;
si->id[0] = 'M';
si->id[1] = 'R';
si->id[2] = 'Z';
si->id[3] = hex(n/0x100000);
si->id[4] = hex(n/0x10000);
si->id[5] = hex(n/0x1000);
si->id[6] = hex(n/0x100);
si->id[7] = hex(n/0x10);
si->id[8] = hex(n);
si->id[9] = 0;
snprintf(si->ser, 12, "%u-%u", mp3hstate.id1, mp3hstate.id2);
si->validID = true;
}
// position
calcgps(data);
// time
getmp3htime(data);
return 1;
#if 0
int repairstep = 16;
int repl = 0;
bool crcok;
// error correction, inspired by oe5dxl's sondeudp
do {
crcok = checkMP3Hcrc(M10_CRCPOS, data);
if(crcok || repairstep==0) break;
repl = 0;
for(int i=0; i<M10_CRCPOS; i++) {
if( ((sondeudp_VARSET[i/32]&(1<<(i%32))) == 0) && (fixcnt[i]>=repairstep) ) {
repl++;
data[i] = fixbytes[i];
}
}
repairstep >>= 1;
} while(true);
if(crcok) {
for(int i=0; i<M10_CRCPOS; i++) {
if(fixbytes[i]==data[i] &&fixcnt[i]<255) fixcnt[i]++;
else { fixcnt[i]=0; fixbytes[i]=data[i]; }
}
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
Serial.printf(" repair: %d/%d\n", repl, repairstep);
if(data[1]==0x9F && data[2]==0x20) {
Serial.println("Decoding...");
// Its a M10
// getid...
char ids[11];
ids[0] = 'M';
ids[1] = 'E';
ids[2] = hex(data[95]/16);
ids[3] = hex(data[95]);
ids[4] = hex(data[93]);
uint32_t id = data[96] + data[97]*256;
ids[5] = hex(id/4096);
ids[6] = hex(id/256);
ids[7] = hex(id/16);
ids[8] = hex(id);
ids[9] = 0;
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);
strncpy(sonde.si()->ser, ids, 10);
sonde.si()->validID = true;
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
// position data
sonde.si()->lat = getint32(data+14) * DEGMUL;
sonde.si()->lon = getint32(data+18) * DEGMUL;
sonde.si()->alt = getint32(data+22) * 0.001;
float ve = getint16(data+4)*VMUL;
float vn = getint16(data+6)*VMUL;
sonde.si()->vs = getint16(data+8) * VMUL;
sonde.si()->hs = sqrt(ve*ve+vn*vn);
float dir = atan2(vn, ve)*(1.0/RAD);
if(dir<0) dir+=360;
sonde.si()->dir = dir;
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;
sonde.si()->validTime = true;
} else {
Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]);
return 0;
}
return crcok?1:2;
#endif
return 0;
}
static uint32_t rxdata;
static bool rxsearching=true;
// search for
// 0xBF3H (or inverse)
void MP3H::processMP3Hdata(uint8_t dt)
{
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
// rxbyte = rxbyte ^ d;
}
// BF3H => 1011 1111 0011 0101 => 10011010 10101010 01011010 01100110 => 9AAA5A66 // 6555a599
if(rxsearching) {
if( rxdata == 0x9AAA5A66 || rxdata == 0x6555a599 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
headerDetected = 1;
Serial.print("SYNC\n");
int rssi=sx1278.getRSSI();
int fei=sx1278.getFEI();
int afc=sx1278.getAFC();
Serial.print("SYNC!!! Test: RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
dataptr[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
//if(rxp==2 && dataptr[0]==0x45 && dataptr[1]==0x20) { isM20 = true; }
if(rxp>=MP3H_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeMP3H(dataptr);
}
}
}
}
}
#define MAXFRAMES 6
int MP3H::receive() {
// we wait for at most 6 frames or until a new seq nr.
uint8_t nFrames = MAXFRAMES; // MP3H sends every frame 6x
static uint32_t lastFrame = 0;
uint8_t retval = RX_TIMEOUT;
unsigned long t0 = millis();
Serial.printf("MP3H::receive() start at %ld\n",t0);
while( millis() - t0 < 1100 + (retval!=RX_TIMEOUT)?1000:0 ) {
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
if ( bitRead(value, 7) ) {
Serial.println("FIFO full");
}
if ( bitRead(value, 4) ) {
Serial.println("FIFO overflow");
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: ready()");
sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
Serial.printf("%02x:",data);
processMP3Hdata(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
if(haveNewFrame) {
Serial.printf("MP3H::receive(): new frame complete after %ldms\n", millis()-t0);
printRaw(dataptr, MP3H_FRAMELEN);
nFrames--;
// frame with CRC error: just skip and retry (unless we have waited for 6 frames alred)
if(haveNewFrame != 1) {
Serial.printf("hNF: %d (ERROR)\n", haveNewFrame);
retval = RX_ERROR;
} else if (sonde.si()->d.time == lastFrame) { // same frame number as seen before => skip
Serial.printf("Skipping frame with frame# %d\n", lastFrame);
// nothing, wait for next, "new" frame
} else { // good and new frame, return it.
Serial.println("Good frame");
haveNewFrame = 0;
lastFrame = sonde.si()->d.time;
return RX_OK;
}
haveNewFrame = 0;
#if 0
if(nFrames <= 0) {
// up to 6 old or erronous frames received => break out
Serial.printf("nFrames is %di, giving up\n", nFrames);
break;
}
#endif
}
delay(2);
}
}
int32_t afc = sx1278.getAFC();
int16_t rssi = sx1278.getRSSI();
Serial.printf("receive: AFC is %d, RSSI is %.1f\n", afc, rssi/2.0);
Serial.printf("MP3H::receive() timed out\n");
return retval;
}
int MP3H::waitRXcomplete() {
return 0;
}
MP3H mp3h = MP3H();

36
RX_FSK/src/MP3H.h Normal file
View File

@ -0,0 +1,36 @@
/*
* MP3H.h
* Functions for decoding MP3H radiosonde
* Copyright (C) 2021 Hansi Reiser, dl9rdz
*
* SPDX-License-Identifier: GPL-2.0+
*/
#ifndef MP3H_h
#define MP3H_h
#include <stdlib.h>
#include <stdint.h>
#include <Arduino.h>
#ifndef inttypes_h
#include <inttypes.h>
#endif
#include "DecoderBase.h"
/* Main class */
class MP3H : public DecoderBase
{
private:
void printRaw(uint8_t *data, int len);
void processMP3Hdata(uint8_t data);
int decodeframeMP3H(uint8_t *data);
public:
MP3H();
int setup(float frequency, int type = 0);
int receive();
int waitRXcomplete();
};
extern MP3H mp3h;
#endif

934
RX_FSK/src/RS41.cpp Normal file
View File

@ -0,0 +1,934 @@
/* RS41 decoder functions */
#include "RS41.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#define RS41_DEBUG 1
#if RS41_DEBUG
#define RS41_DBG(x) x
#else
#define RS41_DBG(x)
#endif
#define RS41MAXLEN (320)
static byte data[800];
static int dpos = 0;
// whole 51 row frame as C structure
// taken from https://github.com/einergehtnochrein/ra-firmware
struct subframeBuffer {
uint64_t valid; // bitmask for subframe valid; lsb=frame 0, etc.
union {
byte rawData[51*16];
struct __attribute__((__packed__)) {
uint16_t crc16; /* CRC16 CCITT Checksum over range 0x002...0x31F */
uint16_t frequency; /* 0x002: TX is on 400 MHz + (frequency / 64) * 10 kHz */
uint8_t startupTxPower; /* 0x004: TX power level at startup (1...7) */
uint8_t reserved005;
uint8_t reserved006;
uint16_t reserved007; /* 0x007: ?? (some bitfield) [0],[1],[2],[3]. Init value = 0xE */
uint16_t reserved009; /* 0x009: ? */
uint8_t reserved00B;
uint8_t reserved00C;
uint8_t serial[8]; /* 0x00D: Sonde ID, 8 char, not terminated */
uint16_t firmwareVersion; /* 0x015: 10000*major + 100*minor + patch*/
uint16_t reserved017;
uint16_t minHeight4Flight; /* 0x019: Height (meter above ground) where flight mode begins */
uint8_t lowBatteryThreshold100mV; /* 0x01B: (Default=18) Shutdown if battery voltage below this
threshold for some time (10s ?)
*/
uint8_t nfcDetectorThreshold; /* 0x01C: NFC detector threshold [25mV] (Default: 0x05 = 125mV) */
uint8_t reserved01D; /* 0x01D: ?? (Init value = 0xB4) */
uint8_t reserved01E; /* 0x01E: ?? (Init value = 0x3C) */
uint16_t reserved01F;
int8_t refTemperatureThreshold; /* 0x021: Reference temperature threshold [°C] */
uint8_t reserved022;
uint16_t reserved023;
uint16_t reserved025;
int16_t flightKillFrames; /* 0x027: Number of frames in flight until kill (-1 = disabled) */
uint16_t reserved029; /* 0x029: ? (Init value = 0) */
uint8_t burstKill; /* 0x02B: Burst kill (0=disabled, 1=enabled) */
uint8_t reserved02C;
uint8_t reserved02D;
uint16_t reserved02E;
uint16_t reserved030;
uint8_t reserved032;
uint16_t reserved033;
uint16_t reserved035;
uint16_t reserved037;
uint16_t reserved039; /* 0x039: */
uint8_t reserved03B; /* 0x03B: */
uint8_t reserved03C; /* 0x03C: */
float refResistorLow; /* 0x03D: Reference resistor low (750 Ohms) */
float refResistorHigh; /* 0x041: Reference resistor high (1100 Ohms) */
float refCapLow; /* 0x045: Reference capacitance low (0) */
float refCapHigh; /* 0x049: Reference capacitance high (47 pF) */
float taylorT[3]; /* 0x04D: Tayor coefficients for main temperature calculation */
float calT; /* 0x059: Calibration factor for main sensor */
float polyT[6]; /* 0x05D: */
float calibU[2]; /* 0x075: Calibration coefficients for humidity sensor */
float matrixU[7][6]; /* 0x07D: Matrix for humidity sensor RH calculation */
float taylorTU[3]; /* 0x125: Coefficients for U sensor temperature calculation */
float calTU; /* 0x131: Calibration factor for U temperature sensor */
float polyTrh[6]; /* 0x135: */
uint8_t reserved14D; /* 0x14D: */
uint32_t reserved14E; /* 0x14E: */
float f152;
uint8_t u156;
float f157; /* 0x157: ?? (Initialized by same value as calibU[0]) */
uint8_t reserved15B; /* 0x15B: */
uint32_t reserved15C; /* 0x15C: */
float f160[35];
uint8_t startIWDG; /* 0x1EC: If ==1 or ==2: Watchdog IWDG will not be started */
uint8_t parameterSetupDone; /* 0x1ED: Set (!=0) if parameter setup was done */
uint8_t enableTestMode; /* 0x1EE: Test mode (service menu) (0=disabled, 1=enabled) */
uint8_t enableTX; /* 0x1EF: 0=TX disabled, 1=TX enabled (maybe this is autostart?) */
float f1F0[8];
float pressureLaunchSite[2]; /* 0x210: Pressure [hPa] at launch site */
struct __attribute__((__packed__)){
char variant[10]; /* 0x218: Sonde variant (e.g. "RS41-SG") */
uint8_t mainboard[10]; /* 0x222: Name of mainboard (e.g. "RSM412") */
} names;
struct __attribute__((__packed__)){
uint8_t mainboard[9]; /* 0x22C: Serial number of mainboard (e.g. "L1123553") */
uint8_t text235[12]; /* 0x235: "0000000000" */
uint16_t reserved241; /* 0x241: */
uint8_t pressureSensor[8]; /* 0x243: Serial number of pressure sensor (e.g. "N1310487") */
uint16_t reserved24B; /* 0x24B: */
} serials;
uint16_t reserved24D; /* 0x24D: */
uint16_t reserved24F; /* 0x24F: */
uint16_t reserved251; /* 0x251: (Init value = 0x21A = 538) */
uint8_t xdataUartBaud; /* 0x253: 1=9k6, 2=19k2, 3=38k4, 4=57k6, 5=115k2 */
uint8_t reserved254;
float cpuTempSensorVoltageAt25deg; /* 0x255: CPU temperature sensor voltage at 25°C */
uint8_t reserved259;
uint8_t reserved25A[0x25E -0x25A];
float matrixP[18]; /* 0x25E: Coefficients for pressure sensor polynomial */
float vectorBp[3]; /* 0x2A6: */
uint8_t reserved2B2[8]; /* 0x2B2: */
float matrixBt[12]; /* 0x2BA: */
uint8_t reserved2EA[0x2FA-0x2EA];
uint16_t halfword2FA[9];
float reserved30C;
float reserved310; /* 0x310: */
uint8_t reserved314; /* 0x314: */
uint8_t reserved315; /* 0x315: */
int16_t burstKillFrames; /* 0x316: Number of active frames after burst kill */
uint8_t reserved318[0x320-0x318];
/* This is fragment 50. It only uses 14 valid bytes! */
int16_t killCountdown; /* 0x320: Counts frames remaining until kill (-1 = inactive) */
uint8_t reserved322[6];
int8_t intTemperatureCpu; /* 0x328: Temperature [°C] of CPU */
int8_t intTemperatureRadio; /* 0x329: Temperature [°C] of radio chip */
int8_t reserved32A; /* 0x32A: */
uint8_t reserved32B; /* 0x32B: */
uint8_t reserved32C; /* 0x32C: ? (the sum of two slow 8-bit counters) */
uint8_t reserved32D; /* 0x32D: ? (the sum of two slow 8-bit counters) */
} value;
};
};
// moved global variable "calibration" to sondeInfo->extra
static uint16_t CRCTAB[256];
#define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a))
#define X2C_DIVL(a, b) ((a)/(b))
static uint32_t X2C_LSH(uint32_t a, int32_t length, int32_t n)
{
uint32_t m;
m = 0;
m = (length == 32) ? 0xFFFFFFFFl : (1 << length) - 1;
if (n > 0) {
if (n >= (int32_t)length)
return 0;
return (a << n) & m;
}
if (n <= (int32_t)-length)
return 0;
return (a >> -n) & m;
}
double atang2(double x, double y)
{
double w;
if (fabs(x)>fabs(y)) {
w = (double)atan(X2C_DIVL(y,x));
if (x<0.0) {
if (y>0.0) w = 3.1415926535898+w;
else w = w-3.1415926535898;
}
}
else if (y!=0.0) {
w = (double)(1.5707963267949f-atan(X2C_DIVL(x, y)));
if (y<0.0) w = w-3.1415926535898;
}
else w = 0.0;
return w;
} /* end atang2() */
static void Gencrctab(void)
{
uint16_t j;
uint16_t i;
uint16_t crc;
for (i = 0U; i<=255U; i++) {
crc = (uint16_t)(i*256U);
for (j = 0U; j<=7U; j++) {
if ((0x8000U & crc)) crc = X2C_LSH(crc,16,1)^0x1021U;
else crc = X2C_LSH(crc,16,1);
} /* end for */
CRCTAB[i] = X2C_LSH(crc,16,-8)|X2C_LSH(crc,16,8);
} /* end for */
} /* end Gencrctab() */
decoderSetupCfg rs41SetupCfg = {
.bitrate = 4800,
.rx_cfg = 0x1E, // Enable auto-AFC, auto-AGC, RX Trigger by preamble
.sync_cfg = 0x57, // Set autostart_RX to 01, preamble 0, SYNC detect==on, syncsize=3 (==4 byte
.sync_len = 8,
.sync_data = (const uint8_t *)"\x08\x6D\x53\x88\x44\x69\x48\x1F",
.preamble_cfg = 0xA8,
};
int RS41::setup(float frequency, int /*type*/)
{
if(!initialized) {
Gencrctab();
initrsc();
initialized = true;
}
if(sx1278.ON()!=0) {
RS41_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(DecoderBase::setup(rs41SetupCfg, sonde.config.rs41.agcbw, sonde.config.rs41.rxbw)!=0 ) {
return 1;
}
#if 0
// all moved to DecoderBase now
if(sx1278.setFSK()!=0) {
RS41_DBG(Serial.println("Setting FSK mode FAILED"));
return 1;
}
if(sx1278.setBitrate(4800)!=0) {
RS41_DBG(Serial.println("Setting bitrate 4800bit/s FAILED"));
return 1;
}
if(sx1278.setAFCBandwidth(sonde.config.rs41.agcbw)!=0) {
RS41_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs41.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs41.rxbw)!=0) {
RS41_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs41.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
RS41_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="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
if(sx1278.setSyncConf(0x57, 8, (const uint8_t *)SYNC)!=0) {
RS41_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
RS41_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
RS41_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
int retval = sx1278.setFrequency(frequency);
dpos = 0;
sx1278.clearIRQFlags();
return retval;
}
uint32_t RS41::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;
}
RS41::RS41() {
}
/* RS41 reed solomon decoder, from dxlAPRS
*/
static int32_t reedsolomon41(byte buf[], uint32_t buf_len, uint32_t len2)
{
uint32_t i;
int32_t res1;
/*tb1, */
int32_t res;
char b1[256];
char b[256];
uint32_t eraspos[24];
uint32_t tmp;
for (i = 0UL; i<=255UL; i++) {
b[i] = 0;
b1[i] = 0;
} /* end for */
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
b[230UL-i] = buf[i*2UL+56UL];
b1[230UL-i] = buf[i*2UL+57UL];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
b[254UL-i] = buf[i+8UL];
b1[254UL-i] = buf[i+32UL];
} /* end for */
res = decodersc(b, eraspos, 0);
res1 = decodersc(b1, eraspos, 0);
if (res>0L && res<=12L) {
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
buf[i*2UL+56UL] = b[230UL-i];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+8UL] = b[254UL-i];
} /* end for */
}
if (res1>0L && res1<=12L) {
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
buf[i*2UL+57UL] = b1[230UL-i];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+32UL] = b1[254UL-i];
} /* end for */
}
if (res<0L || res1<0L) return -1L;
else return res+res1;
return 0;
} /* end reedsolomon41() */
static char crcrs(const byte frame[], uint32_t frame_len,
int32_t from, int32_t to)
{
uint16_t crc;
int32_t i;
int32_t tmp;
crc = 0xFFFFU;
tmp = to-3L;
i = from;
if (i<=tmp) for (;; i++) {
crc = X2C_LSH(crc,16,-8)^CRCTAB[(uint32_t)((crc^(uint16_t)(uint8_t)frame[i])&0xFFU)];
if (i==tmp) break;
} /* end for */
return frame[to-1L]==(char)crc && frame[to-2L]==(char)X2C_LSH(crc,
16,-8);
} /* end crcrs() */
static int32_t getint32(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
uint32_t i;
n = 0UL;
for (i = 3UL;; i--) {
n = n*256UL+(uint32_t)(uint8_t)frame[p+i];
if (i==0UL) break;
} /* end for */
return (int32_t)n;
} /* end getint32() */
static uint32_t getint24(const byte frame[], uint32_t frame_len, uint32_t p) { // 24bit unsigned int
uint32_t val24 = 0;
val24 = frame[p] | (frame[p+1]<<8) | (frame[p+2]<<16);
return val24;
}
static uint32_t getcard16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
return (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
} /* end getcard16() */
static int32_t getint16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
n = (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
if (n>=32768UL) return (int32_t)(n-65536UL);
return (int32_t)n;
} /* end getint16() */
// also used by MP3H.cpp
void wgs84r(double x, double y, double z,
double * lat, double * long0,
double * heig)
{
double sl;
double ct;
double st;
double t;
double rh;
double xh;
double h;
h = x*x+y*y;
if (h>0.0) {
rh = sqrt(h);
xh = x+rh;
*long0 = atang2(xh, y)*2.0;
if (*long0>3.1415926535898) *long0 = *long0-6.2831853071796;
t = atan(X2C_DIVL(z*1.003364089821, rh));
st = sin(t);
ct = cos(t);
*lat = atan((X2C_DIVL(z+4.2841311513312E+4*st*st*st,
rh-4.269767270718E+4*ct*ct*ct)));
sl = sin(*lat);
*heig = X2C_DIVL(rh,cos(*lat))-(X2C_DIVR(6.378137E+6f,
sqrt((1.0-6.6943799901413E-3*sl*sl))));
}
else {
*lat = 0.0;
*long0 = 0.0;
*heig = 0.0;
}
/* lat:=atan(z/(rh*(1.0 - E2))); */
/* heig:=sqrt(h + z*z) - EARTHA; */
} /* end wgs84r() */
// returns: 0=ok, -1=error
static void posrs41(const byte b[], uint32_t b_len, uint32_t p)
{
double dir;
double vu;
double ve;
double vn;
double vz;
double vy;
double vx;
double heig;
double long0;
double lat;
double z;
double y;
double x;
SondeData *si = &(sonde.si()->d);
x = (double)getint32(b, b_len, p)*0.01;
y = (double)getint32(b, b_len, p+4UL)*0.01;
z = (double)getint32(b, b_len, p+8UL)*0.01;
uint8_t sats = getcard16(b, b_len, p+18UL)&255UL;
Serial.printf("x:%g, y:%g, z:%g sats:%d\n", x, y, z, sats);
if( sats<4 || (x==0 && y==0 && z==0) ) {
// RS41 sometimes sends frame with all 0
// or, if sats<4, data is simply garbage. do not use.
if(si->validPos) si->validPos |= 0x80; // flag as old
return;
}
si->sats = sats;
wgs84r(x, y, z, &lat, &long0, &heig);
Serial.print(" ");
si->lat = (float)(X2C_DIVL(lat,1.7453292519943E-2));
Serial.print(si->lat);
Serial.print(" ");
si->lon = (float)(X2C_DIVL(long0,1.7453292519943E-2));
Serial.print(si->lon);
if (heig<1.E+5 && heig>(-1.E+5)) {
Serial.print(" ");
Serial.print((uint32_t)heig);
Serial.print("m");
}
/*speed */
vx = (double)getint16(b, b_len, p+12UL)*0.01;
vy = (double)getint16(b, b_len, p+14UL)*0.01;
vz = (double)getint16(b, b_len, p+16UL)*0.01;
vn = (-(vx*sin(lat)*cos(long0))-vy*sin(lat)*sin(long0))+vz*cos(lat);
ve = -(vx*sin(long0))+vy*cos(long0);
vu = vx*cos(lat)*cos(long0)+vy*cos(lat)*sin(long0)+vz*sin(lat);
dir = X2C_DIVL(atang2(vn, ve),1.7453292519943E-2);
if (dir<0.0) dir = 360.0+dir;
si->dir = dir;
Serial.print(" ");
si->hs = sqrt(vn*vn+ve*ve);
Serial.print(si->hs*3.6);
Serial.print("km/h ");
Serial.print(dir);
Serial.print("deg ");
Serial.print((float)vu);
si->vs = vu;
Serial.print("m/s ");
si->alt = heig;
if( 0==(int)(lat*10000) && 0==(int)(long0*10000) ) {
if(si->validPos) {
// we have an old position, so keep previous position and mark it as old
si->validPos |= 0x80;
}
}
else
si->validPos = 0x7f;
} /* end posrs41() */
void ProcessSubframe( byte *subframeBytes, int subframeNumber ) {
// the total subframe consists of 51 rows, each row 16 bytes
// based on https://github.com/bazjo/RS41_Decoding/tree/master/RS41-SGP#Subframe
struct subframeBuffer *s = (struct subframeBuffer *)sonde.si()->extra;
// Allocate on demand
if(!s) {
s = (struct subframeBuffer *)malloc( sizeof(struct subframeBuffer) );
if(!s) { Serial.println("ProcessSubframe: out of memory"); return; }
sonde.si()->extra = s;
s->valid = 0;
}
memcpy( s->rawData+16*subframeNumber, subframeBytes, 16);
s->valid |= (1ULL << subframeNumber);
Serial.printf("subframe %d; valid: %x%08x\n", subframeNumber, (uint32_t)(s->valid>>32), (uint32_t)s->valid);
for(int i=0; i<16; i++) { Serial.printf("%02x[%c]", subframeBytes[i],( subframeBytes[i]>20 && subframeBytes[i]<127)? subframeBytes[i] : '.'); }
Serial.println("");
// subframeReceived[subframeNumber] = true; // mark this row of the total subframe as complete
#if 0
Serial.printf("subframe number: 0x%02X\n", subframeNumber );
Serial.print("subframe values: ");
for ( int i = 0; i < 16; i++ ) {
Serial.printf( "%02X ", subframeBytes[i] );
}
Serial.println();
Serial.println("Full subframe");
for ( int j = 0; j<51; j++ ) {
Serial.printf("%03X ", j*16);
for ( int i = 0; i < 16; i++ ) {
Serial.printf( "%02X ", s.rawData[j*16+i] );
}
Serial.println();
}
Serial.println();
#endif
}
/* Find the water vapor saturation pressure for a given temperature.
* Uses the Hyland and Wexler equation with coefficients for T < 0°C.
*/
// taken from https://github.com/einergehtnochrein/ra-firmware
static float _RS41_waterVaporSaturationPressure (float Tcelsius)
{
/* Convert to Kelvin */
float T = Tcelsius + 273.15f;
/* Apply some correction magic */
T = 0
- 0.4931358f
+ (1.0f + 4.61e-3f) * T
- 1.3746454e-5f * T * T
+ 1.2743214e-8f * T * T * T
;
/* Plug into H+W equation */
float p = expf(-5800.2206f / T
+ 1.3914993f
+ 6.5459673f * logf(T)
- 4.8640239e-2f * T
+ 4.1764768e-5f * T * T
- 1.4452093e-8f * T * T * T
);
/* Scale result to hPa */
return p / 100.0f;
}
#define PM(x) calibration->value.matrixP[x]
// CALIB_P: matrixP (frames 0x25..0x2A) and type (frame 0x21)
#define CALIB_P ((0x3Fll<<0x25)|(1ll<<0x21))
float GetRAP( uint32_t m, uint32_t m1, uint32_t m2, int16_t ptraw) {
struct subframeBuffer *calibration = (struct subframeBuffer *)sonde.si()->extra;
float pt = (float)ptraw*0.01;
float pw[6];
pw[0] = PM(0) + pt*PM(7) + pt*pt*PM(11) + pt*pt*pt*PM(15);
pw[1] = PM(1) + pt*PM(8) + pt*pt*PM(12) + pt*pt*pt*PM(16);
pw[2] = PM(2) + pt*PM(9) + pt*pt*PM(13) + pt*pt*pt*PM(17);
pw[3] = PM(3) + pt*PM(10)+ pt*pt*PM(14);
pw[4] = PM(4);
pw[5] = PM(5);
float f = (float)m; //meas[9];
float f1 = (float)m1; //meas[10];
float f2 = (float)m2; //meas[11];
float r = f-f1;
if(r!=0.0) {
r = (f2-f1) * PM(6) / r;
float xx = 1.0;
float p = 0.0;
for(int i=0; i<=5; i++) {
p += pw[i] * xx;
xx = xx * r;
}
return p;
}
return NAN;
}
// taken from https://github.com/einergehtnochrein/ra-firmware
float GetRATemp( uint32_t measuredCurrent, uint32_t refMin, uint32_t refMax, float calT, float taylorT[3], float polyT[6] ) {
struct subframeBuffer *calibration = (struct subframeBuffer *)sonde.si()->extra;
/* Reference values for temperature are two known resistors.
* From that we can derive the resistance of the sensor.
*/
float current = ( float(measuredCurrent) - float(refMin) ) / float(refMax - refMin);
float res = calibration->value.refResistorLow
+ (calibration->value.refResistorHigh - calibration->value.refResistorLow) * current;
float x = res * calT;
float Tuncal = 0
+ taylorT[0]
+ taylorT[1] * x
+ taylorT[2] * x * x;
/* Apply calibration polynomial */
float temperature =
Tuncal + polyT[0]
+ polyT[1] * Tuncal
+ polyT[2] * Tuncal * Tuncal
+ polyT[3] * Tuncal * Tuncal * Tuncal
+ polyT[4] * Tuncal * Tuncal * Tuncal * Tuncal
+ polyT[5] * Tuncal * Tuncal * Tuncal * Tuncal * Tuncal;
return temperature;
}
// taken from https://github.com/einergehtnochrein/ra-firmware
float GetRAHumidity( uint32_t humCurrent, uint32_t humMin, uint32_t humMax, float sensorTemp, float externalTemp, float pressure ) {
struct subframeBuffer *calibration = (struct subframeBuffer *)sonde.si()->extra;
float current = float( humCurrent - humMin) / float( humMax - humMin );
/* Compute absolute capacitance from the known references */
float C = calibration->value.refCapLow
+ (calibration->value.refCapHigh - calibration->value.refCapLow) * current;
/* Apply calibration */
float Cp = ( C / calibration->value.calibU[0] - 1.0f) * calibration->value.calibU[1];
/* Compensation for low temperature and pressure at altitude */
if(isnan(pressure)) {
// if no pressure is available (non-SGP), estimate based on altitude
pressure = 1013.25f * expf(-1.18575919e-4f * sonde.si()->d.alt );
}
float Tp = (sensorTemp - 20.0f) / 180.0f;
float sum = 0;
float powc = 1.0f;
float p = pressure / 1000.0f;
for ( int i = 0; i < 3; i++) {
float l = 0;
float powt = 1.0f;
for ( int j = 0; j < 4; j++) {
l += calibration->value.matrixBt[4*i+j] * powt;
powt *= Tp;
}
float x = calibration->value.vectorBp[i];
sum += l * (x * p / (1.0f + x * p) - x * powc / (1.0f + x));
powc *= Cp;
}
Cp -= sum;
float xj = 1.0f;
for ( int j = 0; j < 7; j++) {
float yk = 1.0f;
for ( int k = 0; k < 6; k++) {
sum += xj * yk * calibration->value.matrixU[j][k];
yk *= Tp;
}
xj *= Cp;
}
/* Since there is always a small difference between the temperature readings for
* the atmospheric (main) tempoerature sensor and the temperature sensor inside
* the humidity sensor device, transform the humidity value to the atmospheric conditions
* with its different water vapor saturation pressure.
*/
float RH = sum
* _RS41_waterVaporSaturationPressure(sensorTemp)
/ _RS41_waterVaporSaturationPressure(externalTemp);
return RH;
}
// returns: 0: ok, -1: rs or crc error
int RS41::decode41(byte *data, int maxlen)
{
char buf[128];
int crcok = 1;
SondeData *si = &(sonde.si()->d);
int32_t corr = reedsolomon41(data, 560, 131); // try short frame first
if(corr<0) {
corr = reedsolomon41(data, 560, 230); // try long frame
}
#if 0
Serial.print("RS result:");
Serial.print(corr);
Serial.println();
#endif
int p = 57; // 8 byte header, 48 byte RS
while(p<maxlen) { /* why 555? */
uint8_t typ = data[p++];
uint32_t len = data[p++]+2UL;
if(p+len>maxlen) break;
#if 0
// DEBUG OUTPUT
Serial.print("@");
Serial.print(p-2);
Serial.print(": ID:");
Serial.print(typ, HEX);
Serial.print(", len=");
Serial.print(len);
Serial.print(": ");
for(int i=0; i<len-1; i++) {
char buf[3];
snprintf(buf, 4, "%02X|", data[p+i]);
Serial.print(buf);
}
#endif
// check CRC
if(!crcrs(data, 560, p, p+len)) {
Serial.println("###CRC ERROR###");
crcok = 0;
} else {
switch(typ) {
case 'y': // name
{
if(strncmp(si->id, (const char *)(data+p+2), 8)) {
// ID changed, i.e. new sonde on same frequency. clear calibration and all other data
sonde.clearAllData(sonde.si());
struct subframeBuffer *sub = (struct subframeBuffer *)sonde.si()->extra;
if(sub) { sub->valid = 0; }
}
Serial.print("#");
uint16_t fnr = data[p]+(data[p+1]<<8);
Serial.print(fnr);
si->vframe = si->frame = fnr;
Serial.print("; RS41 ID ");
snprintf(buf, 10, "%.8s ", data+p+2);
Serial.print(buf);
si->batteryVoltage = data[p+10] / 10.0f;
// not needed, if we end up here, the type has to be RS41.... si->type=STYPE_RS41;
strncpy(si->id, (const char *)(data+p+2), 8);
si->id[8]=0;
strncpy(si->ser, (const char *)(data+p+2), 8);
si->ser[8]=0;
si->validID=true;
int calnr = data[p+23];
// not sure about this
if(calnr==0x31) {
uint16_t bt = data[p+30] + 256*data[p+31];
si->burstKT = bt;
}
// this should be right...
if(calnr==0x02) {
uint16_t kt = data[p+31] + 256*data[p+32];
si->launchKT = kt;
}
// and this seems fine as well...
if(calnr==0x32) {
uint16_t cntdown = data[p+24] + (data[p+25]<<8);
uint16_t min = cntdown - (cntdown/3600)*3600;
Serial.printf("Countdown value: %d\n [%2d:%02d:%02d]", cntdown, cntdown/3600, min/60, min-(min/60)*60);
si->countKT = cntdown;
si->crefKT = fnr;
}
ProcessSubframe( data+p+24, calnr );
}
// TODO: some more data
break;
case '|': // date
{
uint32_t gpstime = getint32(data, 560, p+2);
uint16_t gpsweek = getint16(data, 560, p);
// 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
si->time = (gpstime/1000) + 86382 + gpsweek*604800 + 315878400UL;
si->validTime = true;
}
break;
case '{': // pos
posrs41(data+p, len, 0);
break;
case 'z': // 0x7a is character z - 7A-MEAS temperature and humidity frame
{
uint32_t tempMeasMain = getint24(data, 560, p+0);
uint32_t tempMeasRef1 = getint24(data, 560, p+3);
uint32_t tempMeasRef2 = getint24(data, 560, p+6);
uint32_t humidityMain = getint24(data, 560, p+9);
uint32_t humidityRef1 = getint24(data, 560, p+12);
uint32_t humidityRef2 = getint24(data, 560, p+15);
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);
#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 );
#endif
struct subframeBuffer *calibration = (struct subframeBuffer *)(sonde.si()->extra);
// temp: 0xF8==bits 3..7 : we need refResistorlow/high, taylorT, polyT
bool validExternalTemperature = calibration!=NULL && (calibration->valid & 0xF8) == 0xF8;
// humidity: bits 3..20 and 37..46. and bit 33 (variant)
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';
if ( validPressure ) {
si->pressure = GetRAP( pressureMain, pressureRef1, pressureRef2, ptraw );
Serial.printf("Pressure sensor = %f\n", si->pressure);
}
if ( validExternalTemperature ) {
si->temperature = GetRATemp( tempMeasMain, tempMeasRef1, tempMeasRef2,
calibration->value.calT, calibration->value.taylorT, calibration->value.polyT );
Serial.printf("External temperature = %f\n", si->temperature );
}
if ( validHumidity && validExternalTemperature ) {
si->tempRHSensor = GetRATemp( tempHumiMain, tempHumiRef1, tempHumiRef2,
calibration->value.calTU, calibration->value.taylorTU, calibration->value.polyTrh );
Serial.printf("Humidity Sensor temperature = %f\n", si->tempRHSensor );
si->relativeHumidity = GetRAHumidity( humidityMain, humidityRef1, humidityRef2, si->tempRHSensor, si->temperature, si->pressure );
Serial.printf("Relative humidity = %f\n", si->relativeHumidity );
}
}
break;
default:
break;
}}
p += len;
Serial.println();
}
return crcok ? 0 : RX_ERROR;
}
void RS41::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X", data[i]);
Serial.print(buf);
}
Serial.println();
}
void RS41::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
static uint8_t reverse(uint8_t n) {
return (lookup[n&0x0f] << 4) | lookup[n>>4];
}
static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
14U,249U,68U,198U,38U,33U,96U,194U,234U,121U,93U,109U,161U,
84U,105U,71U,12U,220U,232U,92U,241U,247U,118U,130U,127U,7U,
153U,162U,44U,147U,124U,48U,99U,245U,16U,46U,97U,208U,188U,
180U,182U,6U,170U,244U,35U,120U,110U,59U,174U,191U,123U,76U,
193U};
int RS41::receive() {
sx1278.setPayloadLength(RS41MAXLEN-8);
int e = sx1278.receivePacketTimeout(1000, data+8);
#if 1
if(e) { /*Serial.println("TIMEOUT");*/ return RX_TIMEOUT; }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = reverse(data[i]); }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
return decode41(data, RS41MAXLEN);
#else
// FAKE testing data
SondeInfo *si = sonde.si();
si->lat = 48;
si->lon = -100;
si->alt = 30000;
si->vs = 3.4;
si->validPos = 0x7f;
si->validID = 1;
strcpy(si->id, "A1234");
return 0;
#endif
}
int RS41::waitRXcomplete() {
// Currently not used. can be used for additinoal post-processing
// (required for RS92 to avoid FIFO overrun in rx task)
return 0;
}
// copy variant string to buf (max buflen chars; buflen should be 11
// return 0 if subtype is available, -1 if not
int RS41::getSubtype(char *buf, int buflen, SondeInfo *si) {
struct subframeBuffer *sf = (struct subframeBuffer *)si->extra;
if(!sf) return -1;
if( ( (sf->valid>>0x21) &3) != 3 ) return -1; // or 1 instead of 3 for the first 8 chars only, as in autorx?
if(buflen>11) buflen=11; // then buflen should be capped at 9 (8+trailing \0)
strncpy(buf, sf->value.names.variant, buflen);
buf[buflen-1]=0;
if(*buf==0) return -1;
Serial.printf("subframe valid: %x%08x; subtype=%s\n", (uint32_t)(sf->valid>>32), (uint32_t)sf->valid, buf);
return 0;
}
RS41 rs41 = RS41();

10
libraries/SondeLib/RS41.h → RX_FSK/src/RS41.h Executable file → Normal file
View File

@ -15,9 +15,11 @@
#ifndef inttypes_h
#include <inttypes.h>
#endif
#include "Sonde.h"
#include "DecoderBase.h"
/* Main class */
class RS41
class RS41 : public DecoderBase
{
private:
uint32_t bits2val(const uint8_t *bits, int len);
@ -25,6 +27,7 @@ private:
void bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
int decode41(byte *data, int maxlen);
#if 0
#define B 8
#define S 4
uint8_t hamming_conf[ 7*B]; // 7*8=56
@ -41,6 +44,7 @@ private:
{ 1, 1, 0, 1, 0, 0, 1, 0},
{ 1, 1, 1, 0, 0, 0, 0, 1}};
uint8_t He[8] = { 0x7, 0xB, 0xD, 0xE, 0x8, 0x4, 0x2, 0x1}; // Spalten von H:
#endif
// 1-bit-error-Syndrome
boolean initialized = false;
@ -48,7 +52,7 @@ public:
RS41();
// New interface:
// setup() is called when channel is activated (sets mode and frequency and activates receiver)
int setup(float frequency);
int setup(float frequency, int type = 0);
// processRXbyte is called by background task for each received byte
// should be fast enough to not cause sx127x fifo buffer overflow
// void processRXbyte(uint8_t data);
@ -59,6 +63,8 @@ public:
int waitRXcomplete();
//int receiveFrame();
static int getSubtype(char *buf, int buflen, SondeInfo *si);
int use_ecc = 1;
};

172
libraries/SondeLib/RS92.cpp → RX_FSK/src/RS92.cpp Executable file → Normal file
View File

@ -17,7 +17,6 @@
#define RS92_DBG(x)
#endif
//static uint16_t CRCTAB[256];
uint16_t *CRCTAB = NULL;
#define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a))
@ -39,27 +38,6 @@ static uint32_t X2C_LSH(uint32_t a, int32_t length, int32_t n)
return (a >> -n) & m;
}
#if 0
static double atang2(double x, double y)
{
double w;
if (fabs(x)>fabs(y)) {
w = (double)atan((float)(X2C_DIVL(y,x)));
if (x<0.0) {
if (y>0.0) w = 3.1415926535898+w;
else w = w-3.1415926535898;
}
}
else if (y!=0.0) {
w = (double)(1.5707963267949f-atan((float)(X2C_DIVL(x,
y))));
if (y<0.0) w = w-3.1415926535898;
}
else w = 0.0;
return w;
} /* end atang2() */
#endif
static void Gencrctab(void)
{
uint16_t j;
@ -90,7 +68,16 @@ static int haveNewFrame = 0;
static int lastFrame = 0;
static int headerDetected = 0;
int RS92::setup(float frequency)
decoderSetupCfg rs92SetupCfg = {
.bitrate = 4800,
.rx_cfg = 0x1E,
.sync_cfg = 0x70,
.sync_len = 2,
.sync_data = (const uint8_t *)"\x66\x65",
.preamble_cfg = 0xA8,
};
int RS92::setup(float frequency, int /*type*/)
{
#if RS92_DEBUG
Serial.println("Setup sx1278 for RS92 sonde");
@ -106,6 +93,10 @@ int RS92::setup(float frequency)
RS92_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(DecoderBase::setup(rs92SetupCfg, sonde.config.rs92.rxbw, sonde.config.rs92.rxbw)!=0) {
return 1;
}
#if 0
if(sx1278.setFSK()!=0) {
RS92_DBG(Serial.println("Setting FSJ mode FAILED"));
return 1;
@ -140,7 +131,6 @@ int RS92::setup(float frequency)
//const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
// was 0x57
//const char *SYNC="\x99\x9A";
#if 1
// version 1, working with continuous RX
const char *SYNC="\x66\x65";
if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
@ -152,6 +142,7 @@ int RS92::setup(float frequency)
return 1;
}
#endif
#if 0
// version 2, with per-packet rx start, untested
// header is 2a 10 65, i.e. with lsb first
@ -196,19 +187,6 @@ int RS92::setup(float frequency)
return res;
}
#if 0
int RS92::setFrequency(float frequency) {
Serial.print("RS92: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return res;
}
#endif
uint32_t RS92::bits2val(const uint8_t *bits, int len) {
uint32_t val = 0;
for (int j = 0; j < len; j++) {
@ -448,34 +426,6 @@ void RS92::printRaw(uint8_t *data, int len)
Serial.println();
}
#if 0
// I guess this is old copy&paste stuff from RS41??
int RS92::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
static uint8_t reverse(uint8_t n) {
return (lookup[n&0x0f] << 4) | lookup[n>>4];
}
static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
14U,249U,68U,198U,38U,33U,96U,194U,234U,121U,93U,109U,161U,
84U,105U,71U,12U,220U,232U,92U,241U,247U,118U,130U,127U,7U,
153U,162U,44U,147U,124U,48U,99U,245U,16U,46U,97U,208U,188U,
180U,182U,6U,170U,244U,35U,120U,110U,59U,174U,191U,123U,76U,
193U};
#endif
void RS92::stobyte92(uint8_t b)
{
@ -615,7 +565,7 @@ int RS92::waitRXcomplete() {
Serial.printf("decoding frame %d\n", lastFrame);
print_frame(lastFrame==1?data1:data2, 240);
SondeInfo *si = sonde.sondeList+rxtask.receiveSonde;
SondeData *si = &( (sonde.sondeList+rxtask.receiveSonde)->d );
si->lat = gpx.lat;
si->lon = gpx.lon;
si->alt = gpx.alt;
@ -626,102 +576,14 @@ int RS92::waitRXcomplete() {
memcpy(si->id, gpx.id, 9);
memcpy(si->ser, gpx.id, 9);
si->validID = true;
si->frame = gpx.frnr;
si->vframe = si->frame = gpx.frnr;
si->sats = gpx.k;
si->time = (gpx.gpssec/1000) + 86382 + gpx.week*604800 + 315878400UL;
si->validTime = true;
#if 0
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult == 0xFFFF && millis()-t0 < 2000) { delay(20); }
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 = 0xFFFF;
Serial.printf("RS92::waitRXcomplete returning %d (%s)\n", res, RXstr[res]);
return res;
#endif
return 0;
}
#if 0
int oldwaitRXcomplete() {
Serial.println("RS92: receive frame...\n");
sx1278receiveData = true;
delay(6000); // done in other task....
//sx1278receiveData = false;
#if 0
//sx1278.setPayloadLength(518-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
//sx1278.setPayloadLength(0); // infinite for now...
////// test code for continuous reception
// sx1278.receive(); /// active FSK RX mode -- already done above...
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
unsigned long previous = millis();
byte ready=0;
uint32_t wait = 8000;
// while not yet done or FIFO not yet empty
// bit 6: FIFO Empty
// bit 2 payload ready
int by=0;
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
{
if( bitRead(value, 7) ) { Serial.println("FIFO full"); }
if( bitRead(value, 4) ) { Serial.println("FIFO overflow"); }
if( bitRead(value,2)==1 ) ready=1;
if( bitRead(value, 6) == 0 ) { // FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
process8N1data(data);
by++;
#if 0
if(di==1) {
int rssi=getRSSI();
int fei=getFEI();
int afc=getAFC();
Serial.print("Test: RSSI="); Serial.println(rssi);
Serial.print("Test: FEI="); Serial.println(fei);
Serial.print("Test: AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
if(di>520) {
// TODO
Serial.println("TOO MUCH DATA");
break;
}
previous = millis(); // reset timeout after receiving data
#endif
}
value = sx1278.readRegister(REG_IRQ_FLAGS2);
}
Serial.printf("processed %d bytes before end/timeout\n", by);
#endif
/////
#if 0
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; } //if timeout... return 1
printRaw(data, RS92MAXLEN);
//for(int i=0; i<RS92MAXLEN; i++) { data[i] = reverse(data[i]); }
//printRaw(data, MAXLEN);
//for(int i=0; i<RS92MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
//printRaw(data, MAXLEN);
//int res = decode41(data, RS92MAXLEN);
#endif
int res=0;
return res==0 ? RX_OK : RX_ERROR;
}
#endif
RS92 rs92 = RS92();

10
libraries/SondeLib/RS92.h → RX_FSK/src/RS92.h Executable file → Normal file
View File

@ -15,6 +15,7 @@
#ifndef inttypes_h
#include <inttypes.h>
#endif
#include "DecoderBase.h"
struct CONTEXTR9 {
@ -49,17 +50,12 @@ struct CONTEXTR9 {
/* Main class */
class RS92
class RS92 : public DecoderBase
{
private:
void process8N1data(uint8_t data);
void stobyte92(uint8_t byte);
void decodeframe92(uint8_t *data);
#if 0
void dogps(const uint8_t *sf, int sf_len,
struct CONTEXTR9 * cont, uint32_t * timems,
uint32_t * gpstime);
#endif
uint32_t bits2val(const uint8_t *bits, int len);
void printRaw(uint8_t *data, int len);
int bitsToBytes(uint8_t *bits, uint8_t *bytes, int len);
@ -84,7 +80,7 @@ private:
public:
RS92();
int setup(float frequency);
int setup(float frequency, int type = 0);
int receive();
int waitRXcomplete();

View File

@ -11,16 +11,41 @@
#include "SX1278FSK.h"
#include "SPI.h"
#include <Sonde.h>
#include <Display.h>
#include "Sonde.h"
#include "Display.h"
SX1278FSK::SX1278FSK()
#define SPI_MUTEX_LOCK() \
do \
{ \
} while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS)
#define SPI_MUTEX_UNLOCK() xSemaphoreGive(_lock)
SX1278FSK::SX1278FSK() {}
void SX1278FSK::setup(xSemaphoreHandle lock)
{
// Initialize class variables
_lock = lock;
Serial.println("Setup sx1278");
if(_lock) SPI_MUTEX_LOCK();
digitalWrite(sonde.config.sx1278_ss, HIGH);
pinMode(sonde.config.sx1278_ss, OUTPUT);
Serial.printf("Configuing SX1278FSK SPI with miso=%d, mosi=%d, sck=%d, ss=%d\n", sonde.config.sx1278_miso,
sonde.config.sx1278_mosi, sonde.config.sx1278_sck, sonde.config.sx1278_ss);
SPI.begin(sonde.config.sx1278_sck, sonde.config.sx1278_miso, sonde.config.sx1278_mosi, -1); // no hardware CS
// was: SPI.begin();
//Set most significant bit first
SPI.setBitOrder(MSBFIRST);
//Divide the clock frequency
SPI.setClockDivider(SPI_CLOCK_DIV2);
//Set data mode
SPI.setDataMode(SPI_MODE0);
if(_lock) SPI_MUTEX_UNLOCK();
};
static SPISettings spiset = SPISettings(40000000L, MSBFIRST, SPI_MODE0);
static SPISettings spiset = SPISettings(10000000L, MSBFIRST, SPI_MODE0);
/*
Function: Turns the module ON.
@ -34,19 +59,6 @@ uint8_t SX1278FSK::ON()
Serial.println(F("Starting 'ON'"));
#endif
// Powering the module
pinMode(SX1278_SS, OUTPUT);
digitalWrite(SX1278_SS, HIGH);
//Configure the MISO, MOSI, CS, SPCR.
SPI.begin();
//Set most significant bit first
SPI.setBitOrder(MSBFIRST);
//Divide the clock frequency
SPI.setClockDivider(SPI_CLOCK_DIV2);
//Set data mode
SPI.setDataMode(SPI_MODE0);
// Set Maximum Over Current Protection
state = setMaxCurrent(0x1B);
if( state == 0 )
@ -60,7 +72,6 @@ uint8_t SX1278FSK::ON()
{
return 1;
}
// set FSK mode
state = setFSK();
return state;
@ -77,10 +88,12 @@ void SX1278FSK::OFF()
Serial.println(F("Starting 'OFF'"));
#endif
SPI.end();
//SPI.end();
#if 0
// Powering the module
pinMode(SX1278_SS,OUTPUT);
digitalWrite(SX1278_SS,LOW);
#endif
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## Setting OFF ##"));
@ -98,15 +111,16 @@ byte SX1278FSK::readRegister(byte address)
{
byte value = 0x00;
if(_lock) SPI_MUTEX_LOCK();
digitalWrite(sonde.config.sx1278_ss,LOW);
SPI.beginTransaction(spiset);
digitalWrite(SX1278_SS,LOW);
//delay(1);
bitClear(address, 7); // Bit 7 cleared to write in registers
SPI.transfer(address);
value = SPI.transfer(0x00);
digitalWrite(SX1278_SS,HIGH);
SPI.endTransaction();
digitalWrite(sonde.config.sx1278_ss,HIGH);
#if (SX1278FSK_debug_mode > 1)
if(address!=0x3F) {
@ -118,7 +132,7 @@ byte SX1278FSK::readRegister(byte address)
Serial.println();
}
#endif
if(_lock) SPI_MUTEX_UNLOCK();
return value;
}
@ -131,15 +145,16 @@ Parameters:
*/
void SX1278FSK::writeRegister(byte address, byte data)
{
if(_lock) SPI_MUTEX_LOCK();
digitalWrite(sonde.config.sx1278_ss,LOW);
SPI.beginTransaction(spiset);
digitalWrite(SX1278_SS,LOW);
//delay(1);
bitSet(address, 7); // Bit 7 set to read from registers
SPI.transfer(address);
SPI.transfer(data);
digitalWrite(SX1278_SS,HIGH);
SPI.endTransaction();
digitalWrite(sonde.config.sx1278_ss,HIGH);
#if (SX1278FSK_debug_mode > 1)
Serial.print(F("## Writing: ##\t"));
@ -150,7 +165,7 @@ void SX1278FSK::writeRegister(byte address, byte data)
Serial.print(data, HEX);
Serial.println();
#endif
if(_lock) SPI_MUTEX_UNLOCK();
}
/*
@ -164,20 +179,23 @@ void SX1278FSK::writeRegister(byte address, byte data)
*/
void SX1278FSK::clearIRQFlags()
{
#if 0
byte st0;
// Save the previous status
st0 = readRegister(REG_OP_MODE);
// Stdby mode to write in registers
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE);
#endif
// FSK mode flags1 register
writeRegister(REG_IRQ_FLAGS1, 0xFF);
// FSK mode flags2 register
writeRegister(REG_IRQ_FLAGS2, 0xFF);
#if 0
// Getting back to previous status
if(st0 != FSK_STANDBY_MODE) {
writeRegister(REG_OP_MODE, st0);
}
#endif
#if (SX1278FSK_debug_mode > 1)
Serial.println(F("## FSK flags cleared ##"));
#endif
@ -194,7 +212,7 @@ uint8_t SX1278FSK::setFSK()
{
uint8_t state = 2;
byte st0;
byte config1;
//byte config1;
#if (SX1278FSK_debug_mode > 1)
Serial.println();
@ -563,6 +581,13 @@ int32_t SX1278FSK::getAFC()
AFC = (int32_t)(regval * SX127X_FSTEP);
return AFC;
}
uint16_t SX1278FSK::getRawAFC() {
return (readRegister(REG_AFC_MSB)<<8) | readRegister(REG_AFC_LSB);
}
void SX1278FSK::setRawAFC(uint16_t afc) {
writeRegister(REG_AFC_MSB, afc>>8);
writeRegister(REG_AFC_LSB, afc&0xFF);
}
/*
Function: Gets the current supply limit of the power amplifier, protecting battery chemistries.
@ -642,7 +667,7 @@ int8_t SX1278FSK::setMaxCurrent(uint8_t rate)
// Enable Over Current Protection
rate |= B00100000;
state = 1;
//state = 1;
st0 = readRegister(REG_OP_MODE); // Save the previous status
writeRegister(REG_OP_MODE, FSK_STANDBY_MODE); // Set FSK Standby mode to write in registers
writeRegister(REG_OCP, rate); // Modifying maximum current supply
@ -719,7 +744,6 @@ uint8_t SX1278FSK::receivePacketTimeout(uint32_t wait, byte *data)
// It's a bit of a hack.... get RSSI and AFC (a) at beginning of packet and
// for RS41 after about 0.5 sec. It might be more logical to put this decoder-specific
// code into RS41.cpp instead of this file... (maybe TODO?)
if(di==1 || di==290 ) {
int rssi=getRSSI();
int afc=getAFC();
@ -858,4 +882,5 @@ void SX1278FSK::showRxRegisters()
}
#endif
xSemaphoreHandle globalLock =xSemaphoreCreateMutex();
SX1278FSK sx1278 = SX1278FSK();

View File

@ -35,8 +35,6 @@
#define SX1278FSK_debug_mode 0
#define SX1278_SS SS
//! MACROS //
#define bitRead(value, bit) (((value) >> (bit)) & 0x01) // read a bit
#define bitSet(value, bit) ((value) |= (1UL << (bit))) // set bit to '1'
@ -171,7 +169,9 @@ class SX1278FSK
{
public:
// class constructor
SX1278FSK();
SX1278FSK();
void setup(xSemaphoreHandle lock);
// Turn on SX1278 module (return 0 on sucess, 1 otherwise)
uint8_t ON();
@ -240,6 +240,8 @@ public:
// Get current AFC value
int32_t getAFC();
uint16_t getRawAFC();
void setRawAFC(uint16_t afc);
// Get the maximum current supply by the module.
int getMaxCurrent();
@ -254,7 +256,7 @@ public:
// Receive a packet
uint8_t receivePacketTimeout(uint32_t wait, byte *data);
xSemaphoreHandle _lock = NULL;
#if 0
//! It gets the internal temperature of the module.

207
RX_FSK/src/Scanner.cpp Normal file
View File

@ -0,0 +1,207 @@
#include "Scanner.h"
#include <U8x8lib.h>
#include "SX1278FSK.h"
#include "Sonde.h"
#include "Display.h"
double STARTF;
struct scancfg {
int PLOT_W; // Width of plot, in pixel
int PLOT_H8; // Height of plot, in 8 pixel units
int TICK1; // Pixel per MHz marker
int TICK2; // Pixel per sub-Mhz marker (250k or 200k)
double CHANSTEP; // Scanner frequenz steps
int SMPL_PIX; // Frequency steps per pixel
int NCHAN; // number of channels to scan, PLOT_W * SMPL_PIX
int SMOOTH;
int ADDWAIT;
int VSCALE;
};
//struct scancfg scanLCD={ 121, 7, 120/6, 120/6/4, 6000.0/120.0/20.0, 20, 120*20, 1 };
struct scancfg scanLCD={ 121, 7, 120/6, 120/6/4, 6000.0/120.0/10.0, 10, 120*10, 2, 40, 1 };
struct scancfg scanTFT={ 210, 16, 210/6, 210/6/5, 6000.0/210.0/10.0, 10, 210*10, 1, 0, 1 };
struct scancfg scan934x={ 300, 22, 300/6, 300/6/5, 6000.0/300.0/7.0, 7, 300*5, 1, 10, 2 };
struct scancfg &scanconfig = scanTFT;
#define CHANBW 12500
//#define PIXSAMPL (50/CHANBW)
//#define STARTF 401000000
// max of 120*5 and 210*3
//#define MAXN 210*10
//#define MAXN 120*20
#define MAXN 300*10
// max of 120 and 210 (ceil(210/8)*8)) -- now ceil(300/8)*8
//#define MAXDISP 216
#define MAXDISP 304
int scanresult[MAXN];
int scandisp[MAXDISP];
double peakf=0;
//#define PLOT_MIN -250
#define PLOT_MIN (sonde.config.noisefloor*2)
#define PLOT_SCALE(x) (x<PLOT_MIN?0:(x-PLOT_MIN)/2)
const byte tilepatterns[9]={0,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF};
void Scanner::fillTiles(uint8_t *row, int value) {
for(int y=0; y<scanconfig.PLOT_H8; y++) {
int nbits = scanconfig.VSCALE*value - 8*(scanconfig.PLOT_H8-1-y);
if(nbits<0) { row[8*y]=0; continue; }
if(nbits>=8) { row[8*y]=255; continue; }
row[8*y] = tilepatterns[nbits];
}
}
/* LCD:
* There are 16*8 columns to plot, NPLOT must be lower than that
* currently, we use 128 * 50kHz channels
* There are 8*8 values to plot; MIN is bottom end,
* TFT:
* There are 210 columns to plot
* Currently we use 210 * (6000/120)kHz channels, i.e. 28.5714kHz
*/
///// unused???? uint8_t tiles[16] = { 0x0f,0x0f,0x0f,0x0f,0xf0,0xf0,0xf0,0xf0, 1, 3, 7, 15, 31, 63, 127, 255};
// type 0: lcd, 1: tft(ILI9225), 2: lcd(sh1106) 3:TFT(ili9341), 4: TFT(ili9342)
#define ISTFT (sonde.config.disptype!=0 && sonde.config.disptype!=2)
void Scanner::plotResult()
{
int yofs = 0;
char buf[30];
if(ISTFT) {
yofs = 2;
if (sonde.config.marker != 0) {
itoa((sonde.config.startfreq), buf, 10);
disp.rdis->drawString(0, 1, buf);
disp.rdis->drawString(scanconfig.PLOT_W/2-10, 1, "MHz");
itoa((sonde.config.startfreq + 6), buf, 10);
disp.rdis->drawString(scanconfig.PLOT_W-15, 1, buf);
}
}
else {
if (sonde.config.marker != 0) {
itoa((sonde.config.startfreq), buf, 10);
disp.rdis->drawString(0, 1, buf);
disp.rdis->drawString(7, 1, "MHz");
itoa((sonde.config.startfreq + 6), buf, 10);
disp.rdis->drawString(13, 1, buf);
}
}
uint8_t row[scanconfig.PLOT_H8*8];
for(int i=0; i<scanconfig.PLOT_W; i+=8) {
for(int j=0; j<8; j++) {
fillTiles(row+j, PLOT_SCALE(scandisp[i+j]));
if( (i+j)>=scanconfig.PLOT_W ) { for(int y=0; y<scanconfig.PLOT_H8; y++) row[j+8*y]=0; }
if( ((i+j)%scanconfig.TICK1)==0) { row[j] |= 0x07; }
if( ((i+j)%scanconfig.TICK2)==0) { row[j] |= 0x01; }
}
for(int y=0; y<scanconfig.PLOT_H8; y++) {
if(sonde.config.marker && y==1 && !ISTFT ) {
// don't overwrite MHz marker text
if(i<3*8 || (i>=7*8&&i<10*8) || i>=13*8) continue;
}
disp.rdis->drawTile(i/8, y+yofs, 1, row+8*y);
}
}
if(ISTFT) { // large TFT
sprintf(buf, "Peak: %03.3f MHz", peakf*0.000001);
disp.rdis->drawString(0, (yofs+scanconfig.PLOT_H8+1)*8, buf);
} else {
sprintf(buf, "Peak: %03.3fMHz", peakf*0.000001);
disp.rdis->drawString(0, 7, buf);
}
}
void Scanner::scan()
{
if(!ISTFT) { // LCD small
scanconfig = scanLCD;
} else if (sonde.config.disptype==1) {
scanconfig = scanTFT;
} else {
scanconfig = scan934x;
}
// Configure
STARTF = (sonde.config.startfreq * 1000000);
sx1278.writeRegister(REG_PLL_HOP, 0x80); // FastHopOn
sx1278.setRxBandwidth((int)(scanconfig.CHANSTEP*1000));
double bw = sx1278.getRxBandwidth();
Serial.print("RX Bandwith for scan: "); Serial.println(bw);
sx1278.writeRegister(REG_RSSI_CONFIG, scanconfig.SMOOTH&0x07);
sx1278.setFrequency(STARTF);
Serial.print("Start freq = "); Serial.println(STARTF);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
unsigned long start = millis();
uint32_t lastfrf= STARTF * (1<<19) / SX127X_CRYSTAL_FREQ;
float freq = STARTF;
int wait = scanconfig.ADDWAIT + 20 + 1000*(1<<(scanconfig.SMOOTH+1))/4/(0.001*CHANBW);
Serial.print("wait time (us) is: "); Serial.println(wait);
for(int iter=0; iter<3; iter++) { // three interations, to catch all RS41 transmissions
delayMicroseconds(20000); yield();
for(int i=0; i<scanconfig.PLOT_W*scanconfig.SMPL_PIX; i++) {
freq = STARTF + 1000.0*i*scanconfig.CHANSTEP;
//freq = 404000000 + 100*i*scanconfig.CHANSTEP;
uint32_t frf = freq * 1.0 * (1<<19) / SX127X_CRYSTAL_FREQ;
if( (lastfrf>>16)!=(frf>>16) ) {
sx1278.writeRegister(REG_FRF_MSB, (frf&0xff0000)>>16);
}
if( ((lastfrf&0x00ff00)>>8) != ((frf&0x00ff00)>>8) ) {
sx1278.writeRegister(REG_FRF_MID, (frf&0x00ff00)>>8);
}
sx1278.writeRegister(REG_FRF_LSB, (frf&0x0000ff));
lastfrf = frf;
// Wait TS_HOP (20us) + TS_RSSI ( 2^(scacconfig.SMOOTH+1) / 4 / CHANBW us)
delayMicroseconds(wait);
int rssi = -(int)sx1278.readRegister(REG_RSSI_VALUE_FSK);
if(iter==0) { scanresult[i] = rssi; } else {
if(rssi>scanresult[i]) scanresult[i]=rssi;
}
}
}
yield();
unsigned long duration = millis()-start;
Serial.print("wait: ");
Serial.println(wait);
Serial.print("Scan time: ");
Serial.println(duration);
Serial.print("Final freq: ");
Serial.println(freq);
int peakidx=-1;
int peakres=-9999;
for(int i=0; i<scanconfig.PLOT_W; i+=1) {
int r=scanresult[i*scanconfig.SMPL_PIX];
if(r>peakres+1) { peakres=r; peakidx=i*scanconfig.SMPL_PIX; }
scandisp[i] = r;
for(int j=1; j<scanconfig.SMPL_PIX; j++) {
r = scanresult[i*scanconfig.SMPL_PIX+j];
scandisp[i]+=r;
if(r>peakres+1) { peakres=r; peakidx=i*scanconfig.SMPL_PIX+j; }
}
//for(int j=1; j<PIXSAMPL; j++) { if(scanresult[i+j]>scandisp[i/PIXSAMPL]) scandisp[i/PIXSAMPL] = scanresult[i+j]; }
Serial.print(scanresult[i]); Serial.print(", ");
}
peakidx--;
double newpeakf = STARTF + scanconfig.CHANSTEP*1000.0*peakidx;
if(newpeakf<peakf-20000 || newpeakf>peakf+20000) peakf=newpeakf; // different frequency
else if (newpeakf < peakf) peakf = 0.75*newpeakf + 0.25*peakf; // averaging on frequency, some bias towards lower...
else peakf = 0.25*newpeakf + 0.75*peakf;
Serial.println("\n");
for(int i=0; i<scanconfig.PLOT_W; i++) {
scandisp[i]/=scanconfig.SMPL_PIX;
Serial.print(scandisp[i]); Serial.print(", ");
}
Serial.println("\n");
Serial.print("Peak: ");
Serial.print(peakf);
}
Scanner scanner = Scanner();

1
libraries/SondeLib/Scanner.h → RX_FSK/src/Scanner.h Executable file → Normal file
View File

@ -9,7 +9,6 @@
#ifndef inttypes_h
#include <inttypes.h>
#endif
class Scanner
{
private:

247
RX_FSK/src/ShFreqImport.cpp Normal file
View File

@ -0,0 +1,247 @@
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <inttypes.h>
#include <Arduino.h>
#include "ShFreqImport.h"
#include "Sonde.h"
static int ppos;
static int quotes;
static char id[20];
static int idpos;
static float lat, lon, freq;
static char type[20];
static uint8_t inuse[1+99/8]; // MAXSONDE is 99
static char keyword[40];
static int keywordpos;
static char value[40];
static int valuepos;
static int importState;
static float homelat, homelon;
// Map SondeHub type string to Stype. -1 for not supported types.
int ShFreqImport::stringToStype(const char *type) {
if(type[2]=='4') return STYPE_RS41;
if(type[2]=='9') return STYPE_RS92;
if(type[1]=='1') return STYPE_M10M20;
if(type[1]=='2') return STYPE_M10M20;
if(type[0]=='D') return STYPE_DFM;
if(type[2]=='3') return STYPE_MP3H; // TODO: check if '3' is correct
return -1; // iMet is not supported
}
// in Display.cpp
extern float calcLatLonDist(float lat1, float lon1, float lat2, float lon2);
void ShFreqImport::setLabel(int idx, char *id, float lat, float lon) {
snprintf(sonde.sondeList[idx].launchsite, 18, "@%s/%d", id, (int)(calcLatLonDist(homelat, homelon, lat, lon)/1000));
sonde.sondeList[idx].launchsite[17] = 0;
}
void ShFreqImport::usekeyvalue() {
if(strcmp(keyword,"lat")==0) lat = atof(value);
if(strcmp(keyword,"lon")==0) lon = atof(value);
if(strcmp(keyword,"frequency")==0) { if(isnan(freq)) freq = atof(value); } // prefer tx_frequency if available
if(strcmp(keyword,"tx_frequency")==0) freq = atof(value);
if(strcmp(keyword,"type")==0) strcpy(type, value);
}
/* populate qrg.txt with frequency of near sonde */
void ShFreqImport::populate(char *id, float lat, float lon, float freq, const char *type)
{
//printf(" ID %s: %.5f, %.5f f=%.3f, type=%s \n", id, lat, lon, freq, type);
// Skip if freq already exists
int stype = stringToStype(type);
if(stype<0) return; // unsupported type
// check if frequency exists already
// don't do anything if its a static entry
// update label if its a dynamic SH entry
int i;
for(i=0; i<sonde.config.maxsonde; i++) {
if( abs(sonde.sondeList[i].freq-freq)<0.003 ) { // exists already, max error 3000 Hz
Serial.printf("id %s close to %d\n", id, i);
if( sonde.sondeList[i].type == stype) {
char *l = sonde.sondeList[i].launchsite;
if( *l=='@' || *l==' ' || *l==0 ) {
setLabel(i, id, lat, lon);
inuse[i/8] |= (1<<(i&7));
}
sonde.sondeList[i].active = 1;
return;
}
}
}
// find slot
// slots with empty launchsite are considered available for automated entries
while(ppos < sonde.config.maxsonde) {
if( *sonde.sondeList[ppos].launchsite==' ' || *sonde.sondeList[ppos].launchsite== 0 ) break;
ppos++;
}
if(ppos >= sonde.config.maxsonde) {
Serial.println("populate: out of free slots");
return;
} // no more free slots
sonde.clearAllData(&sonde.sondeList[ppos]);
sonde.sondeList[ppos].active = 1;
sonde.sondeList[ppos].freq = freq;
sonde.sondeList[ppos].type = (SondeType)stype;
setLabel(ppos, id, lat, lon);
inuse[ppos/8] |= (1<<(ppos&7));
ppos++;
}
// clears all remaining automatically filled slots (no longer in SH data)
void ShFreqImport::cleanup() {
//Serial.println("Cleanup called ********");
for(int i=0; i<sonde.config.maxsonde; i++) {
if( (((inuse[i/8]>>(i&7))&1) == 0) && *sonde.sondeList[i].launchsite=='@' ) {
// Don't remove the currently active entry
if(i==sonde.currentSonde) continue;
Serial.printf("removing #%d\n", i);
sonde.sondeList[i].launchsite[0] = 0;
sonde.sondeList[i].active = 0;
sonde.sondeList[i].freq = 400;
}
}
}
#define BUFLEN 128
#define VALLEN 20
int ShFreqImport::handleChar(char c) {
Serial.print(c);
switch(importState) {
case START:
// wait for initial '{'
if(c=='{') {
Serial.println("{ found");
importState++;
}
break;
case BEFOREID:
// what for first '"' in { "A1234567" : { ... } }; or detect end
if(c=='"') { idpos = 0; lat = NAN; lon = NAN; freq = NAN; *type = 0; importState++; }
if(c=='}') {
importState = ENDREACHED;
cleanup();
return 1;
}
break;
case COPYID:
// copy ID "A1234567" until second '"' is earched
if(c=='"') { id[idpos] = 0; importState++; }
else id[idpos++] = c;
break;
case AFTERID:
// wait for '{' in '"A1234567": { ...'
if(c=='{') importState++;
break;
case BEFOREKEY:
if(c=='"') { keywordpos = 0; importState++; }
break;
case COPYKEY:
if(c=='"') { importState++; keyword[keywordpos] = 0; /* printf("Key: >%s<\n", keyword);*/ }
else keyword[keywordpos++] = c;
break;
case AFTERKEY:
if(c==':') {
valuepos = 0;
quotes = 0;
if(strcmp(keyword,"lat")==0 || strcmp(keyword, "lon")==0 || strcmp(keyword, "frequency")==0 || strcmp(keyword, "tx_frequency")==0)
importState = BEFORENUMVAL;
else {
if (strcmp(keyword, "type")==0)
importState = BEFORESTRINGVAL;
else
importState = SKIPVAL;
}
}
break;
case BEFORENUMVAL:
if( (c>='0'&&c<='9') || c=='-') { value[0] = c; valuepos=1; importState++; }
break;
case COPYNUMVAL:
if( !(c>='0'&&c<='9') && c!='-' && c!='.' ) {
value[valuepos]=0; importState=SKIPVAL; usekeyvalue();
if(c!=',' && c!='}') break;
}
else { value[valuepos++] = c; break; }
// intenionall fall-through
case SKIPVAL:
// This is rather fragile, we *should* handle more escaping and so on but do not do so so far, only simple quotes
if(c=='"') quotes = !quotes;
if(quotes) break;
if(c==',') importState = BEFOREKEY;
if(c=='}') {
// we have an ID and all key/value pairs, check if its good....
if( !isnan(lat) && !isnan(lon) && !isnan(freq) && type[0] ) {
printf("SondeHub import: populate %s %f %f %f %s\n", id, lat, lon, freq, type);
populate(id, lat, lon, freq, type);
} else {
printf("Skipping incomplete %s\n", id);
}
importState = ENDORNEXT;
}
break;
case BEFORESTRINGVAL:
if(c=='"') importState++;
break;
case COPYSTRINGVAL:
if(c=='"') { importState=SKIPVAL; value[valuepos]=0; usekeyvalue(); }
else value[valuepos++] = c;
break;
case ENDORNEXT:
// next we have to see either a final "}', or a comma before the next id
if(c==',') importState = BEFOREID;
else if (c=='}') { importState = ENDREACHED; cleanup(); return 1; }
break;
case ENDREACHED:
Serial.println("REPLY: END REACHED");
return 1;
}
return 0;
}
// lat lon in deg, dist in km, time in minutes
int ShFreqImport::shImportSendRequest(WiFiClient *client, float lat, float lon, int dist, int time) {
if(!client->connected()) {
if(!client->connect(sonde.config.sondehub.host, 80)) {
Serial.println("Connection FAILED");
return 1;
}
}
Serial.println("Sending SondeHub import request");
char req[300];
snprintf(req, 200, "GET /sondes?lat=%f&lon=%f&distance=%d&last=%d HTTP/1.1\r\n"
"Host: %s\r\n"
"Accept: application/json\r\n"
"Cache-Control: no-cache\r\n\r\n",
lat, lon, dist*1000, time*60, sonde.config.sondehub.host);
client->print(req);
Serial.print(req);
importState = START;
homelat = lat;
homelon = lon;
memset(inuse, 0, sizeof(inuse));
ppos = 0;
return 0;
}
// return 0 if more data should be read (later), 1 if finished (close connection...)
int ShFreqImport::shImportHandleReply(WiFiClient *client) {
if(!client->connected()) return 1;
while(client->available()) {
int res = handleChar(client->read());
if(res) return res;
}
return 0;
}

30
RX_FSK/src/ShFreqImport.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef SH_FREQ_IMPORT_
#define SH_FREQ_IMPORT_H
// Automated frequency import from SondeHub
#include <WiFi.h>
enum ImportState { START, BEFOREID, COPYID, AFTERID, BEFOREKEY, COPYKEY, AFTERKEY, SKIPVAL, BEFORENUMVAL, COPYNUMVAL, BEFORESTRINGVAL, COPYSTRINGVAL, AFTERPAYLOAD, ENDORNEXT, ENDREACHED };
class ShFreqImport {
public:
// Fetch data from sondehub and populate qrg.txt with result
// return: 0: ok; 1: failure
static int shImportSendRequest(WiFiClient *client, float lat, float lon, int dist, int time);
// return 0: ok, need more data; 1: finished/failure, close connection
// Asynchronous I/O. Handle data if available
static int shImportHandleReply(WiFiClient *client);
private:
static int stringToStype(const char *type);
static void setLabel(int idx, char *id, float lat, float lon);
static void usekeyvalue();
static int handleChar(char c);
// add one entry on available slot at or after ppos
static void populate(char *id, float lat, float lon, float freq, const char *type);
static void cleanup();
};
#endif

522
libraries/SondeLib/Sonde.cpp → RX_FSK/src/Sonde.cpp Executable file → Normal file
View File

@ -5,12 +5,13 @@
#include "RS41.h"
#include "RS92.h"
#include "DFM.h"
#include "M10.h"
#include "M10M20.h"
#include "MP3H.h"
#include "SX1278FSK.h"
#include "Display.h"
#include <Wire.h>
extern SX1278FSK sx1278;
uint8_t debug = 255-8-16;
RXTask rxtask = { -1, -1, -1, 0xFFFF, 0 };
@ -19,19 +20,31 @@ const char *evstring[]={"NONE", "KEY1S", "KEY1D", "KEY1M", "KEY1L", "KEY2S", "KE
const char *RXstr[]={"RX_OK", "RX_TIMEOUT", "RX_ERROR", "RX_UNKNOWN"};
int fingerprintValue[]={ 17, 31, 64, 4, 55, 48, 23, 128+23, -1 };
// Dependency to enum SondeType
const char *sondeTypeStr[NSondeTypes] = { "DFM ", "RS41", "RS92", "Mxx ", "M10 ", "M20 ", "MP3H" };
const char *sondeTypeLongStr[NSondeTypes] = { "DFM (all)", "RS41", "RS92", "M10/M20", "M10 ", "M20 ", "MP3-H1" };
const char sondeTypeChar[NSondeTypes] = { 'D', '4', 'R', 'M', 'M', '2', '3' };
const char *manufacturer_string[]={"Graw", "Vaisala", "Vaisala", "Meteomodem", "Meteomodem", "Meteomodem", "Meteo-Radiy"};
int fingerprintValue[]={ 17, 31, 64, 4, 55, 48, 23, 128+23, 119, 128+119, -1 };
const char *fingerprintText[]={
"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 LORA v1.0 (0.9\" OLED@4,15)",
"Heltec v1/v2 (0.9\"OLED@4,15)",
"TTGO T-Beam (old version), 0.9\" OLED@21,22",
"TTGO T-Beam (old version), SPI TFT@4,21,22",
"TTGO T-Beam (new version 1.0), 0.9\" OLED@21,22",
"TTGO T-Beam (new version 1.0), SPI TFT@4,13,14",
"TTGO T-Beam (V0.7), 0.9\" OLED@21,22",
"TTGO T-Beam (V0.7), SPI TFT@4,21,22",
"TTGO T-Beam (V1.0), 0.9\" OLED@21,22",
"TTGO T-Beam (V1.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 */
/* global variables from RX_FSK.ino */
int getKeyPressEvent();
int handlePMUirq();
extern uint8_t pmu_irq;
extern SX1278FSK sx1278;
/* Task model:
* There is a background task for all SX1278 interaction.
@ -41,7 +54,7 @@ int getKeyPressEvent(); /* in RX_FSK.ino */
* - Periodically it calls Sonde::receive(), which calls the current decoder's receive()
* function. It should return control to the SX1278 main loop at least once per second.
* It will also set the internal variable receiveResult. The decoder's receive function
* must make sure that there are no FIFI overflows in the SX1278.
* must make sure that there are no FIFO overflows in the SX1278.
* - the Arduino main loop will call the waitRXcomplete function, which should return as
* soon as there is some new data to display, or no later than after 1s, returning the
* value of receiveResult (or timeout, if receiveResult was not set within 1s). It
@ -66,27 +79,35 @@ void Sonde::defaultConfig() {
Serial.printf("Board fingerprint is %d\n", fingerprint);
sondeList = (SondeInfo *)malloc((MAXSONDE+1)*sizeof(SondeInfo));
// addSonde should initialize everything anyway, so this should not strictly be necessary, but does no harm either
memset(sondeList, 0, (MAXSONDE+1)*sizeof(SondeInfo));
for(int i=0; i<(MAXSONDE+1); i++) {
sondeList[i].freq=400;
sondeList[i].type=STYPE_RS41;
clearAllData(&sondeList[i]);
}
config.touch_thresh = 70;
config.led_pout = -1;
config.power_pout = -1;
config.spectrum=10;
// Try autodetecting board type
config.type = TYPE_TTGO;
// Seems like on startup, GPIO4 is 1 on v1 boards, 0 on v2.1 boards?
config.gpsOn=0;
config.gps_rxd = -1;
config.gps_txd = -1;
strcpy(config.gps_lat,"43.591");
strcpy(config.gps_lon,"7.100");
config.gps_alt=123;
config.gps_Lat=0;
config.gps_Lon=0;
config.gps_Alt=0;
config.batt_adc = -1;
config.sx1278_ss = SS; // default SS pin, on all TTGOs
config.sx1278_miso = MISO;
config.sx1278_mosi = MOSI;
config.sx1278_sck = SCK;
config.oled_rst = 16;
config.disptype = 0;
config.dispcontrast = -1;
config.oled_orient = 1;
config.button2_axp = 0;
config.norx_timeout = 20;
config.screenfile = 1;
if(initlevels[16]==0) {
config.oled_sda = 4;
config.oled_scl = 15;
@ -98,24 +119,58 @@ void Sonde::defaultConfig() {
} else {
config.oled_sda = 21;
config.oled_scl = 22;
if(initlevels[17]==0) { // T-Beam
if(initlevels[12]==0) { // T-Beam v1.0
Serial.println("Autoconfig: looks like T-Beam 1.0 board");
config.button_pin = 38;
config.button2_pin = 15 + 128; //T4 + 128; // T4 = GPIO13
// Maybe in future use as default only PWR as button2?
//config.button2_pin = 255;
config.button2_axp = 1;
config.gps_rxd = 34;
// Check for I2C-Display@21,22
#define SSD1306_ADDRESS 0x3c
if(initlevels[17]==0) { // T-Beam or M5Stack Core2?
int tbeam=7;
if(initlevels[12]==0) {
tbeam = 10;
Serial.println("Autoconfig: looks like T-Beam 1.0 or M5Stack Core2 board");
} else if ( initlevels[4]==1 && initlevels[12]==1 ) {
tbeam = 11;
Serial.println("Autoconfig: looks like T-Beam 1.1 board");
}
if(tbeam == 10 || tbeam == 11) { // T-Beam v1.0 or T-Beam v1.1
Wire.begin(21, 22);
Wire.beginTransmission(SSD1306_ADDRESS);
byte err = Wire.endTransmission();
delay(100); // otherwise its too fast?!
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
if(err!=0 && fingerprint!=17) { // hmm. 17 after powerup with oled commected and no i2c answer!?!?
#define BM8563_ADDRESS 0x51
Wire.beginTransmission(BM8563_ADDRESS);
byte err = Wire.endTransmission();
if(err) { // try again
delay(400);
Wire.beginTransmission(BM8563_ADDRESS);
err = Wire.endTransmission();
}
if(err==0) {
Serial.println("M5stack Core2 board detected\n");
config.type = TYPE_M5_CORE2;
config.button_pin = 255;
config.button2_pin = 255;
config.button2_axp = 1;
config.disptype = 4; // ILI9342
config.oled_sda = 23;
config.oled_scl = 18;
config.oled_rst = -1;
config.screenfile = 4;
config.gps_rxd = 13;
config.gps_txd = -1; // 14
config.sx1278_ss = 33;
config.sx1278_miso = 38;
config.sx1278_mosi = 23; //MOSI;
config.sx1278_sck = 18; // SCK;
} else { // some t-beam...
config.button_pin = 38;
config.button2_pin = 15 + 128; //T4 + 128; // T4 = GPIO13
// Maybe in future use as default only PWR as button2?
//config.button2_pin = 255;
config.button2_axp = 1;
config.gps_rxd = 34;
config.gps_txd = 12;
// Check for I2C-Display@21,22
#define SSD1306_ADDRESS 0x3c
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
delay(100); // otherwise its too fast?!
Wire.beginTransmission(SSD1306_ADDRESS);
err = Wire.endTransmission();
if(err!=0 && fingerprint!=17) { // hmm. 17 after powerup with oled commected and no i2c answer!?!?
fingerprint |= 128;
Serial.println("no I2C display found, assuming large TFT display\n");
// CS=0, RST=14, RS=2, SDA=4, CLK=13
@ -124,11 +179,14 @@ void Sonde::defaultConfig() {
config.oled_sda = 4;
config.oled_scl = 13;
config.oled_rst = 14;
config.spectrum = -1; // no spectrum for now on large display
} else {
config.screenfile = 2;
} else {
// OLED display, pins 21,22 ok...
config.disptype = 0;
Serial.println("... with small OLED display\n");
}
}
} else {
Serial.println("Autoconfig: looks like T-Beam v0.7 board");
@ -142,40 +200,38 @@ void Sonde::defaultConfig() {
config.oled_sda = 4;
config.oled_scl = 21;
config.oled_rst = 22;
config.spectrum = -1; // no spectrum for now on large display
config.screenfile = 2;
}
}
} else {
// Likely a TTGO V2.1_1.6
config.button_pin = 2 + 128; // GPIO2 / T2
config.button2_pin = 14 + 128; // GPIO14 / T6
config.led_pout = 25;
config.batt_adc = 35;
}
}
//
config.noisefloor = -125;
config.gainLNA=0;
strcpy(config.call,"NOCALL");
strcpy(config.passcode, "---");
config.passcode = -1;
strcpy(config.mdnsname, "radiosonde");
strcpy(config.vbatmax,"1.84");
strcpy(config.vbatmin,"1.64");
config.telemetryOn=0;
config.maxsonde=15;
config.debug=0;
config.wifi=1;
config.buzzerOn=0;
config.buzzerFreq=700;
config.buzzerPort=12;
config.dbsmetre=0;
config.degdec=0;
config.maxsonde=15;
config.debug=0;
config.wifi=1;
config.wifiap=1;
config.display[0]=0;
config.display[1]=1;
config.display[2]=-1;
config.startfreq=400;
config.channelbw=10;
config.marker=0;
config.showafc=0;
config.freqofs=0;
config.rs41.agcbw=12500;
config.rs41.rxbw=6300;
@ -183,23 +239,44 @@ void Sonde::defaultConfig() {
config.rs92.alt2d=480;
config.dfm.agcbw=20800;
config.dfm.rxbw=10400;
config.m10m20.agcbw=20800;
config.m10m20.rxbw=12500;
config.mp3h.agcbw=12500;
config.mp3h.rxbw=12500;
config.udpfeed.active = 1;
config.udpfeed.type = 0;
strcpy(config.udpfeed.host, "fra1od.fr.to");
strcpy(config.udpfeed.host, "192.168.42.20");
strcpy(config.udpfeed.symbol, "/O");
config.udpfeed.port = 14580;
config.udpfeed.port = 9002;
config.udpfeed.highrate = 1;
config.udpfeed.idformat = ID_DFMGRAW;
config.tcpfeed.active = 0;
config.tcpfeed.type = 1;
strcpy(config.tcpfeed.host, "radiosondy.info");
strcpy(config.tcpfeed.symbol, "/O");
config.tcpfeed.port = 14580;
config.tcpfeed.port = 12345;
config.tcpfeed.highrate = 10;
config.tcpfeed.idformat = ID_DFMDXL;
config.kisstnc.active = 0;
strcpy(config.ephftp,"igs.bkg.bund.de/IGS/BRDC/");
config.mqtt.active = 0;
strcpy(config.mqtt.id, "rdz_sonde_server");
config.mqtt.port = 1883;
strcpy(config.mqtt.username, "/0");
strcpy(config.mqtt.password, "/0");
strcpy(config.mqtt.prefix, "rdz_sonde_server/");
}
extern struct st_configitems config_list[];
extern const int N_CONFIG;
void Sonde::checkConfig() {
if(config.maxsonde > MAXSONDE) config.maxsonde = MAXSONDE;
if(config.sondehub.fiinterval<5) config.sondehub.fiinterval = 5;
if(config.sondehub.fimaxdist>700) config.sondehub.fimaxdist = 700;
if(config.sondehub.fimaxage>48) config.sondehub.fimaxage = 48;
if(config.sondehub.fimaxdist==0) config.sondehub.fimaxdist = 150;
if(config.sondehub.fimaxage==0) config.sondehub.fimaxage = 2;
}
void Sonde::setConfig(const char *cfg) {
while(*cfg==' '||*cfg=='\t') cfg++;
if(*cfg=='#') return;
@ -209,143 +286,52 @@ void Sonde::setConfig(const char *cfg) {
*s=0; s--;
while(s>cfg && (*s==' '||*s=='\t')) { *s=0; s--; }
Serial.printf("configuration option '%s'=%s \n", cfg, val);
if(strcmp(cfg,"noisefloor")==0) {
config.noisefloor = atoi(val);
if(config.noisefloor==0) config.noisefloor=-130;
} else if(strcmp(cfg,"gainLNA")==0) {
config.gainLNA = atoi(val);
} else if(strcmp(cfg,"call")==0) {
strncpy(config.call, val, 9);
config.call[9]=0;
} else if(strcmp(cfg,"passcode")==0) {
strncpy(config.passcode, val, 9);
} else if(strcmp(cfg,"button_pin")==0) {
config.button_pin = atoi(val);
} else if(strcmp(cfg,"button2_pin")==0) {
config.button2_pin = atoi(val);
} else if(strcmp(cfg,"button2_axp")==0) {
config.button2_axp = atoi(val);
} else if(strcmp(cfg,"touch_thresh")==0) {
config.touch_thresh = atoi(val);
} else if(strcmp(cfg,"led_pout")==0) {
config.led_pout = atoi(val);
} else if(strcmp(cfg,"power_pout")==0) {
config.power_pout = atoi(val);
} else if(strcmp(cfg,"disptype")==0) {
config.disptype = atoi(val);
} else if(strcmp(cfg,"oled_sda")==0) {
config.oled_sda = atoi(val);
} else if(strcmp(cfg,"oled_scl")==0) {
config.oled_scl = atoi(val);
} else if(strcmp(cfg,"oled_rst")==0) {
config.oled_rst = atoi(val);
} else if(strcmp(cfg,"oled_orient")==0) {
config.oled_orient = atoi(val);
} else if(strcmp(cfg,"gpsOn")==0) {
config.gpsOn = atoi(val);
} else if(strcmp(cfg,"gps_rxd")==0) {
config.gps_rxd = atoi(val);
} else if(strcmp(cfg,"gps_txd")==0) {
config.gps_txd = atoi(val);
} else if(strcmp(cfg,"gps_lat")==0) {
strncpy(config.gps_lat, val, 8);
} else if(strcmp(cfg,"gps_lon")==0) {
strncpy(config.gps_lon, val, 8);
} else if(strcmp(cfg,"gps_alt")==0) {
config.gps_alt = atoi(val);
} else if(strcmp(cfg,"maxsonde")==0) {
config.maxsonde = atoi(val);
if(config.maxsonde>MAXSONDE) config.maxsonde=MAXSONDE;
} else if(strcmp(cfg,"debug")==0) {
config.debug = atoi(val);
} else if(strcmp(cfg,"wifi")==0) {
config.wifi = atoi(val);
} else if(strcmp(cfg,"wifiap")==0) {
config.wifiap = atoi(val);
} else if(strcmp(cfg,"mdnsname")==0) {
strncpy(config.mdnsname, val, 14);
} else if(strcmp(cfg,"vbatmax")==0) {
strncpy(config.vbatmax,val,5);
} else if(strcmp(cfg,"vbatmin")==0) {
strncpy(config.vbatmin,val,5);
} else if(strcmp(cfg,"telemetryOn")==0) {
config.telemetryOn=atoi(val);
} else if(strcmp(cfg,"buzzerOn")==0) {
config.buzzerOn=atoi(val);
} else if(strcmp(cfg,"buzzerPort")==0) {
config.buzzerPort=atoi(val);
} else if(strcmp(cfg,"buzzerFreq")==0) {
config.buzzerFreq=atoi(val);
} else if(strcmp(cfg,"dbsmetre")==0) {
config.dbsmetre=atoi(val);
} else if(strcmp(cfg,"degdec")==0) {
config.degdec=atoi(val);
} else if(strcmp(cfg,"display")==0) {
int i = 0;
char *ptr;
while(val) {
ptr = strchr(val,',');
if(ptr) *ptr = 0;
config.display[i++] = atoi(val);
val = ptr?ptr+1:NULL;
Serial.printf("appending value %d next is %s\n", config.display[i-1], val?val:"");
// new code: use config_list to find config entry...
int i;
for(i=0; i<N_CONFIG; i++) {
if(strcmp(cfg, config_list[i].name)!=0) continue;
if(config_list[i].type>0) { // string with that length
strlcpy((char *)config_list[i].data, val, config_list[i].type+1);
break;
}
config.display[i] = -1;
} else if (strcmp(cfg, "norx_timeout")==0) {
config.norx_timeout = atoi(val);
} else if(strcmp(cfg,"startfreq")==0) {
config.startfreq = atoi(val);
} else if(strcmp(cfg,"channelbw")==0) {
config.channelbw = atoi(val);
} else if(strcmp(cfg,"spectrum")==0) {
config.spectrum = atoi(val);
} else if(strcmp(cfg,"marker")==0) {
config.marker = atoi(val);
} else if(strcmp(cfg,"showafc")==0) {
config.showafc = atoi(val);
} else if(strcmp(cfg,"freqofs")==0) {
config.freqofs = atoi(val);
} else if(strcmp(cfg,"rs41.agcbw")==0) {
config.rs41.agcbw = atoi(val);
} else if(strcmp(cfg,"rs41.rxbw")==0) {
config.rs41.rxbw = atoi(val);
} else if(strcmp(cfg,"dfm.agcbw")==0) {
config.dfm.agcbw = atoi(val);
} else if(strcmp(cfg,"dfm.rxbw")==0) {
config.dfm.rxbw = atoi(val);
} else if(strcmp(cfg,"rs92.alt2d")==0) {
config.rs92.alt2d= atoi(val);
} else if(strcmp(cfg,"kisstnc.active")==0) {
config.kisstnc.active = atoi(val);
} else if(strcmp(cfg,"kisstnc.idformat")==0) {
config.kisstnc.idformat = atoi(val);
} else if(strcmp(cfg,"rs92.rxbw")==0) {
config.rs92.rxbw = atoi(val);
} else if(strcmp(cfg,"axudp.active")==0) {
config.udpfeed.active = atoi(val)>0;
} else if(strcmp(cfg,"axudp.host")==0) {
strncpy(config.udpfeed.host, val, 63);
} else if(strcmp(cfg,"axudp.port")==0) {
config.udpfeed.port = atoi(val);
} else if(strcmp(cfg,"axudp.symbol")==0) {
strncpy(config.udpfeed.symbol, val, 3);
} else if(strcmp(cfg,"axudp.highrate")==0) {
config.udpfeed.highrate = atoi(val);
} else if(strcmp(cfg,"axudp.idformat")==0) {
config.udpfeed.idformat = atoi(val);
} else if(strcmp(cfg,"tcp.active")==0) {
config.tcpfeed.active = atoi(val)>0;
} else if(strcmp(cfg,"tcp.host")==0) {
strncpy(config.tcpfeed.host, val, 63);
} else if(strcmp(cfg,"tcp.port")==0) {
config.tcpfeed.port = atoi(val);
} else if(strcmp(cfg,"tcp.symbol")==0) {
strncpy(config.tcpfeed.symbol, val, 3);
} else if(strcmp(cfg,"tcp.highrate")==0) {
config.tcpfeed.highrate = atoi(val);
} else if(strcmp(cfg,"tcp.idformat")==0) {
config.tcpfeed.idformat = atoi(val);
} else {
switch(config_list[i].type) {
case 0: // integer
case -4: // integer (with "touch button" checkbox in web form)
case -3: // integer (boolean on/off swith in web form)
case -2: // integer (ID type)
*(int *)config_list[i].data = atoi(val);
break;
case -7: // double
{
double d = atof(val);
if(*val == 0 || d==0) d = NAN;
*(double *)config_list[i].data = d;
break;
}
case -6: // display list
{
int idx = 0;
char *ptr;
while(val) {
ptr = strchr(val,',');
if(ptr) *ptr = 0;
config.display[idx++] = atoi(val);
val = ptr?ptr+1:NULL;
Serial.printf("appending value %d next is %s\n", config.display[idx-1], val?val:"");
}
config.display[idx] = -1;
break;
}
default:
// skipping non-supported types
break;
}
break;
}
if(i==N_CONFIG) {
Serial.printf("Invalid config option '%s'=%s \n", cfg, val);
}
}
@ -364,40 +350,49 @@ void Sonde::addSonde(float frequency, SondeType type, int active, char *launchsi
return;
}
Serial.printf("Adding %f - %d - %d - %s\n", frequency, type, active, launchsite);
sondeList[nSonde].type = type;
sondeList[nSonde].freq = frequency;
// reset all data if type or frequency has changed
if(type != sondeList[nSonde].type || frequency != sondeList[nSonde].freq) {
//TODO: Check for potential race condition with decoders
// do not clear extra while decoder is potentiall still accessing it!
if(sondeList[nSonde].extra) free(sondeList[nSonde].extra);
memset(&sondeList[nSonde], 0, sizeof(SondeInfo));
sondeList[nSonde].type = type;
sondeList[nSonde].d.typestr[0] = 0;
sondeList[nSonde].freq = frequency;
memcpy(sondeList[nSonde].rxStat, "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", 18); // unknown/undefined
clearAllData(sondeList+nSonde);
}
sondeList[nSonde].active = active;
strncpy(sondeList[nSonde].launchsite, launchsite, 17);
memcpy(sondeList[nSonde].rxStat, "\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3\x3", 18); // unknown/undefined
nSonde++;
}
// called by updateState (only)
void Sonde::nextConfig() {
currentSonde++;
if(currentSonde>=nSonde) {
if(currentSonde>=config.maxsonde) {
currentSonde=0;
}
// Skip non-active entries (but don't loop forever if there are no active ones)
for(int i=0; i<config.maxsonde; i++) {
for(int i=0; i<config.maxsonde - 1; i++) {
if(!sondeList[currentSonde].active) {
currentSonde++;
if(currentSonde>=nSonde) currentSonde=0;
if(currentSonde>=config.maxsonde) currentSonde=0;
}
}
}
void Sonde::nextRxSonde() {
rxtask.currentSonde++;
if(rxtask.currentSonde>=nSonde) {
if(rxtask.currentSonde>=config.maxsonde) {
rxtask.currentSonde=0;
}
for(int i=0; i<config.maxsonde; i++) {
for(int i=0; i<config.maxsonde - 1; i++) {
if(!sondeList[rxtask.currentSonde].active) {
rxtask.currentSonde++;
if(rxtask.currentSonde>=nSonde) rxtask.currentSonde=0;
if(rxtask.currentSonde>=config.maxsonde) rxtask.currentSonde=0;
}
}
Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
//Serial.printf("nextRxSonde: %d\n", rxtask.currentSonde);
}
void Sonde::nextRxFreq(int addkhz) {
// last entry is for the variable frequency
@ -417,34 +412,50 @@ void Sonde::setup() {
Serial.print("Invalid rxtask.currentSonde: ");
Serial.println(rxtask.currentSonde);
rxtask.currentSonde = 0;
for(int i=0; i<config.maxsonde - 1; i++) {
if(!sondeList[rxtask.currentSonde].active) {
rxtask.currentSonde++;
if(rxtask.currentSonde>=config.maxsonde) rxtask.currentSonde=0;
}
}
sonde.currentSonde = rxtask.currentSonde;
}
// update receiver config
Serial.print("\nSonde::setup() on sonde index ");
Serial.print("Sonde::setup() start on index ");
Serial.println(rxtask.currentSonde);
switch(sondeList[rxtask.currentSonde].type) {
case STYPE_RS41:
rs41.setup(sondeList[rxtask.currentSonde].freq * 1000000);
break;
case STYPE_DFM06:
case STYPE_DFM09:
dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type==STYPE_DFM06?0:1 );
case STYPE_DFM:
dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type );
break;
case STYPE_RS92:
rs92.setup( sondeList[rxtask.currentSonde].freq * 1000000);
break;
case STYPE_M10:
m10.setup( sondeList[rxtask.currentSonde].freq * 1000000);
case STYPE_M20:
case STYPE_M10M20:
m10m20.setup( sondeList[rxtask.currentSonde].freq * 1000000);
break;
case STYPE_MP3H:
mp3h.setup( sondeList[rxtask.currentSonde].freq * 1000000);
break;
}
// debug
float afcbw = sx1278.getAFCBandwidth();
float rxbw = sx1278.getRxBandwidth();
Serial.printf("AFC BW: %f RX BW: %f\n", afcbw, rxbw);
int freq = (int)sx1278.getFrequency();
int afcbw = (int)sx1278.getAFCBandwidth();
int rxbw = (int)sx1278.getRxBandwidth();
Serial.printf("Sonde::setup() done: Type %s Freq %f, AFC BW: %d, RX BW: %d\n", sondeTypeStr[sondeList[rxtask.currentSonde].type], 0.000001*freq, afcbw, rxbw);
// reset rxtimer / norxtimer state
sonde.sondeList[sonde.currentSonde].lastState = -1;
}
extern void flashLed(int ms);
extern void buzzerLed(int temps);
void Sonde::receive() {
uint16_t res = 0;
SondeInfo *si = &sondeList[rxtask.currentSonde];
@ -456,16 +467,21 @@ void Sonde::receive() {
res = rs92.receive();
break;
case STYPE_M10:
res = m10.receive();
case STYPE_M20:
case STYPE_M10M20:
res = m10m20.receive();
break;
case STYPE_DFM06:
case STYPE_DFM09:
case STYPE_DFM:
res = dfm.receive();
break;
case STYPE_MP3H:
res = mp3h.receive();
break;
}
// state information for RX_TIMER / NORX_TIMER events
if(res==0) { // RX OK
if(res==RX_OK || res==RX_ERROR) { // something was received...
flashLed(700);
if(sonde.config.buzzerOn==1) {
buzzerLed(500);
@ -473,12 +489,12 @@ void Sonde::receive() {
if(si->lastState != 1) {
si->rxStart = millis();
si->lastState = 1;
sonde.dispsavectlON();
}
} else { // RX not ok
if(res==RX_ERROR) {
flashLed(100);
}
Serial.printf("RX result %d, laststate was %d\n", res, si->lastState);
} else { // RX Timeout
flashLed( (res==RX_OK)?700:100);
ledcWriteTone(0, 0);
//Serial.printf("Sonde::receive(): result %d (%s), laststate was %d\n", res, (res<=3)?RXstr[res]:"?", si->lastState);
if(si->lastState != 0) {
si->norxStart = millis();
si->lastState = 0;
@ -493,28 +509,33 @@ void Sonde::receive() {
int event = getKeyPressEvent();
if (!event) event = timeoutEvent(si);
else sonde.dispsavectlON();
int action = (event==EVT_NONE) ? ACT_NONE : disp.layout->actions[event];
Serial.printf("event %x: action is %x\n", event, action);
//if(action!=ACT_NONE) { Serial.printf("event %x: action is %x\n", event, action); }
// If action is to move to a different sonde index, we do update things here, set activate
// to force the sx1278 task to call sonde.setup(), and pass information about sonde to
// main loop (display update...)
if(action == ACT_NEXTSONDE || action==ACT_PREVSONDE || (action>64&&action<128) ) {
if(action == ACT_DISPLAY_SCANNER || action == ACT_NEXTSONDE || action==ACT_PREVSONDE || (action>64&&action<128) ) {
// handled here...
if(action==ACT_NEXTSONDE||action==ACT_PREVSONDE)
nextRxSonde();
else
nextRxFreq( action-64 );
action = ACT_SONDE(rxtask.currentSonde);
if(action==ACT_DISPLAY_SCANNER) {
// nothing to do here, be re-call setup() for M10/M20 for repeating AFC
}
else {
if(action==ACT_NEXTSONDE||action==ACT_PREVSONDE)
nextRxSonde();
else
nextRxFreq( action-64 );
action = ACT_SONDE(rxtask.currentSonde);
}
if(rxtask.activate==-1) {
// race condition here. maybe better use mutex. TODO
rxtask.activate = action;
rxtask.activate = ACT_SONDE(rxtask.currentSonde);
}
}
Serial.printf("Sonde:receive(): result %d (%s), event %02x => action %02x\n", res, (res<=3)?RXstr[res]:"?", event, action);
res = (action<<8) | (res&0xff);
Serial.printf("receive Result is %04x\n", res);
// let waitRXcomplete resume...
rxtask.receiveResult = res;
}
// return (action<<8) | (rxresult)
@ -522,10 +543,14 @@ uint16_t Sonde::waitRXcomplete() {
uint16_t res=0;
uint32_t t0 = millis();
rxloop:
while( rxtask.receiveResult==0xFFFF && millis()-t0 < 3000) { delay(50); }
while( (pmu_irq!=1) && rxtask.receiveResult==0xFFFF && millis()-t0 < 3000) { delay(50); }
if( pmu_irq ) {
handlePMUirq();
if(pmu_irq!=2) goto rxloop;
}
if( rxtask.receiveResult == RX_UPDATERSSI ) {
rxtask.receiveResult = 0xFFFF;
Serial.print("RSSI update: ");
Serial.printf("RSSI update: %d/2\n", sonde.si()->rssi);
disp.updateDisplayRSSI();
goto rxloop;
}
@ -548,12 +573,16 @@ rxloop:
rs92.waitRXcomplete();
break;
case STYPE_M10:
m10.waitRXcomplete();
case STYPE_M20:
case STYPE_M10M20:
m10m20.waitRXcomplete();
break;
case STYPE_DFM06:
case STYPE_DFM09:
case STYPE_DFM:
dfm.waitRXcomplete();
break;
case STYPE_MP3H:
mp3h.waitRXcomplete();
break;
}
memmove(sonde.si()->rxStat+1, sonde.si()->rxStat, 17);
sonde.si()->rxStat[0] = res;
@ -562,30 +591,29 @@ rxloop:
uint8_t Sonde::timeoutEvent(SondeInfo *si) {
uint32_t now = millis();
#if 1
Serial.printf("Timeout check: %d - %d vs %d; %d - %d vs %d; %d - %d vs %d\n",
#if 0
Serial.printf("Timeout check: %d - %d vs %d; %d - %d vs %d; %d - %d vs %d; lastState: %d\n",
now, si->viewStart, disp.layout->timeouts[0],
now, si->rxStart, disp.layout->timeouts[1],
now, si->norxStart, disp.layout->timeouts[2]);
now, si->norxStart, disp.layout->timeouts[2], si->lastState);
#endif
Serial.printf("lastState is %d\n", si->lastState);
if(disp.layout->timeouts[0]>=0 && now - si->viewStart >= disp.layout->timeouts[0]) {
Serial.println("View timer expired");
Serial.println("Sonde::timeoutEvent: View");
return EVT_VIEWTO;
}
if(si->lastState==1 && disp.layout->timeouts[1]>=0 && now - si->rxStart >= disp.layout->timeouts[1]) {
Serial.println("RX timer expired");
Serial.println("Sonde::timeoutEvent: RX");
return EVT_RXTO;
}
if(si->lastState==0 && disp.layout->timeouts[2]>=0 && now - si->norxStart >= disp.layout->timeouts[2]) {
Serial.println("NORX timer expired");
Serial.println("Sonde::timeoutEvent: NORX");
return EVT_NORXTO;
}
return 0;
}
uint8_t Sonde::updateState(uint8_t event) {
Serial.printf("Sonde::updateState for event %d\n", event);
//Serial.printf("Sonde::updateState for event %02x\n", event);
// No change
if(event==ACT_NONE) return 0xFF;
@ -646,6 +674,14 @@ uint8_t Sonde::updateState(uint8_t event) {
return 0xFF;
}
void Sonde::clearAllData(SondeInfo *si) {
// set everything to 0
memset(&(si->d), 0, sizeof(SondeData));
// set floats to NaN
si->d.lat = si->d.lon = si->d.alt = si->d.vs = si->d.hs = si->d.dir = NAN;
si->d.temperature = si->d.tempRHSensor = si->d.relativeHumidity = si->d.pressure = si->d.batteryVoltage = NAN;
}
void Sonde::updateDisplayPos() {
disp.updateDisplayPos();
}
@ -676,13 +712,25 @@ void Sonde::updateDisplayIP() {
void Sonde::updateDisplay()
{
int t = millis();
disp.updateDisplay();
Serial.printf("updateDisplay took %d ms\n", (int)(millis()-t));
}
void Sonde::clearDisplay() {
disp.rdis->clear();
}
void Sonde::dispsavectlON() {
disp.dispsavectlON();
}
void Sonde::dispsavectlOFF(int rxactive) {
disp.dispsavectlOFF(rxactive);
}
SondeType Sonde::realType(SondeInfo *si) {
if(TYPE_IS_METEO(si->type) && si->d.subtype>0 ) { return si->d.subtype==1 ? STYPE_M10:STYPE_M20; }
else return si->type;
}
Sonde sonde = Sonde();

216
libraries/SondeLib/Sonde.h → RX_FSK/src/Sonde.h Executable file → Normal file
View File

@ -2,9 +2,25 @@
#ifndef Sonde_h
#define Sonde_h
#include <inttypes.h>
#include <Arduino.h>
enum DbgLevel { DEBUG_OFF=0, DEBUG_INFO=1, DEBUG_SPARSER=16, DEBUG_DISPLAY=8 }; // to be extended for configuring serial debug output
extern uint8_t debug;
#define DebugPrint(l,x) if(debug&l) { Serial.print(x); }
#define DebugPrintln(l,x) if(debug&l) { Serial.println(x); }
#define DebugPrintf(l,...) if(debug&l) { Serial.printf(__VA_ARGS__); }
// RX_TIMEOUT: no header detected
// RX_ERROR: header detected, but data not decoded (crc error, etc.)
// RX_OK: header and data ok
// RX_PARTIAL: header detected, some data ok, some with errors
// For RS41: Some blocks with CRC error, some blocks ok in a single frame
// For DFM: In +- 1s, some but not all DAT-subframes 1,2,3,4,5,6,7,8 received
// For RS92 ??? unclear
// For M10/M20 its always all or nothing, no PARTIAL data
// For MP3H its alway all or nothing, no PARTIAL data
// RX_OK: header and all data ok
enum RxResult { RX_OK, RX_TIMEOUT, RX_ERROR, RX_UNKNOWN, RX_NOPOS };
#define RX_UPDATERSSI 0xFFFE
@ -46,39 +62,66 @@ extern const char *RXstr[];
// 01000000 => goto sonde -1
// 01000001 => goto sonde +1
#define NSondeTypes 5
enum SondeType { STYPE_DFM06, STYPE_DFM09, STYPE_RS41, STYPE_RS92, STYPE_M10 };
#define NSondeTypes 7
enum SondeType { STYPE_DFM, STYPE_RS41, STYPE_RS92, STYPE_M10M20, STYPE_M10, STYPE_M20, STYPE_MP3H };
extern const char *sondeTypeStr[NSondeTypes];
extern const char *sondeTypeLongStr[NSondeTypes];
extern const char sondeTypeChar[NSondeTypes];
extern const char *manufacturer_string[NSondeTypes];
typedef struct st_sondeinfo {
// receiver configuration
bool active;
SondeType type;
float freq;
#define ISOLED(cfg) ((cfg).disptype==0 || (cfg).disptype==2)
#define TYPE_IS_DFM(t) ( (t)==STYPE_DFM )
#define TYPE_IS_METEO(t) ( (t)==STYPE_M10M20 || (t)==STYPE_M10 || (t)==STYPE_M20 )
#define VALIDPOS(x) (((x)&0x03)==0x03)
#define VALIDALT(x) ((x)&0x04)
#define VALIDVS(x) ((x)&0x08)
#define VALIDHS(x) ((x)&0x10)
#define VALIDDIR(x) ((x)&0x20)
#define VALIDSATS(x) ((x)&0x40)
typedef struct st_sondedata {
// decoded ID
char id[10];
char ser[12];
bool validID;
char launchsite[18];
char typestr[5]; // decoded type (use type if *typestr==0)
int8_t subtype; /* 0 for none/unknown, hex type for dfm, 1/2 for M10/M20 */
// decoded position
float lat; // latitude
float lon; // longitude
float az; // azimut
float vbat; // vbat %
int durvolheure; // duree vol heure
int durvolminute; // duree vol minute
int durvolseconde; // duree vol seconde
float alt; // altitude
float vs; // vertical speed
float hs; // horizontal speed
float vs; // vertical speed in m/s
float hs; // horizontal speed in m/s
float dir; // 0..360
uint8_t sats; // number of sats
uint8_t validPos; // bit pattern for validity of above 7 fields; 0x80: position is old
// decoded GPS time
uint32_t time;
uint16_t sec;
uint32_t frame;
uint32_t vframe; // vframe==frame if frame is unique/continous, otherweise vframe is derived from gps time
bool validTime;
// shut down timers, currently only for RS41; -1=disabled
uint16_t launchKT, burstKT, countKT;
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)
float temperature; // platinum resistor temperature
float tempRHSensor; // temperature of relative humidity sensor
float relativeHumidity; // relative humidity
float pressure;
float batteryVoltage = -1;
} SondeData;
typedef struct st_sondeinfo {
// First part: static configuration, not decoded data.
// receiver configuration
bool active;
SondeType type;
float freq;
char launchsite[18];
// Second part: internal decoder state. no need to clear this on new sonde
// RSSI from receiver
int rssi; // signal strength
int32_t afc; // afc correction value
@ -88,9 +131,11 @@ typedef struct st_sondeinfo {
uint32_t norxStart; // millis() timestamp of continuous no rx start
uint32_t viewStart; // millis() timestamp of viewinf this sonde with current display
int8_t lastState; // -1: disabled; 0: norx; 1: rx
// shut down timers, currently only for RS41; -1=disabled
int16_t launchKT, burstKT, countKT;
uint16_t crefKT; // frame number in which countKT was last sent
// Third part: decoded data. Clear if reception of a new sonde has started
SondeData d;
// Decoder-specific data, dynamically allocated (for RS41: calibration data)
void *extra;
} SondeInfo;
// rxStat: 3=undef[empty] 1=timeout[.] 2=errro[E] 0=ok[|] 4=no valid position[°]
@ -126,10 +171,16 @@ struct st_dfmconfig {
int agcbw;
int rxbw;
};
struct st_m10m20config {
int agcbw;
int rxbw;
};
struct st_mp3hconfig {
int agcbw;
int rxbw;
};
enum IDTYPE { ID_DFMDXL, ID_DFMGRAW, ID_DFMAUTO };
struct st_feedinfo {
bool active;
int type; // 0:UDP(axudp), 1:TCP(aprs.fi)
@ -139,76 +190,130 @@ struct st_feedinfo {
int lowrate;
int highrate;
int lowlimit;
int idformat; // 0: dxl 1: real 2: auto
};
// maybe extend for external Bluetooth interface?
// internal bluetooth consumes too much memory
struct st_kisstnc {
bool active;
int idformat;
};
struct st_mqtt {
int active;
char id[64];
char host[64];
int port;
char username[64];
char password[64];
char prefix[64];
};
struct st_cm {
int active;
char host[64];
int port;
};
struct st_sondehub {
int active;
int chase;
char host[64];
char callsign[64];
char antenna[64];
char email[64];
int fiactive;
int fiinterval;
int fimaxdist;
double fimaxage;
};
// to be extended
enum { TYPE_TTGO, TYPE_M5_CORE2 };
typedef struct st_rdzconfig {
int type; // autodetected type, TTGO or M5_CORE2
// hardware configuration
int button_pin; // PIN port number menu button (+128 for touch mode)
int button_pin; // PIN port number menu button (+128 for touch mode)
int button2_pin; // PIN port number menu button (+128 for touch mode)
int button2_axp; // Use AXP192 power button as button2
int touch_thresh; // Threshold value (0..100) for touch input button
int led_pout; // POUT port number of LED (used as serial monitor)
int power_pout; // Power control pin (for Heltec v2)
int disptype; // 0=OLED; 1=ILI9225
int oled_sda; // OLED data pin
int oled_scl; // OLED clock pin
int oled_rst; // OLED reset pin
int oled_orient; // OLED orientation (default: 1)
int gpsOn; // GPS Active On=1/Off=0
int oled_sda; // OLED/TFT data pin
int oled_scl; // OLED/TFT clock pin
int oled_rst; // OLED/TFT reset pin
int oled_orient; // OLED/TFT orientation (default: 1)
int gps_rxd; // GPS module RXD pin. We expect 9600 baud NMEA data.
int gps_txd; // GPS module TXD pin
char gps_lat[20]; // QTH no gps latitude
char gps_lon[20]; // QTH no gps longitude
int gps_alt; // QTH no gps altitude
float gps_Lat,gps_Lon, gps_Alt; // QTH gps OM lat, lon, alt
int batt_adc; // Pin for ADC battery measurement (GPIO35 on TTGO V2.1_1.6)
int sx1278_ss; // SPI slave select for sx1278
int sx1278_miso; // SPI MISO for sx1278
int sx1278_mosi; // SPI MOSI for sx1278
int sx1278_sck; // SPI SCK for sx1278
// software configuration
int debug; // show port and config options after reboot
int wifi; // connect to known WLAN 0=skip
int wifiap; // enable/disable WiFi AccessPoint mode 0=disable
int8_t display[30]; // list of display mode (0:scanner, 1:default, 2,... additional modes)
int debug; // show port and config options after reboot
double rxlat;
double rxlon;
double rxalt;
int wifi; // connect to known WLAN 0=skip
int screenfile;
int8_t display[30]; // list of display mode (0:scanner, 1:default, 2,... additional modes)
int dispsaver; // Turn display on/off (0=always on, 10*n+1: off after n seconds,
// 10*n+2: scanner off after n seconds, RX always shown)
int dispcontrast; // For OLED: set contrast to 0..255 (-1: don't set/leave at factory default)
int startfreq; // spectrum display start freq (400, 401, ...)
int channelbw; // spectrum channel bandwidth (valid: 5, 10, 20, 25, 50, 100 kHz)
int spectrum; // show freq spectrum for n seconds -1=disable; 0=forever
int marker; // show freq marker in spectrum 0=disable
int marker; // show freq marker in spectrum 0=disable
int maxsonde; // number of max sonde in scan (range=1-99)
int norx_timeout; // Time after which rx mode switches to scan mode (without rx signal)
int noisefloor; // for spectrum display
int gainLNA;
char mdnsname[15]; // mDNS-Name, defaults to radiosonde
char vbatmax[5]; // Vbat maxi when bat charged
char vbatmin[5]; // Vbat minimum discharged
int telemetryOn; // Active Save information telemetry
int buzzerPort; // Buzzer port
int buzzerFreq; // Buzzer Frequency
int noisefloor; // for spectrum display
char mdnsname[15]; // mDNS-Name, defaults to rdzsonde
// Add f4IYT
int buzzerPort; // Buzzer port
int buzzerFreq; // Buzzer Frequency
int buzzerOn; // Buzzer On
int dbsmetre; // Db or Smetre display
int degdec; // Degres or Decimal 0=decimal 1=degres
// receiver configuration
int showafc; // show afc value in rx screen
int freqofs; // frequency offset (tuner config = rx frequency + freqofs) in Hz
struct st_rs41config rs41; // configuration options specific for RS41 receiver
struct st_rs92config rs92;
struct st_dfmconfig dfm;
struct st_m10m20config m10m20;
struct st_mp3hconfig mp3h;
char ephftp[40];
// data feed configuration
// for now, one feed for each type is enough, but might get extended to more?
char call[10]; // APRS callsign
char passcode[9]; // APRS passcode
int passcode; // APRS passcode
int chase;
char objcall[10]; // APRS object call (for wettersonde.net)
char beaconsym[5]; // APRS beacon symbol
char comment[32];
struct st_feedinfo udpfeed; // target for AXUDP messages
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_mqtt mqtt;
struct st_sondehub sondehub;
struct st_cm cm;
} RDZConfig;
#define MAXSONDE 99
struct st_configitems {
const char *name;
// const char *label; => now handled in JS
int type; // 0: numeric; i>0 string of length i; -1: separator; -2: type selector
void *data;
};
// defined in RX_FSK.ino
extern struct st_configitems config_list[];
extern const int N_CONFIG;
#define MAXSONDE 50
extern int fingerprintValue[];
extern const char *fingerprintText[];
@ -226,9 +331,12 @@ public:
// moved to heap, saving space in .bss
//SondeInfo sondeList[MAXSONDE+1];
SondeInfo *sondeList;
// helper function for type string
static SondeType realType(SondeInfo *si);
Sonde();
void defaultConfig();
void checkConfig();
void setConfig(const char *str);
void clearSonde();
@ -241,13 +349,9 @@ public:
void setup();
void receive();
uint16_t waitRXcomplete();
/* old and temp interface */
#if 0
void processRXbyte(uint8_t data);
int receiveFrame();
#endif
SondeInfo *si();
void clearAllData(SondeInfo *si);
uint8_t timeoutEvent(SondeInfo *si);
uint8_t updateState(uint8_t event);
@ -261,6 +365,8 @@ public:
void updateDisplayIP();
void updateDisplay();
void clearDisplay();
void dispsavectlON();
void dispsavectlOFF(int rxactive);
void setIP(String ip, bool isAP);
};

117
libraries/SondeLib/aprs.cpp → RX_FSK/src/aprs.cpp Executable file → Normal file
View File

@ -17,6 +17,8 @@
#include <inttypes.h>
#include "aprs.h"
extern const char *version_name;
extern const char *version_id;
#if 0
int openudp(const char *ip, int port, struct sockaddr_in *si) {
int fd;
@ -204,7 +206,7 @@ extern int aprsstr_mon2raw(const char *mon, char raw[], int raw_len)
--n;
}
aprsstr_appcrc(raw, raw_len, p);
fprintf(stderr,"results in %s\n",raw);
//fprintf(stderr,"results in %s\n",raw);
return p+2;
} /* end mon2raw() */
@ -215,6 +217,7 @@ extern int aprsstr_mon2kiss(const char *mon, char raw[], int raw_len)
if(len==0) return 0;
int idx=0;
raw[idx++] = '\xC0';
raw[idx++] = 0; // channel 0
for(int i=0; i<len-2; i++) { // -2: discard CRC, not used in KISS
if(tmp[i]=='\xC0') {
raw[idx++] = '\xDB';
@ -234,28 +237,11 @@ extern int aprsstr_mon2kiss(const char *mon, char raw[], int raw_len)
#define FEET (1.0/0.3048)
#define KNOTS (1.851984)
#define X2C_max_longcard 0xFFFFFFFFUL
static uint32_t X2C_TRUNCC(double x, uint32_t min0, uint32_t max0)
{
uint32_t i;
if (x < (double)min0)
i = (uint32_t)min0;
if (x > (double)max0)
i = (uint32_t)max0;
i = (uint32_t)x;
if ((double)i > x)
--i;
return i;
}
static uint32_t truncc(double r)
{
if (r<=0.0) return 0UL;
else if (r>=2.E+9) return 2000000000UL;
else return (uint32_t)X2C_TRUNCC(r,0UL,X2C_max_longcard);
else return (uint32_t)r;
return 0;
} /* end truncc() */
@ -271,16 +257,53 @@ static uint32_t dao91(double x)
} /* end dao91() */
char b[201];
char raw[201];
char b[251];
//char raw[201];
const char *destcall="APRRDZ";
char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym) {
// float lat, float lon, float alt, float speed, float dir, float climb, const char *type, const char *objname, const char *usercall, const char *sym, const char *comm)
*b=0;
char *aprs_send_beacon(const char *usercall, float lat, float lon, const char *sym, const char *comment) {
*b = 0;
aprsstr_append(b, usercall);
aprsstr_append(b, ">");
const char *destcall="APZRDZ";
aprsstr_append(b, destcall);
#if 0
aprsstr_append(b, ":/"); // / is report with timestamp
int i = strlen(b);
int sec = 0; // TODO: NOW!!!
snprintf(b+i, APRS_MAXLEN, "%02d%02d%02dh", sec/(60*60), (sec%(60*60))/60, sec%60);
#else
// report without timestamp
aprsstr_append(b, ":!"); // ! is report w/p timestamp
#endif
// lat
int i = strlen(b);
int lati = abs((int)lat);
int latm = (fabs(lat)-lati)*6000;
snprintf(b+i, APRS_MAXLEN-i, "%02d%02d.%02d%c%c", lati, latm/100, latm%100, lat<0?'S':'N', sym[0]);
// lon
i = strlen(b);
int loni = abs((int)lon);
int lonm = (fabs(lon)-loni)*6000;
snprintf(b+i, APRS_MAXLEN-i, "%03d%02d.%02d%c%c", loni, lonm/100, lonm%100, lon<0?'W':'E', sym[1]);
// maybe add alt
// maybe add DAO?
i = strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "%s", comment);
i = strlen(b);
snprintf(b+i, APRS_MAXLEN-i, " %s-%s", version_name, version_id);
//sprintf(b + strlen(b), "%s", version_name);
return b;
}
char *aprs_senddata(SondeInfo *si, const char *usercall, const char *objcall, const char *sym) {
SondeData *s = &(si->d);
*b=0;
aprsstr_append(b, *objcall ? objcall : usercall);
aprsstr_append(b, ">");
// const char *destcall="APRARX,SONDEGATE,TCPIP,qAR,oh3bsg";
aprsstr_append(b, destcall);
// if(*objcall) { aprsstr_append(b, ","); aprsstr_append(b, usercall); }
// uncompressed
aprsstr_append(b, ":;");
char tmp[10];
@ -290,7 +313,7 @@ char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym) {
// time
int i = strlen(b);
int sec = s->time % 86400;
snprintf(b+i, APRS_MAXLEN-1, "%02d%02d%02dz", sec/(60*60), (sec%(60*60))/60, sec%60);
snprintf(b+i, APRS_MAXLEN-1, "%02d%02d%02dh", sec/(60*60), (sec%(60*60))/60, sec%60);
i = strlen(b);
//aprsstr_append_data(time, ds);
int lati = abs((int)s->lat);
@ -302,25 +325,43 @@ char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym) {
snprintf(b+i, APRS_MAXLEN-i, "%03d%02d.%02d%c%c", loni, lonm/100, lonm%100, s->lon<0?'W':'E', sym[1]);
if(s->hs>0.5) {
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "%03d/%03d", realcard(s->dir+1.5), realcard(s->hs*1.0/KNOTS+0.5));
snprintf(b+i, APRS_MAXLEN-i, "%03d/%03d", realcard(s->dir+1.5), realcard(s->hs*3.6/KNOTS+0.5));
}
if(s->alt>0.5) {
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "/A=%06d", realcard(s->alt*FEET+0.5));
}
int dao=1;
if(dao) {
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "!w%c%c!", 33+dao91(s->lat), 33+dao91(s->lon));
// always use DAO
i=strlen(b);
snprintf(b+i, APRS_MAXLEN-i, "!w%c%c!", 33+dao91(s->lat), 33+dao91(s->lon));
// ??? strcat(b, "&");
i=strlen(b);
i += snprintf(b+i, APRS_MAXLEN-i, "Clb=%.1fm/s ", s->vs );
if( !isnan(s->pressure) ) {
sprintf(b+strlen(b), "p=%.1fhPa ", s->pressure);
}
strcat(b, "&");
char comm[100];
snprintf(comm, 100, "Clb=%.1fm/s %.3fMHz Type=%s", s->vs, s->freq, sondeTypeStr[s->type]);
strcat(b, comm);
if(s->type==STYPE_M10||s->type==STYPE_DFM06||s->type==STYPE_DFM09) {
snprintf(comm, 100, " ser=%s", s->ser);
strcat(b, comm);
if( !isnan(s->temperature) ) {
sprintf(b+strlen(b), "t=%.1fC ", s->temperature);
}
if( !isnan(s->relativeHumidity) ) {
sprintf(b+strlen(b), "h=%.1f%% ", s->relativeHumidity);
}
char type[12];
if ( si->type == STYPE_RS41 && RS41::getSubtype(type, 11, si) == 0 ) {
// type was copied to type
} else {
strncpy(type, sondeTypeStr[sonde.realType(si)], 11); type[11]=0;
}
sprintf(b+strlen(b), "%.3fMHz Type=%s ", si->freq, type /* sondeTypeStr[sonde.realType(si)] */ );
if( s->countKT != 0xffff && s->vframe - s->crefKT < 51 ) {
sprintf(b+strlen(b), "TxOff=%dh%dm ", s->countKT/3600, (s->countKT-s->countKT/3600*3600)/60);
}
if( TYPE_IS_DFM(si->type) || TYPE_IS_METEO(si->type) ) {
sprintf(b + strlen(b), "ser=%s ", s->ser);
}
sprintf(b + strlen(b), "%s", version_name);
return b;
}

4
libraries/SondeLib/aprs.h → RX_FSK/src/aprs.h Executable file → Normal file
View File

@ -2,13 +2,15 @@
#ifndef _aprs_h
#define _aprs_h
#include "Sonde.h"
#include "RS41.h"
#define APRS_MAXLEN 201
void aprs_gencrctab(void);
int aprsstr_mon2raw(const char *mon, char raw[], int raw_len);
int aprsstr_mon2kiss(const char *mon, char raw[], int raw_len);
char *aprs_senddata(SondeInfo *s, const char *usercall, const char *sym);
char *aprs_send_beacon(const char *call, float lat, float lon, const char *sym, const char *comment);
char *aprs_senddata(SondeInfo *s, const char *usercall, const char *objcall, const char *sym);
#endif

View File

@ -30,6 +30,25 @@ TTGO T-Beam 1.0 with OLED display => fingerprint 0010111 => 23
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:4 1:4 2:0 3:4 4:0 5:4 6:0 7:4 8:0 9:4 10:4 11:4 12:0 13:0 14:4 15:4 16:4 17:0 18:0 19:0 20:0 21:4 22:4 23:4 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
TTGO T-Team 1.0 with IL9225 TFT => fingerprint 23
0:1 1:0 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:0 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:1 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
M5Stack Core2
0:1 1:0 2:0 3:1 4:1 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:1 14:1 15:1 16:1 17:0 18:1 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:1 5:1 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:1 14:1 15:1 16:1 17:0 18:1 19:0 20:0 21:1 22:1 23:1 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
Board fingerprint is 87 (nach power on)
0:1 1:0 2:0 3:1 4:0 5:0 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0
0:1 1:1 2:0 3:1 4:0 5:0 6:0 7:1 8:0 9:1 10:1 11:1 12:0 13:0 14:1 15:0 16:1 17:0 18:0 19:0 20:0 21:1 22:1 23:0 24:0 25:0 26:0 27:0 28:0 29:0 30:0 31:0 32:0 33:0 34:0 35:0 36:0 37:0 38:0 (before setup)
Board fingerprint is 22 (nach reset)
Autoconfig: looks like T-Beam 1.0 board
vs 0010111
T-Beam 1.1 seems to be different: 1110111
GPIO4 = 1 (additional pullup) => +64
GPIO12 = 1 (maybe additional pullup) => +32
1
Fingerprint GPIOs: 4, 12, 16, 17, 21, 22, 23,

View File

View File

@ -0,0 +1,495 @@
const uint8_t FreeMono12pt8bBitmaps[] PROGMEM = {
0x00, 0xDB, 0x6D, 0x92, 0x48, 0x01, 0xB8, 0xF7, 0x67, 0x67, 0x67, 0x66,
0x66, 0x62, 0x00, 0x02, 0x40, 0x90, 0x44, 0x11, 0x04, 0x87, 0xFC, 0x48,
0x12, 0x04, 0x87, 0xF8, 0x48, 0x12, 0x04, 0x81, 0x20, 0x48, 0x12, 0x00,
0x08, 0x04, 0x0F, 0x0C, 0x6C, 0x14, 0x02, 0x00, 0xC0, 0x1E, 0x00, 0x80,
0x30, 0x18, 0x17, 0x18, 0x70, 0x10, 0x08, 0x04, 0x00, 0x18, 0x19, 0x04,
0x21, 0x08, 0x44, 0x0F, 0x00, 0x0C, 0x1C, 0x78, 0x20, 0xC0, 0xC8, 0x22,
0x08, 0x42, 0x20, 0x78, 0x3E, 0x40, 0x40, 0x40, 0x20, 0x60, 0x91, 0x8A,
0x8A, 0x86, 0x86, 0x7B, 0xFF, 0x6D, 0xB0, 0x01, 0x26, 0x44, 0xCC, 0xCC,
0xCC, 0x44, 0x62, 0x31, 0x13, 0x24, 0xC9, 0x24, 0x92, 0xD6, 0x90, 0x00,
0x04, 0x02, 0x11, 0x17, 0xF0, 0xC0, 0x50, 0x4C, 0x42, 0x00, 0x04, 0x00,
0x80, 0x10, 0x02, 0x00, 0x40, 0x08, 0x3F, 0xF8, 0x20, 0x04, 0x00, 0x80,
0x10, 0x02, 0x00, 0x3B, 0x9C, 0xC4, 0x60, 0xFF, 0xE0, 0x6F, 0x70, 0x00,
0x80, 0x80, 0x40, 0x40, 0x20, 0x20, 0x10, 0x10, 0x08, 0x08, 0x04, 0x06,
0x02, 0x01, 0x01, 0x00, 0x80, 0x80, 0x40, 0x00, 0x1C, 0x33, 0x10, 0x50,
0x28, 0x14, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x2C, 0x12, 0x18,
0xF8, 0x18, 0x1C, 0x1A, 0x19, 0x08, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04,
0x02, 0x01, 0x00, 0x80, 0x43, 0xFE, 0x1E, 0x11, 0x90, 0x38, 0x10, 0x08,
0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, 0x07, 0xFE, 0x1E,
0x08, 0x64, 0x08, 0x02, 0x00, 0x80, 0x20, 0x70, 0x1C, 0x00, 0x80, 0x10,
0x04, 0x01, 0x00, 0x70, 0x23, 0xF0, 0x06, 0x0A, 0x0A, 0x12, 0x12, 0x22,
0x62, 0x42, 0x82, 0x82, 0xFF, 0x02, 0x02, 0x02, 0x0F, 0x3F, 0x08, 0x22,
0x00, 0x80, 0x20, 0x08, 0x03, 0xF0, 0x02, 0x00, 0x40, 0x10, 0x04, 0x01,
0x00, 0xD0, 0x23, 0xF0, 0x07, 0x8E, 0x0C, 0x0C, 0x04, 0x02, 0x02, 0x31,
0x66, 0xA0, 0xE0, 0x60, 0x28, 0x14, 0x09, 0x08, 0x7C, 0xFF, 0x81, 0x81,
0x01, 0x01, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, 0x08, 0x08, 0x08, 0x08,
0x1C, 0x31, 0x20, 0x50, 0x28, 0x14, 0x09, 0x08, 0x78, 0x43, 0x40, 0xA0,
0x30, 0x18, 0x16, 0x09, 0xF8, 0x1C, 0x31, 0x10, 0x50, 0x18, 0x0C, 0x05,
0x06, 0x87, 0x3E, 0x80, 0x40, 0x20, 0x20, 0x30, 0x33, 0xF0, 0x7F, 0x70,
0x00, 0x06, 0xF7, 0x39, 0xCE, 0x00, 0x00, 0x07, 0x73, 0x98, 0xCC, 0x00,
0x00, 0xC0, 0x60, 0x18, 0x0C, 0x06, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03,
0x00, 0x30, 0x01, 0x00, 0xFF, 0xF0, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x06,
0x00, 0x30, 0x01, 0x80, 0x18, 0x01, 0x80, 0xC0, 0x30, 0x18, 0x0C, 0x02,
0x00, 0x00, 0x3C, 0x61, 0xA0, 0x40, 0x10, 0x08, 0x08, 0x08, 0x18, 0x08,
0x04, 0x00, 0x00, 0x01, 0xC0, 0xE0, 0x3C, 0x62, 0xC1, 0x81, 0x81, 0x87,
0x99, 0x91, 0x91, 0x91, 0x8F, 0x81, 0x80, 0x80, 0x40, 0x3F, 0x1F, 0x00,
0x0C, 0x00, 0x48, 0x01, 0x20, 0x04, 0x80, 0x21, 0x00, 0x84, 0x02, 0x08,
0x1F, 0xE0, 0x40, 0x81, 0x01, 0x08, 0x04, 0x20, 0x13, 0xE1, 0xF0, 0x7E,
0x02, 0x1C, 0x20, 0x62, 0x02, 0x20, 0x22, 0x06, 0x3F, 0x82, 0x1C, 0x20,
0x22, 0x01, 0x20, 0x12, 0x01, 0x20, 0x2F, 0xFC, 0x1E, 0x06, 0x39, 0x01,
0x40, 0x28, 0x01, 0x00, 0x20, 0x04, 0x00, 0x80, 0x10, 0x02, 0x00, 0x20,
0x16, 0x04, 0x3F, 0x00, 0x7C, 0x08, 0x71, 0x01, 0x20, 0x14, 0x02, 0x80,
0x50, 0x0A, 0x01, 0x40, 0x28, 0x05, 0x00, 0xA0, 0x24, 0x0D, 0xFE, 0x00,
0x7F, 0xE4, 0x04, 0x80, 0x90, 0x12, 0x00, 0x44, 0x0F, 0x81, 0x10, 0x22,
0x04, 0x00, 0x80, 0x90, 0x12, 0x03, 0xFF, 0xC0, 0x7F, 0xE4, 0x04, 0x80,
0x90, 0x12, 0x00, 0x44, 0x0F, 0x81, 0x10, 0x22, 0x04, 0x00, 0x80, 0x10,
0x02, 0x01, 0xFC, 0x00, 0x0F, 0x06, 0x39, 0x01, 0x40, 0x08, 0x01, 0x00,
0x20, 0x04, 0x00, 0x83, 0xF0, 0x0A, 0x01, 0x60, 0x26, 0x04, 0x7F, 0x80,
0x70, 0xE2, 0x04, 0x20, 0x42, 0x04, 0x20, 0x42, 0x04, 0x3F, 0xC2, 0x04,
0x20, 0x42, 0x04, 0x20, 0x42, 0x04, 0x20, 0x4F, 0x8F, 0xFF, 0x04, 0x02,
0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x87,
0xFC, 0x0F, 0xE0, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80, 0x08, 0x00, 0x80,
0x08, 0x80, 0x88, 0x08, 0x80, 0x88, 0x08, 0xC1, 0x03, 0xE0, 0x78, 0xF1,
0x01, 0x08, 0x10, 0x43, 0x02, 0x30, 0x13, 0x00, 0xB0, 0x06, 0x60, 0x21,
0x81, 0x04, 0x08, 0x10, 0x40, 0x82, 0x06, 0x7C, 0x1C, 0xFC, 0x04, 0x00,
0x80, 0x10, 0x02, 0x00, 0x40, 0x08, 0x01, 0x00, 0x20, 0x04, 0x04, 0x80,
0x90, 0x12, 0x03, 0xFF, 0xC0, 0x70, 0x18, 0xC0, 0x73, 0x02, 0x8A, 0x0A,
0x28, 0x48, 0x91, 0x22, 0x48, 0x88, 0xA2, 0x23, 0x08, 0x84, 0x22, 0x00,
0x88, 0x02, 0x20, 0x0B, 0xE1, 0xF0, 0xE0, 0xF6, 0x02, 0x50, 0x25, 0x82,
0x48, 0x24, 0x42, 0x44, 0x24, 0x22, 0x43, 0x24, 0x12, 0x40, 0xA4, 0x0A,
0x40, 0x6F, 0x86, 0x0F, 0x01, 0x8C, 0x20, 0x64, 0x02, 0x40, 0x18, 0x01,
0x80, 0x18, 0x01, 0x80, 0x1C, 0x01, 0x40, 0x36, 0x02, 0x30, 0x41, 0xF8,
0x7E, 0x04, 0x38, 0x81, 0x10, 0x12, 0x02, 0x40, 0x48, 0x11, 0xFC, 0x20,
0x04, 0x00, 0x80, 0x10, 0x02, 0x01, 0xFC, 0x00, 0x0F, 0x01, 0x8C, 0x20,
0x64, 0x02, 0x40, 0x18, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0x40,
0x36, 0x02, 0x30, 0x41, 0xF8, 0x04, 0x01, 0xF9, 0x10, 0xE0, 0x7E, 0x01,
0x0E, 0x08, 0x10, 0x40, 0x42, 0x02, 0x10, 0x30, 0x83, 0x07, 0xE0, 0x21,
0x81, 0x06, 0x08, 0x10, 0x40, 0x42, 0x02, 0x7C, 0x0C, 0x1E, 0x0C, 0x74,
0x0D, 0x01, 0x40, 0x18, 0x03, 0x80, 0x1E, 0x00, 0xC0, 0x18, 0x06, 0x01,
0xC0, 0xEF, 0xC0, 0xFF, 0xF0, 0x86, 0x10, 0xC2, 0x10, 0x40, 0x08, 0x01,
0x00, 0x20, 0x04, 0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0x7F, 0x00, 0xF0,
0xF4, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40,
0x24, 0x02, 0x60, 0x22, 0x06, 0x30, 0x41, 0xF8, 0x78, 0x3C, 0x80, 0x22,
0x01, 0x08, 0x04, 0x10, 0x10, 0x40, 0x80, 0x82, 0x02, 0x10, 0x08, 0x40,
0x11, 0x00, 0x48, 0x01, 0xA0, 0x03, 0x00, 0x0C, 0x00, 0x78, 0x39, 0x00,
0x22, 0x00, 0x88, 0xC2, 0x23, 0x08, 0x8A, 0x42, 0x29, 0x09, 0x24, 0x24,
0x90, 0x91, 0x41, 0x85, 0x06, 0x14, 0x18, 0x50, 0x60, 0xC0, 0x70, 0x76,
0x02, 0x20, 0x41, 0x0C, 0x08, 0x80, 0xD0, 0x06, 0x00, 0x60, 0x0D, 0x00,
0x98, 0x10, 0x82, 0x04, 0x60, 0x2F, 0x0F, 0x70, 0x76, 0x02, 0x20, 0x41,
0x0C, 0x18, 0x80, 0x90, 0x05, 0x00, 0x60, 0x02, 0x00, 0x20, 0x02, 0x00,
0x20, 0x02, 0x01, 0xFC, 0x7F, 0x90, 0x24, 0x09, 0x04, 0x42, 0x00, 0x80,
0x40, 0x20, 0x18, 0x04, 0x12, 0x05, 0x01, 0x40, 0x7F, 0xF0, 0xE8, 0x88,
0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x8F, 0x80, 0x40, 0x10, 0x08, 0x02,
0x01, 0x00, 0x40, 0x20, 0x18, 0x04, 0x02, 0x00, 0x80, 0x40, 0x10, 0x08,
0x02, 0x01, 0x00, 0x40, 0xF1, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11,
0x1F, 0x00, 0x0C, 0x0D, 0x04, 0x44, 0x34, 0x08, 0x00, 0xFF, 0xFC, 0x84,
0x30, 0x7F, 0x00, 0x10, 0x02, 0x00, 0x43, 0xF9, 0x81, 0x20, 0x24, 0x04,
0x81, 0x8F, 0xDC, 0xC0, 0x04, 0x00, 0x40, 0x04, 0x00, 0x40, 0x04, 0xFC,
0x50, 0x66, 0x02, 0x60, 0x14, 0x01, 0x40, 0x16, 0x01, 0x60, 0x25, 0x06,
0xCF, 0xC0, 0x3F, 0xCC, 0x19, 0x01, 0x40, 0x08, 0x01, 0x00, 0x20, 0x02,
0x01, 0x60, 0x47, 0xF0, 0x00, 0xC0, 0x04, 0x00, 0x40, 0x04, 0x00, 0x43,
0xE4, 0x41, 0xC8, 0x0C, 0x80, 0x48, 0x04, 0x80, 0x48, 0x04, 0x80, 0xC4,
0x1C, 0x3E, 0x70, 0x3F, 0x10, 0x28, 0x06, 0x01, 0xFF, 0xE0, 0x08, 0x02,
0x00, 0x40, 0x4F, 0xE0, 0x00, 0x07, 0xF1, 0x00, 0x40, 0x10, 0x3F, 0xE1,
0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x0F, 0xF0, 0x3E,
0x68, 0x2A, 0x03, 0x40, 0x68, 0x05, 0x01, 0xA0, 0x36, 0x0E, 0x63, 0x43,
0x88, 0x01, 0x00, 0x40, 0x08, 0x3E, 0x00, 0x60, 0x02, 0x00, 0x20, 0x02,
0x00, 0x20, 0x02, 0xF8, 0x30, 0x42, 0x04, 0x20, 0x42, 0x04, 0x20, 0x42,
0x04, 0x20, 0x42, 0x04, 0xF8, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x00, 0x00,
0x1F, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x0F,
0xFC, 0x04, 0x08, 0x10, 0x00, 0x1F, 0xC0, 0x81, 0x02, 0x04, 0x08, 0x10,
0x20, 0x40, 0x81, 0x02, 0x0F, 0xF0, 0xC0, 0x08, 0x01, 0x00, 0x20, 0x04,
0x00, 0x8F, 0x90, 0x82, 0x20, 0x48, 0x0F, 0x01, 0xB0, 0x23, 0x04, 0x30,
0x83, 0x30, 0x78, 0x3C, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40,
0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x0F, 0xFC, 0xFE, 0xF0,
0xC6, 0x22, 0x10, 0x88, 0x42, 0x21, 0x08, 0x84, 0x22, 0x10, 0x88, 0x42,
0x21, 0x0B, 0xC6, 0x30, 0x6F, 0x83, 0x04, 0x20, 0x42, 0x02, 0x20, 0x22,
0x02, 0x20, 0x22, 0x02, 0x20, 0x2F, 0x8F, 0x3F, 0x0C, 0x13, 0x01, 0x40,
0x28, 0x03, 0x00, 0x60, 0x14, 0x02, 0x40, 0x87, 0xE0, 0xCF, 0xC5, 0x06,
0x60, 0x26, 0x01, 0x40, 0x16, 0x01, 0x60, 0x17, 0x02, 0x58, 0xC4, 0x70,
0x40, 0x04, 0x00, 0x40, 0x0F, 0x80, 0x3F, 0x74, 0x1C, 0x80, 0xC8, 0x04,
0x80, 0x48, 0x04, 0x80, 0x4C, 0x0C, 0x63, 0x41, 0xC4, 0x00, 0x40, 0x04,
0x00, 0x40, 0x1E, 0x73, 0xC2, 0xC4, 0x60, 0x08, 0x01, 0x00, 0x20, 0x04,
0x00, 0x80, 0x10, 0x1F, 0xF0, 0x3F, 0x90, 0x24, 0x09, 0x80, 0x1E, 0x00,
0x60, 0x06, 0x01, 0xC0, 0xEF, 0xE0, 0x00, 0x04, 0x00, 0x80, 0x10, 0x0F,
0xF8, 0x40, 0x08, 0x01, 0x00, 0x20, 0x04, 0x00, 0x80, 0x10, 0x01, 0x02,
0x3F, 0x80, 0xE1, 0xE2, 0x02, 0x20, 0x22, 0x02, 0x20, 0x22, 0x02, 0x20,
0x22, 0x06, 0x20, 0xA1, 0xF3, 0xF8, 0xF9, 0x01, 0x08, 0x10, 0x60, 0x81,
0x0C, 0x08, 0x40, 0x22, 0x01, 0x20, 0x05, 0x00, 0x30, 0x00, 0xF0, 0x7A,
0x01, 0x10, 0x08, 0x8C, 0x42, 0x62, 0x12, 0x90, 0xA5, 0x05, 0x18, 0x28,
0xC0, 0x86, 0x00, 0x78, 0xF3, 0x04, 0x18, 0x80, 0xD0, 0x06, 0x00, 0x70,
0x09, 0x81, 0x0C, 0x20, 0x6F, 0x8F, 0xF0, 0xF2, 0x02, 0x20, 0x41, 0x04,
0x10, 0x80, 0x88, 0x09, 0x00, 0x50, 0x05, 0x00, 0x20, 0x02, 0x00, 0x40,
0x04, 0x07, 0xE0, 0xFF, 0x41, 0xA0, 0x80, 0x80, 0x80, 0x80, 0x80, 0x81,
0xC0, 0xFF, 0xC0, 0x00, 0x61, 0x04, 0x10, 0x41, 0x04, 0x63, 0x02, 0x04,
0x10, 0x41, 0x04, 0x10, 0x30, 0x7F, 0xFF, 0xC0, 0x03, 0x02, 0x08, 0x20,
0x82, 0x08, 0x30, 0x72, 0x08, 0x20, 0x82, 0x08, 0x43, 0x00, 0x78, 0x71,
0x30, 0x30, 0xFF, 0xFE, 0x00, 0x18, 0x00, 0x61, 0xE1, 0x98, 0x66, 0x40,
0x98, 0x01, 0x60, 0x05, 0x80, 0x26, 0x01, 0x18, 0x18, 0x60, 0x41, 0x81,
0x06, 0x00, 0x18, 0x00, 0x60, 0xE1, 0x83, 0x86, 0x00, 0x1F, 0xFF, 0xC0,
0x00, 0xFC, 0x00, 0x92, 0x4B, 0x6D, 0xB0, 0x08, 0x08, 0x08, 0x3E, 0x63,
0x81, 0x80, 0x80, 0x80, 0x80, 0x41, 0x3E, 0x08, 0x08, 0x08, 0x0E, 0x0C,
0xC2, 0x00, 0x80, 0x20, 0x08, 0x0F, 0xC0, 0x40, 0x10, 0x04, 0x01, 0x00,
0x80, 0x20, 0x7F, 0xF0, 0x07, 0x81, 0x87, 0x30, 0x32, 0x02, 0x40, 0x04,
0x00, 0xFF, 0x84, 0x00, 0xFF, 0x84, 0x00, 0x60, 0x02, 0x00, 0x10, 0x30,
0xFC, 0x70, 0x76, 0x02, 0x20, 0x41, 0x0C, 0x18, 0x80, 0x90, 0x07, 0x03,
0xFC, 0x02, 0x00, 0x20, 0x3F, 0xC0, 0x20, 0x02, 0x01, 0xF8, 0x20, 0x84,
0xC0, 0xE0, 0x10, 0x1E, 0x0C, 0x74, 0x0D, 0x01, 0x40, 0x18, 0x03, 0x80,
0x1E, 0x00, 0xC0, 0x18, 0x06, 0x01, 0xC0, 0xEF, 0xC0, 0x1F, 0xC4, 0x08,
0x81, 0x10, 0x0D, 0x81, 0x18, 0x20, 0xC3, 0x06, 0x30, 0x61, 0x84, 0x0B,
0x80, 0xC0, 0x08, 0x83, 0x1F, 0xC0, 0x31, 0x86, 0x80, 0xC0, 0x00, 0x00,
0x0F, 0xE4, 0x09, 0x02, 0x60, 0x07, 0x80, 0x18, 0x01, 0x80, 0x70, 0x3B,
0xF8, 0x07, 0x80, 0x61, 0x82, 0x01, 0x10, 0xD2, 0x4C, 0xC6, 0x21, 0x18,
0x80, 0x62, 0x01, 0x88, 0x06, 0x21, 0x14, 0x7C, 0xD8, 0x02, 0x30, 0x30,
0x3F, 0x80, 0x30, 0x90, 0x13, 0xE8, 0x50, 0xBF, 0x80, 0x08, 0x23, 0x08,
0xC2, 0x30, 0xCC, 0x31, 0x86, 0x18, 0x61, 0x84, 0x18, 0x41, 0x04, 0xFF,
0xE0, 0x04, 0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0x08, 0xFF, 0xE0, 0x07,
0x80, 0x61, 0x82, 0x01, 0x13, 0xC2, 0x44, 0xC6, 0x11, 0x18, 0x44, 0x61,
0xE1, 0x84, 0x86, 0x11, 0x14, 0xE6, 0xD8, 0x02, 0x30, 0x30, 0x3F, 0x80,
0xFE, 0x79, 0x0A, 0x0C, 0x18, 0x49, 0x8C, 0x00, 0x04, 0x00, 0x80, 0x10,
0x02, 0x00, 0x41, 0xFF, 0xC1, 0x00, 0x20, 0x04, 0x00, 0x80, 0x00, 0x00,
0x0F, 0xFE, 0x31, 0x28, 0x41, 0x08, 0x42, 0x30, 0xFC, 0x31, 0x20, 0x42,
0x18, 0x10, 0x61, 0x78, 0x21, 0x84, 0xC0, 0xC0, 0x00, 0x7F, 0x90, 0x24,
0x09, 0x04, 0x42, 0x00, 0x80, 0x40, 0x20, 0x18, 0x04, 0x12, 0x05, 0x01,
0x40, 0x7F, 0xF0, 0xE1, 0xE2, 0x02, 0x20, 0x22, 0x02, 0x20, 0x22, 0x02,
0x20, 0x22, 0x06, 0x20, 0xE3, 0xF3, 0x20, 0x02, 0x00, 0x20, 0x02, 0x00,
0x20, 0x00, 0x07, 0xCF, 0xA3, 0x14, 0x62, 0x8C, 0x51, 0x8A, 0x31, 0x43,
0xA8, 0x1D, 0x00, 0xA0, 0x14, 0x02, 0x80, 0x50, 0x0A, 0x01, 0x41, 0xE6,
0x74, 0x42, 0x1A, 0x06, 0x00, 0x00, 0x07, 0xFA, 0x0D, 0x04, 0x04, 0x04,
0x04, 0x04, 0x04, 0x0E, 0x07, 0xFE, 0x27, 0x08, 0x42, 0x10, 0x84, 0xF8,
0x39, 0x8A, 0x0C, 0x18, 0x30, 0x9E, 0x00, 0x84, 0x18, 0x41, 0x84, 0x18,
0xC1, 0x8C, 0x31, 0x8C, 0x63, 0x08, 0xC2, 0x10, 0x80, 0x03, 0xFC, 0x74,
0x12, 0x10, 0x50, 0x41, 0x41, 0x06, 0x04, 0x88, 0x1E, 0x20, 0x48, 0x81,
0x23, 0x04, 0x04, 0x10, 0x58, 0x41, 0x31, 0x04, 0x3F, 0xF0, 0x3C, 0xF1,
0x0A, 0x24, 0x30, 0xE0, 0x41, 0x81, 0xFE, 0x04, 0x08, 0x10, 0x10, 0xC0,
0x42, 0x84, 0xF1, 0xE0, 0x38, 0xC1, 0x8C, 0x00, 0x07, 0x07, 0x60, 0x22,
0x04, 0x10, 0xC1, 0x88, 0x09, 0x00, 0x50, 0x06, 0x00, 0x20, 0x02, 0x00,
0x20, 0x02, 0x00, 0x20, 0x1F, 0xC0, 0x1C, 0x1C, 0x00, 0x00, 0x00, 0x08,
0x18, 0x60, 0xC0, 0x80, 0x80, 0x81, 0xC1, 0x7E, 0x08, 0x00, 0x30, 0x00,
0x20, 0x00, 0x40, 0x00, 0x00, 0x7C, 0x00, 0x30, 0x01, 0x20, 0x04, 0x80,
0x12, 0x00, 0x84, 0x02, 0x10, 0x08, 0x20, 0x7F, 0x81, 0x02, 0x04, 0x04,
0x20, 0x10, 0x80, 0x4F, 0x87, 0xC0, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00,
0x80, 0x00, 0x00, 0x7C, 0x00, 0x30, 0x01, 0x20, 0x04, 0x80, 0x12, 0x00,
0x84, 0x02, 0x10, 0x08, 0x20, 0x7F, 0x81, 0x02, 0x04, 0x04, 0x20, 0x10,
0x80, 0x4F, 0x87, 0xC0, 0x03, 0x00, 0x1A, 0x00, 0x84, 0x00, 0x00, 0x1F,
0x00, 0x0C, 0x00, 0x48, 0x01, 0x20, 0x04, 0x80, 0x21, 0x00, 0x84, 0x02,
0x08, 0x1F, 0xE0, 0x40, 0x81, 0x01, 0x08, 0x04, 0x20, 0x13, 0xE1, 0xF0,
0x06, 0x20, 0x27, 0x00, 0x00, 0x07, 0xC0, 0x03, 0x00, 0x12, 0x00, 0x48,
0x01, 0x20, 0x08, 0x40, 0x21, 0x00, 0x82, 0x07, 0xF8, 0x10, 0x20, 0x40,
0x42, 0x01, 0x08, 0x04, 0xF8, 0x7C, 0x08, 0x60, 0x71, 0x80, 0x00, 0x07,
0xC0, 0x03, 0x00, 0x12, 0x00, 0x48, 0x01, 0x20, 0x08, 0x40, 0x21, 0x00,
0x82, 0x07, 0xF8, 0x10, 0x20, 0x40, 0x42, 0x01, 0x08, 0x04, 0xF8, 0x7C,
0x03, 0x80, 0x12, 0x00, 0x48, 0x01, 0xE0, 0x00, 0x00, 0x7C, 0x00, 0x30,
0x01, 0x20, 0x04, 0x80, 0x12, 0x00, 0x84, 0x02, 0x10, 0x08, 0x20, 0x7F,
0x81, 0x02, 0x04, 0x04, 0x20, 0x10, 0x80, 0x4F, 0x87, 0xC0, 0x1F, 0xFC,
0x14, 0x10, 0x50, 0x42, 0x41, 0x09, 0x04, 0x24, 0x80, 0x9E, 0x04, 0x48,
0x1F, 0x20, 0x44, 0x02, 0x10, 0x48, 0x41, 0x21, 0x07, 0xDF, 0xF0, 0x1E,
0x06, 0x39, 0x01, 0x40, 0x28, 0x01, 0x00, 0x20, 0x04, 0x00, 0x80, 0x10,
0x02, 0x00, 0x20, 0x16, 0x04, 0x3F, 0x01, 0x00, 0x30, 0x02, 0x03, 0xC0,
0x10, 0x01, 0x00, 0x10, 0x01, 0x00, 0x00, 0xFF, 0xC8, 0x09, 0x01, 0x20,
0x24, 0x00, 0x88, 0x1F, 0x02, 0x20, 0x44, 0x08, 0x01, 0x01, 0x20, 0x24,
0x07, 0xFF, 0x80, 0x00, 0x00, 0x60, 0x18, 0x06, 0x00, 0x00, 0xFF, 0xC8,
0x09, 0x01, 0x20, 0x24, 0x00, 0x88, 0x1F, 0x02, 0x20, 0x44, 0x08, 0x01,
0x01, 0x20, 0x24, 0x07, 0xFF, 0x80, 0x06, 0x01, 0xE0, 0x42, 0x00, 0x07,
0xFE, 0x40, 0x48, 0x09, 0x01, 0x20, 0x04, 0x40, 0xF8, 0x11, 0x02, 0x20,
0x40, 0x08, 0x09, 0x01, 0x20, 0x3F, 0xFC, 0x30, 0xC6, 0x38, 0x00, 0x3F,
0xF2, 0x02, 0x40, 0x48, 0x09, 0x00, 0x22, 0x07, 0xC0, 0x88, 0x11, 0x02,
0x00, 0x40, 0x48, 0x09, 0x01, 0xFF, 0xE0, 0x00, 0x18, 0x06, 0x01, 0x80,
0x07, 0xF8, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20,
0x10, 0x08, 0x04, 0x3F, 0xE0, 0x00, 0x01, 0x03, 0x03, 0x00, 0x07, 0xF8,
0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08,
0x04, 0x3F, 0xE0, 0x18, 0x1A, 0x10, 0xC0, 0x0F, 0xF0, 0x40, 0x20, 0x10,
0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x7F, 0xC0,
0x43, 0x71, 0x80, 0x1F, 0xE0, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02,
0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0xFF, 0x80, 0x3E, 0x02, 0x1C, 0x20,
0x22, 0x01, 0x20, 0x12, 0x01, 0x7E, 0x1A, 0x11, 0x20, 0x12, 0x01, 0x20,
0x12, 0x02, 0x20, 0x67, 0xF8, 0x1C, 0x43, 0x38, 0x00, 0x0E, 0x0F, 0x60,
0x25, 0x02, 0x58, 0x24, 0x82, 0x44, 0x24, 0x42, 0x42, 0x24, 0x32, 0x41,
0x24, 0x0A, 0x40, 0xA4, 0x06, 0xF8, 0x60, 0x00, 0x00, 0x80, 0x06, 0x00,
0x20, 0x00, 0x00, 0xF0, 0x18, 0xC2, 0x06, 0x40, 0x24, 0x01, 0x80, 0x18,
0x01, 0x80, 0x18, 0x01, 0xC0, 0x14, 0x03, 0x60, 0x23, 0x04, 0x1F, 0x80,
0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0x00, 0x00, 0xF0, 0x18, 0xC2, 0x06,
0x40, 0x24, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0xC0, 0x14, 0x03,
0x60, 0x23, 0x04, 0x1F, 0x80, 0x06, 0x00, 0xD0, 0x10, 0x80, 0x00, 0x0F,
0x01, 0x8C, 0x20, 0x64, 0x02, 0x40, 0x18, 0x01, 0x80, 0x18, 0x01, 0x80,
0x1C, 0x01, 0x40, 0x36, 0x02, 0x30, 0x41, 0xF8, 0x0C, 0x41, 0x38, 0x00,
0x00, 0xF0, 0x18, 0xC2, 0x06, 0x40, 0x24, 0x01, 0x80, 0x18, 0x01, 0x80,
0x18, 0x01, 0xC0, 0x14, 0x03, 0x60, 0x23, 0x04, 0x1F, 0x80, 0x10, 0xC3,
0x8C, 0x00, 0x00, 0xF0, 0x18, 0xC2, 0x06, 0x40, 0x24, 0x01, 0x80, 0x18,
0x01, 0x80, 0x18, 0x01, 0xC0, 0x14, 0x03, 0x60, 0x23, 0x04, 0x1F, 0x80,
0x81, 0xA1, 0x89, 0x83, 0x81, 0x81, 0xA1, 0x89, 0x82, 0x80, 0x00, 0x00,
0x00, 0xF1, 0x18, 0xE2, 0x06, 0x40, 0x64, 0x09, 0x81, 0x18, 0x21, 0x86,
0x18, 0x41, 0xC8, 0x15, 0x03, 0x60, 0x27, 0x04, 0x5F, 0x88, 0x00, 0x00,
0x00, 0x80, 0x06, 0x00, 0x20, 0x00, 0x0F, 0x0F, 0x40, 0x24, 0x02, 0x40,
0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x26, 0x02, 0x20,
0x63, 0x04, 0x1F, 0x80, 0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0x00, 0x0F,
0x0F, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24,
0x02, 0x40, 0x26, 0x02, 0x20, 0x63, 0x04, 0x1F, 0x80, 0x06, 0x00, 0xD0,
0x10, 0x80, 0x00, 0xF0, 0xF4, 0x02, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02,
0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x60, 0x22, 0x06, 0x30, 0x41, 0xF8,
0x10, 0xC3, 0x8C, 0x00, 0x0F, 0x0F, 0x40, 0x24, 0x02, 0x40, 0x24, 0x02,
0x40, 0x24, 0x02, 0x40, 0x24, 0x02, 0x40, 0x26, 0x02, 0x20, 0x63, 0x04,
0x1F, 0x80, 0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0x00, 0x07, 0x07, 0x60,
0x22, 0x04, 0x10, 0xC1, 0x88, 0x09, 0x00, 0x50, 0x06, 0x00, 0x20, 0x02,
0x00, 0x20, 0x02, 0x00, 0x20, 0x1F, 0xC0, 0x7E, 0x04, 0x00, 0x80, 0x1F,
0x02, 0x1C, 0x40, 0x88, 0x09, 0x01, 0x20, 0x24, 0x08, 0xFE, 0x10, 0x02,
0x01, 0xFC, 0x00, 0x00, 0x03, 0xE0, 0x84, 0x30, 0x44, 0x08, 0x82, 0x11,
0xC2, 0x0C, 0x40, 0x48, 0x05, 0x00, 0xA0, 0x14, 0x42, 0x88, 0xF8, 0xF0,
0x30, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x20, 0x04, 0x00,
0x87, 0xF3, 0x02, 0x40, 0x48, 0x09, 0x03, 0x1F, 0xB8, 0x02, 0x00, 0x80,
0x20, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x20, 0x04, 0x00, 0x87, 0xF3, 0x02,
0x40, 0x48, 0x09, 0x03, 0x1F, 0xB8, 0x0C, 0x03, 0xC0, 0x8C, 0x00, 0x00,
0x00, 0xFE, 0x00, 0x20, 0x04, 0x00, 0x87, 0xF3, 0x02, 0x40, 0x48, 0x09,
0x03, 0x1F, 0xB8, 0x18, 0x84, 0xE0, 0x00, 0x00, 0x07, 0xF0, 0x01, 0x00,
0x20, 0x04, 0x3F, 0x98, 0x12, 0x02, 0x40, 0x48, 0x18, 0xFD, 0xC0, 0x63,
0x8C, 0x70, 0x00, 0x00, 0x07, 0xF0, 0x01, 0x00, 0x20, 0x04, 0x3F, 0x98,
0x12, 0x02, 0x40, 0x48, 0x18, 0xFD, 0xC0, 0x0E, 0x02, 0x40, 0x48, 0x07,
0x00, 0x00, 0x00, 0x1F, 0xC0, 0x04, 0x00, 0x80, 0x10, 0xFE, 0x60, 0x48,
0x09, 0x01, 0x20, 0x63, 0xF7, 0x3E, 0xF0, 0x0A, 0x20, 0x10, 0x80, 0x41,
0x3F, 0xFD, 0x04, 0x08, 0x10, 0x20, 0x40, 0x43, 0x85, 0xF7, 0xE0, 0x3F,
0xCC, 0x19, 0x01, 0x40, 0x08, 0x01, 0x00, 0x20, 0x02, 0x01, 0x60, 0x47,
0xF0, 0x10, 0x03, 0x00, 0x20, 0x3C, 0x00, 0x10, 0x02, 0x00, 0x40, 0x00,
0x00, 0x0F, 0xC4, 0x0A, 0x01, 0x80, 0x7F, 0xF8, 0x02, 0x00, 0x80, 0x10,
0x13, 0xF8, 0x03, 0x01, 0x00, 0x80, 0x00, 0x00, 0x0F, 0xC4, 0x0A, 0x01,
0x80, 0x7F, 0xF8, 0x02, 0x00, 0x80, 0x10, 0x13, 0xF8, 0x04, 0x06, 0x83,
0x10, 0x82, 0x00, 0x0F, 0xC4, 0x0A, 0x01, 0x80, 0x7F, 0xF8, 0x02, 0x00,
0x80, 0x10, 0x13, 0xF8, 0x31, 0x8C, 0x60, 0x00, 0x00, 0x3F, 0x10, 0x28,
0x06, 0x01, 0xFF, 0xE0, 0x08, 0x02, 0x00, 0x40, 0x4F, 0xE0, 0x30, 0x02,
0x00, 0x40, 0x00, 0x00, 0x1F, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40,
0x10, 0x04, 0x01, 0x0F, 0xFC, 0x02, 0x01, 0x00, 0x80, 0x00, 0x00, 0x1F,
0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x0F, 0xFC,
0x08, 0x05, 0x82, 0x30, 0x00, 0x00, 0x1F, 0x00, 0x40, 0x10, 0x04, 0x01,
0x00, 0x40, 0x10, 0x04, 0x01, 0x0F, 0xFC, 0x63, 0x98, 0xE0, 0x00, 0x00,
0x7C, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x3F,
0xF0, 0x30, 0x81, 0xE0, 0x78, 0x00, 0x80, 0x08, 0x7F, 0x98, 0x36, 0x02,
0x80, 0x70, 0x06, 0x00, 0xC0, 0x2C, 0x04, 0xC1, 0x0F, 0xC0, 0x1C, 0x43,
0x38, 0x00, 0x00, 0x00, 0x6F, 0x83, 0x04, 0x20, 0x42, 0x02, 0x20, 0x22,
0x02, 0x20, 0x22, 0x02, 0x20, 0x2F, 0x8F, 0x10, 0x01, 0x00, 0x10, 0x00,
0x00, 0x00, 0x7E, 0x18, 0x26, 0x02, 0x80, 0x50, 0x06, 0x00, 0xC0, 0x28,
0x04, 0x81, 0x0F, 0xC0, 0x02, 0x00, 0x80, 0x20, 0x00, 0x00, 0x00, 0x7E,
0x18, 0x26, 0x02, 0x80, 0x50, 0x06, 0x00, 0xC0, 0x28, 0x04, 0x81, 0x0F,
0xC0, 0x0C, 0x03, 0x40, 0xC4, 0x00, 0x00, 0x00, 0x7E, 0x18, 0x26, 0x02,
0x80, 0x50, 0x06, 0x00, 0xC0, 0x28, 0x04, 0x81, 0x0F, 0xC0, 0x18, 0x84,
0xE0, 0x00, 0x00, 0x03, 0xF0, 0xC1, 0x30, 0x14, 0x02, 0x80, 0x30, 0x06,
0x01, 0x40, 0x24, 0x08, 0x7E, 0x00, 0x71, 0x8E, 0x30, 0x00, 0x00, 0x03,
0xF0, 0xC1, 0x30, 0x14, 0x02, 0x80, 0x30, 0x06, 0x01, 0x40, 0x24, 0x08,
0x7E, 0x00, 0x0C, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x30, 0x06, 0x00, 0x00, 0x11, 0xFA, 0x30, 0x46,
0x0A, 0x41, 0x24, 0x21, 0x44, 0x14, 0x82, 0x70, 0x22, 0x04, 0x5F, 0x88,
0x00, 0x08, 0x00, 0x40, 0x02, 0x00, 0x00, 0x00, 0x0E, 0x1E, 0x20, 0x22,
0x02, 0x20, 0x22, 0x02, 0x20, 0x22, 0x02, 0x20, 0x62, 0x0A, 0x1F, 0x30,
0x01, 0x00, 0x60, 0x0C, 0x00, 0x00, 0x00, 0x0E, 0x1E, 0x20, 0x22, 0x02,
0x20, 0x22, 0x02, 0x20, 0x22, 0x02, 0x20, 0x62, 0x0A, 0x1F, 0x30, 0x06,
0x00, 0xF0, 0x18, 0x80, 0x00, 0x00, 0x0E, 0x1E, 0x20, 0x22, 0x02, 0x20,
0x22, 0x02, 0x20, 0x22, 0x02, 0x20, 0x62, 0x0A, 0x1F, 0x30, 0x30, 0xC3,
0x0C, 0x00, 0x00, 0x00, 0xE1, 0xE2, 0x02, 0x20, 0x22, 0x02, 0x20, 0x22,
0x02, 0x20, 0x22, 0x06, 0x20, 0xA1, 0xF3, 0x00, 0x80, 0x10, 0x02, 0x00,
0x00, 0x00, 0x0F, 0x0F, 0x20, 0x22, 0x04, 0x10, 0x41, 0x08, 0x08, 0x80,
0x90, 0x05, 0x00, 0x50, 0x02, 0x00, 0x20, 0x04, 0x00, 0x40, 0x7E, 0x00,
0xC0, 0x04, 0x00, 0x40, 0x04, 0x00, 0x4F, 0xC5, 0x06, 0x60, 0x26, 0x01,
0x40, 0x16, 0x01, 0x60, 0x17, 0x02, 0x58, 0xC4, 0x70, 0x40, 0x04, 0x00,
0x40, 0x0F, 0x80, 0x38, 0xC3, 0x8C, 0x00, 0x00, 0x00, 0xF0, 0xF2, 0x02,
0x20, 0x41, 0x04, 0x10, 0x80, 0x88, 0x09, 0x00, 0x50, 0x05, 0x00, 0x20,
0x02, 0x00, 0x40, 0x04, 0x07, 0xE0 };
const GFXglyph FreeMono12pt8bGlyphs[] PROGMEM = {
{ 0, 1, 1, 14, 0, 0 }, // 0x20 ' ' U+0020
{ 1, 3, 15, 14, 6, -14 }, // 0x21 '!' U+0021
{ 7, 8, 7, 14, 3, -14 }, // 0x22 '"' U+0022
{ 14, 10, 17, 14, 2, -15 }, // 0x23 '#' U+0023
{ 36, 9, 18, 14, 3, -15 }, // 0x24 '$' U+0024
{ 57, 10, 15, 14, 2, -14 }, // 0x25 '%' U+0025
{ 76, 8, 12, 14, 3, -11 }, // 0x26 '&' U+0026
{ 88, 3, 7, 14, 6, -14 }, // 0x27 ''' U+0027
{ 91, 4, 18, 14, 7, -14 }, // 0x28 '(' U+0028
{ 100, 3, 18, 14, 4, -14 }, // 0x29 ')' U+0029
{ 107, 9, 9, 14, 3, -14 }, // 0x2a '*' U+002A
{ 118, 11, 12, 14, 2, -12 }, // 0x2b '+' U+002B
{ 135, 5, 6, 14, 3, -2 }, // 0x2c ',' U+002C
{ 139, 11, 1, 14, 2, -6 }, // 0x2d '-' U+002D
{ 141, 4, 3, 14, 5, -2 }, // 0x2e '.' U+002E
{ 143, 9, 18, 14, 3, -15 }, // 0x2f '/' U+002F
{ 164, 9, 15, 14, 3, -14 }, // 0x30 '0' U+0030
{ 181, 9, 15, 14, 3, -14 }, // 0x31 '1' U+0031
{ 198, 9, 15, 14, 2, -14 }, // 0x32 '2' U+0032
{ 215, 10, 15, 14, 2, -14 }, // 0x33 '3' U+0033
{ 234, 8, 15, 14, 3, -14 }, // 0x34 '4' U+0034
{ 249, 10, 15, 14, 2, -14 }, // 0x35 '5' U+0035
{ 268, 9, 15, 14, 3, -14 }, // 0x36 '6' U+0036
{ 285, 8, 15, 14, 3, -14 }, // 0x37 '7' U+0037
{ 300, 9, 15, 14, 3, -14 }, // 0x38 '8' U+0038
{ 317, 9, 15, 14, 3, -14 }, // 0x39 '9' U+0039
{ 334, 4, 10, 14, 5, -9 }, // 0x3a ':' U+003A
{ 339, 5, 13, 14, 3, -9 }, // 0x3b ';' U+003B
{ 348, 11, 11, 14, 2, -11 }, // 0x3c '<' U+003C
{ 364, 12, 4, 14, 1, -8 }, // 0x3d '=' U+003D
{ 370, 11, 11, 14, 2, -11 }, // 0x3e '>' U+003E
{ 386, 9, 14, 14, 3, -13 }, // 0x3f '?' U+003F
{ 402, 8, 16, 14, 3, -14 }, // 0x40 '@' U+0040
{ 418, 14, 14, 14, 0, -13 }, // 0x41 'A' U+0041
{ 443, 12, 14, 14, 1, -13 }, // 0x42 'B' U+0042
{ 464, 11, 14, 14, 2, -13 }, // 0x43 'C' U+0043
{ 484, 11, 14, 14, 1, -13 }, // 0x44 'D' U+0044
{ 504, 11, 14, 14, 1, -13 }, // 0x45 'E' U+0045
{ 524, 11, 14, 14, 1, -13 }, // 0x46 'F' U+0046
{ 544, 11, 14, 14, 2, -13 }, // 0x47 'G' U+0047
{ 564, 12, 14, 14, 1, -13 }, // 0x48 'H' U+0048
{ 585, 9, 14, 14, 3, -13 }, // 0x49 'I' U+0049
{ 601, 12, 14, 14, 2, -13 }, // 0x4a 'J' U+004A
{ 622, 13, 14, 14, 1, -13 }, // 0x4b 'K' U+004B
{ 645, 11, 14, 14, 2, -13 }, // 0x4c 'L' U+004C
{ 665, 14, 14, 14, 0, -13 }, // 0x4d 'M' U+004D
{ 690, 12, 14, 14, 1, -13 }, // 0x4e 'N' U+004E
{ 711, 12, 14, 14, 1, -13 }, // 0x4f 'O' U+004F
{ 732, 11, 14, 14, 1, -13 }, // 0x50 'P' U+0050
{ 752, 12, 17, 14, 1, -13 }, // 0x51 'Q' U+0051
{ 778, 13, 14, 14, 1, -13 }, // 0x52 'R' U+0052
{ 801, 10, 14, 14, 2, -13 }, // 0x53 'S' U+0053
{ 819, 11, 14, 14, 2, -13 }, // 0x54 'T' U+0054
{ 839, 12, 14, 14, 1, -13 }, // 0x55 'U' U+0055
{ 860, 14, 14, 14, 0, -13 }, // 0x56 'V' U+0056
{ 885, 14, 14, 14, 0, -13 }, // 0x57 'W' U+0057
{ 910, 12, 14, 14, 1, -13 }, // 0x58 'X' U+0058
{ 931, 12, 14, 14, 1, -13 }, // 0x59 'Y' U+0059
{ 952, 10, 14, 14, 2, -13 }, // 0x5a 'Z' U+005A
{ 970, 4, 18, 14, 7, -14 }, // 0x5b '[' U+005B
{ 979, 9, 18, 14, 3, -15 }, // 0x5c '\' U+005C
{ 1000, 4, 18, 14, 4, -14 }, // 0x5d ']' U+005D
{ 1009, 9, 7, 14, 3, -14 }, // 0x5e '^' U+005E
{ 1017, 14, 1, 14, 0, 3 }, // 0x5f '_' U+005F
{ 1019, 4, 3, 14, 4, -14 }, // 0x60 '`' U+0060
{ 1021, 11, 10, 14, 2, -9 }, // 0x61 'a' U+0061
{ 1035, 12, 15, 14, 1, -14 }, // 0x62 'b' U+0062
{ 1058, 11, 10, 14, 2, -9 }, // 0x63 'c' U+0063
{ 1072, 12, 15, 14, 2, -14 }, // 0x64 'd' U+0064
{ 1095, 10, 10, 14, 2, -9 }, // 0x65 'e' U+0065
{ 1108, 10, 15, 14, 3, -14 }, // 0x66 'f' U+0066
{ 1127, 11, 14, 14, 2, -9 }, // 0x67 'g' U+0067
{ 1147, 12, 15, 14, 1, -14 }, // 0x68 'h' U+0068
{ 1170, 10, 15, 14, 2, -14 }, // 0x69 'i' U+0069
{ 1189, 7, 19, 14, 4, -14 }, // 0x6a 'j' U+006A
{ 1206, 11, 15, 14, 2, -14 }, // 0x6b 'k' U+006B
{ 1227, 10, 15, 14, 2, -14 }, // 0x6c 'l' U+006C
{ 1246, 14, 10, 14, 0, -9 }, // 0x6d 'm' U+006D
{ 1264, 12, 10, 14, 1, -9 }, // 0x6e 'n' U+006E
{ 1279, 11, 10, 14, 2, -9 }, // 0x6f 'o' U+006F
{ 1293, 12, 14, 14, 1, -9 }, // 0x70 'p' U+0070
{ 1314, 12, 14, 14, 2, -9 }, // 0x71 'q' U+0071
{ 1335, 11, 10, 14, 2, -9 }, // 0x72 'r' U+0072
{ 1349, 10, 10, 14, 2, -9 }, // 0x73 's' U+0073
{ 1362, 11, 14, 14, 1, -13 }, // 0x74 't' U+0074
{ 1382, 12, 10, 14, 1, -9 }, // 0x75 'u' U+0075
{ 1397, 13, 10, 14, 1, -9 }, // 0x76 'v' U+0076
{ 1414, 13, 10, 14, 1, -9 }, // 0x77 'w' U+0077
{ 1431, 12, 10, 14, 1, -9 }, // 0x78 'x' U+0078
{ 1446, 12, 14, 14, 1, -9 }, // 0x79 'y' U+0079
{ 1467, 9, 10, 14, 3, -9 }, // 0x7a 'z' U+007A
{ 1479, 6, 18, 14, 4, -14 }, // 0x7b '{' U+007B
{ 1493, 1, 18, 14, 7, -14 }, // 0x7c '|' U+007C
{ 1496, 6, 18, 14, 5, -14 }, // 0x7d '}' U+007D
{ 1510, 10, 3, 14, 2, -7 }, // 0x7e '~' U+007E
{ 1514, 14, 19, 14, 0, -16 }, // 0x7f 'REPLACEMENT CHARACTER *' U+2370
{ 1548, 1, 1, 14, 0, 0 }, // 0x80 'NO-BREAK SPACE' U+00A0
{ 1549, 3, 15, 14, 6, -10 }, // 0x81 'INVERTED EXCLAMATION MARK' U+00A1
{ 1555, 8, 15, 14, 3, -14 }, // 0x82 'CENT SIGN' U+00A2
{ 1570, 10, 14, 14, 2, -13 }, // 0x83 'POUND SIGN' U+00A3
{ 1588, 12, 14, 14, 1, -13 }, // 0x84 'EURO SIGN *' U+20AC
{ 1609, 12, 14, 14, 1, -13 }, // 0x85 'YEN SIGN' U+00A5
{ 1630, 10, 18, 14, 2, -17 }, // 0x86 'LATIN CAPITAL LETTER S WITH CARON *' U+0160
{ 1653, 11, 15, 14, 2, -13 }, // 0x87 'SECTION SIGN' U+00A7
{ 1674, 10, 15, 14, 2, -14 }, // 0x88 'LATIN SMALL LETTER S WITH CARON *' U+0161
{ 1693, 14, 14, 14, 0, -13 }, // 0x89 'COPYRIGHT SIGN' U+00A9
{ 1718, 7, 7, 14, 4, -13 }, // 0x8a 'FEMININE ORDINAL INDICATOR' U+00AA
{ 1725, 11, 10, 14, 2, -9 }, // 0x8b 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00AB
{ 1739, 11, 7, 14, 2, -10 }, // 0x8c 'NOT SIGN' U+00AC
{ 1749, 11, 1, 14, 2, -6 }, // 0x8d 'SOFT HYPHEN' U+00AD
{ 1751, 14, 14, 14, 0, -13 }, // 0x8e 'REGISTERED SIGN' U+00AE
{ 1776, 7, 1, 14, 4, -13 }, // 0x8f 'MACRON' U+00AF
{ 1777, 7, 7, 14, 4, -14 }, // 0x90 'DEGREE SIGN' U+00B0
{ 1784, 11, 13, 14, 2, -12 }, // 0x91 'PLUS-MINUS SIGN' U+00B1
{ 1802, 6, 9, 14, 4, -14 }, // 0x92 'SUPERSCRIPT TWO' U+00B2
{ 1809, 6, 9, 14, 4, -14 }, // 0x93 'SUPERSCRIPT THREE' U+00B3
{ 1816, 10, 18, 14, 2, -17 }, // 0x94 'LATIN CAPITAL LETTER Z WITH CARON *' U+017D
{ 1839, 12, 15, 14, 1, -9 }, // 0x95 'MICRO SIGN' U+00B5
{ 1862, 11, 16, 14, 2, -14 }, // 0x96 'PILCROW SIGN' U+00B6
{ 1884, 2, 3, 14, 6, -6 }, // 0x97 'MIDDLE DOT' U+00B7
{ 1885, 9, 15, 14, 3, -14 }, // 0x98 'LATIN SMALL LETTER Z WITH CARON *' U+017E
{ 1902, 5, 9, 14, 5, -14 }, // 0x99 'SUPERSCRIPT ONE' U+00B9
{ 1908, 7, 7, 14, 4, -13 }, // 0x9a 'MASCULINE ORDINAL INDICATOR' U+00BA
{ 1915, 11, 10, 14, 2, -9 }, // 0x9b 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00BB
{ 1929, 14, 14, 14, 0, -13 }, // 0x9c 'LATIN CAPITAL LIGATURE OE *' U+0152
{ 1954, 14, 10, 14, 0, -9 }, // 0x9d 'LATIN SMALL LIGATURE OE *' U+0153
{ 1972, 12, 17, 14, 1, -16 }, // 0x9e 'LATIN CAPITAL LETTER Y WITH DIAERESIS *' U+0178
{ 1998, 8, 14, 14, 3, -9 }, // 0x9f 'INVERTED QUESTION MARK' U+00BF
{ 2012, 14, 19, 14, 0, -18 }, // 0xa0 'LATIN CAPITAL LETTER A WITH GRAVE' U+00C0
{ 2046, 14, 19, 14, 0, -18 }, // 0xa1 'LATIN CAPITAL LETTER A WITH ACUTE' U+00C1
{ 2080, 14, 18, 14, 0, -17 }, // 0xa2 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' U+00C2
{ 2112, 14, 17, 14, 0, -16 }, // 0xa3 'LATIN CAPITAL LETTER A WITH TILDE' U+00C3
{ 2142, 14, 17, 14, 0, -16 }, // 0xa4 'LATIN CAPITAL LETTER A WITH DIAERESIS' U+00C4
{ 2172, 14, 19, 14, 0, -18 }, // 0xa5 'LATIN CAPITAL LETTER A WITH RING ABOVE' U+00C5
{ 2206, 14, 14, 14, 0, -13 }, // 0xa6 'LATIN CAPITAL LETTER AE' U+00C6
{ 2231, 11, 18, 14, 2, -13 }, // 0xa7 'LATIN CAPITAL LETTER C WITH CEDILLA' U+00C7
{ 2256, 11, 19, 14, 1, -18 }, // 0xa8 'LATIN CAPITAL LETTER E WITH GRAVE' U+00C8
{ 2283, 11, 19, 14, 1, -18 }, // 0xa9 'LATIN CAPITAL LETTER E WITH ACUTE' U+00C9
{ 2310, 11, 18, 14, 1, -17 }, // 0xaa 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' U+00CA
{ 2335, 11, 17, 14, 1, -16 }, // 0xab 'LATIN CAPITAL LETTER E WITH DIAERESIS' U+00CB
{ 2359, 9, 19, 14, 3, -18 }, // 0xac 'LATIN CAPITAL LETTER I WITH GRAVE' U+00CC
{ 2381, 9, 19, 14, 3, -18 }, // 0xad 'LATIN CAPITAL LETTER I WITH ACUTE' U+00CD
{ 2403, 9, 18, 14, 3, -17 }, // 0xae 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' U+00CE
{ 2424, 9, 17, 14, 3, -16 }, // 0xaf 'LATIN CAPITAL LETTER I WITH DIAERESIS' U+00CF
{ 2444, 12, 14, 14, 0, -13 }, // 0xb0 'LATIN CAPITAL LETTER ETH' U+00D0
{ 2465, 12, 17, 14, 1, -16 }, // 0xb1 'LATIN CAPITAL LETTER N WITH TILDE' U+00D1
{ 2491, 12, 19, 14, 1, -18 }, // 0xb2 'LATIN CAPITAL LETTER O WITH GRAVE' U+00D2
{ 2520, 12, 19, 14, 1, -18 }, // 0xb3 'LATIN CAPITAL LETTER O WITH ACUTE' U+00D3
{ 2549, 12, 18, 14, 1, -17 }, // 0xb4 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' U+00D4
{ 2576, 12, 17, 14, 1, -16 }, // 0xb5 'LATIN CAPITAL LETTER O WITH TILDE' U+00D5
{ 2602, 12, 17, 14, 1, -16 }, // 0xb6 'LATIN CAPITAL LETTER O WITH DIAERESIS' U+00D6
{ 2628, 9, 9, 14, 3, -10 }, // 0xb7 'MULTIPLICATION SIGN' U+00D7
{ 2639, 12, 16, 14, 1, -14 }, // 0xb8 'LATIN CAPITAL LETTER O WITH STROKE' U+00D8
{ 2663, 12, 19, 14, 1, -18 }, // 0xb9 'LATIN CAPITAL LETTER U WITH GRAVE' U+00D9
{ 2692, 12, 19, 14, 1, -18 }, // 0xba 'LATIN CAPITAL LETTER U WITH ACUTE' U+00DA
{ 2721, 12, 18, 14, 1, -17 }, // 0xbb 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' U+00DB
{ 2748, 12, 17, 14, 1, -16 }, // 0xbc 'LATIN CAPITAL LETTER U WITH DIAERESIS' U+00DC
{ 2774, 12, 19, 14, 1, -18 }, // 0xbd 'LATIN CAPITAL LETTER Y WITH ACUTE' U+00DD
{ 2803, 11, 14, 14, 1, -13 }, // 0xbe 'LATIN CAPITAL LETTER THORN' U+00DE
{ 2823, 11, 15, 14, 1, -14 }, // 0xbf 'LATIN SMALL LETTER SHARP S' U+00DF
{ 2844, 11, 15, 14, 2, -14 }, // 0xc0 'LATIN SMALL LETTER A WITH GRAVE' U+00E0
{ 2865, 11, 15, 14, 2, -14 }, // 0xc1 'LATIN SMALL LETTER A WITH ACUTE' U+00E1
{ 2886, 11, 15, 14, 2, -14 }, // 0xc2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' U+00E2
{ 2907, 11, 14, 14, 2, -13 }, // 0xc3 'LATIN SMALL LETTER A WITH TILDE' U+00E3
{ 2927, 11, 14, 14, 2, -13 }, // 0xc4 'LATIN SMALL LETTER A WITH DIAERESIS' U+00E4
{ 2947, 11, 16, 14, 2, -15 }, // 0xc5 'LATIN SMALL LETTER A WITH RING ABOVE' U+00E5
{ 2969, 14, 10, 14, 0, -9 }, // 0xc6 'LATIN SMALL LETTER AE' U+00E6
{ 2987, 11, 14, 14, 2, -9 }, // 0xc7 'LATIN SMALL LETTER C WITH CEDILLA' U+00E7
{ 3007, 10, 15, 14, 2, -14 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8
{ 3026, 10, 15, 14, 2, -14 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9
{ 3045, 10, 15, 14, 2, -14 }, // 0xca 'LATIN SMALL LETTER E WITH CIRCUMFLEX' U+00EA
{ 3064, 10, 14, 14, 2, -13 }, // 0xcb 'LATIN SMALL LETTER E WITH DIAERESIS' U+00EB
{ 3082, 10, 15, 14, 2, -14 }, // 0xcc 'LATIN SMALL LETTER I WITH GRAVE' U+00EC
{ 3101, 10, 15, 14, 2, -14 }, // 0xcd 'LATIN SMALL LETTER I WITH ACUTE' U+00ED
{ 3120, 10, 15, 14, 2, -14 }, // 0xce 'LATIN SMALL LETTER I WITH CIRCUMFLEX' U+00EE
{ 3139, 10, 14, 14, 2, -13 }, // 0xcf 'LATIN SMALL LETTER I WITH DIAERESIS' U+00EF
{ 3157, 11, 15, 14, 2, -14 }, // 0xd0 'LATIN SMALL LETTER ETH' U+00F0
{ 3178, 12, 14, 14, 1, -13 }, // 0xd1 'LATIN SMALL LETTER N WITH TILDE' U+00F1
{ 3199, 11, 15, 14, 2, -14 }, // 0xd2 'LATIN SMALL LETTER O WITH GRAVE' U+00F2
{ 3220, 11, 15, 14, 2, -14 }, // 0xd3 'LATIN SMALL LETTER O WITH ACUTE' U+00F3
{ 3241, 11, 15, 14, 2, -14 }, // 0xd4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' U+00F4
{ 3262, 11, 14, 14, 2, -13 }, // 0xd5 'LATIN SMALL LETTER O WITH TILDE' U+00F5
{ 3282, 11, 14, 14, 2, -13 }, // 0xd6 'LATIN SMALL LETTER O WITH DIAERESIS' U+00F6
{ 3302, 11, 12, 14, 2, -12 }, // 0xd7 'DIVISION SIGN' U+00F7
{ 3319, 12, 12, 14, 1, -10 }, // 0xd8 'LATIN SMALL LETTER O WITH STROKE' U+00F8
{ 3337, 12, 15, 14, 1, -14 }, // 0xd9 'LATIN SMALL LETTER U WITH GRAVE' U+00F9
{ 3360, 12, 15, 14, 1, -14 }, // 0xda 'LATIN SMALL LETTER U WITH ACUTE' U+00FA
{ 3383, 12, 15, 14, 1, -14 }, // 0xdb 'LATIN SMALL LETTER U WITH CIRCUMFLEX' U+00FB
{ 3406, 12, 14, 14, 1, -13 }, // 0xdc 'LATIN SMALL LETTER U WITH DIAERESIS' U+00FC
{ 3427, 12, 19, 14, 1, -14 }, // 0xdd 'LATIN SMALL LETTER Y WITH ACUTE' U+00FD
{ 3456, 12, 18, 14, 1, -13 }, // 0xde 'LATIN SMALL LETTER THORN' U+00FE
{ 3483, 12, 18, 14, 1, -13 } }; // 0xdf 'LATIN SMALL LETTER Y WITH DIAERESIS' U+000FF
const GFXfont FreeMono12pt8b PROGMEM = {
(uint8_t *)FreeMono12pt8bBitmaps,
(GFXglyph *)FreeMono12pt8bGlyphs,
0x20, 0xDF, 26 };
// Approx. 4861 bytes

View File

View File

@ -0,0 +1,374 @@
const uint8_t FreeMono9pt8bBitmaps[] PROGMEM = {
0x00, 0x55, 0x54, 0x1C, 0xDE, 0xF7, 0x39, 0x80, 0x20, 0x50, 0xA1, 0x42,
0x9F, 0xCA, 0x14, 0xFE, 0x91, 0x22, 0x44, 0x80, 0x10, 0x21, 0xB4, 0x08,
0x08, 0x0F, 0x01, 0x83, 0x0B, 0xE0, 0x81, 0x02, 0x00, 0x71, 0x22, 0x44,
0x86, 0x23, 0xB8, 0x0E, 0x22, 0x44, 0x70, 0x78, 0x81, 0x02, 0x06, 0x12,
0xA6, 0x44, 0x76, 0xD9, 0x24, 0x25, 0x25, 0x24, 0x49, 0x12, 0x91, 0x24,
0x9B, 0x49, 0x48, 0x10, 0x22, 0x4B, 0xE2, 0x89, 0x00, 0x00, 0x08, 0x04,
0x02, 0x01, 0x00, 0x83, 0xF8, 0x20, 0x10, 0x08, 0x00, 0x37, 0x64, 0x40,
0x7F, 0x00, 0xFC, 0x02, 0x08, 0x10, 0x40, 0x82, 0x04, 0x10, 0x20, 0x41,
0x02, 0x08, 0x00, 0x38, 0x8A, 0x14, 0x18, 0x30, 0x60, 0xC1, 0x82, 0x89,
0xE0, 0x30, 0xA2, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10, 0x23, 0xF8, 0x79,
0x0A, 0x10, 0x20, 0x41, 0x04, 0x10, 0x41, 0x07, 0xF8, 0x79, 0x08, 0x08,
0x20, 0xC3, 0x80, 0x81, 0x02, 0x07, 0xF0, 0x0C, 0x28, 0x91, 0x24, 0x48,
0xA1, 0x7F, 0x04, 0x08, 0x78, 0x7D, 0x02, 0x04, 0x07, 0xC0, 0x40, 0x81,
0x03, 0x09, 0xF0, 0x1E, 0x41, 0x02, 0x08, 0x96, 0xB0, 0xC1, 0x42, 0x84,
0xF0, 0xFF, 0x04, 0x10, 0x20, 0x41, 0x02, 0x04, 0x10, 0x20, 0x40, 0x79,
0x0A, 0x0C, 0x14, 0x47, 0x31, 0x41, 0x83, 0x09, 0xF0, 0x38, 0x8A, 0x0C,
0x18, 0x28, 0xCE, 0x81, 0x04, 0x09, 0xE0, 0x1F, 0x00, 0x3F, 0x03, 0x30,
0x03, 0x76, 0x44, 0x00, 0x03, 0x04, 0x18, 0x60, 0x60, 0x18, 0x04, 0x03,
0xFF, 0x80, 0x00, 0x1F, 0xF0, 0x40, 0x18, 0x03, 0x00, 0x60, 0x20, 0x60,
0xC0, 0x80, 0xFD, 0x04, 0x08, 0x10, 0xC2, 0x04, 0x00, 0x10, 0x70, 0x78,
0x8A, 0x14, 0x29, 0xD4, 0xA9, 0x4A, 0x89, 0x01, 0x01, 0xE0, 0x3C, 0x01,
0x40, 0x48, 0x09, 0x01, 0x10, 0x42, 0x0F, 0xC1, 0x04, 0x40, 0x9E, 0x3C,
0xFE, 0x20, 0x90, 0x48, 0x27, 0xE2, 0x09, 0x02, 0x81, 0x41, 0x7F, 0x00,
0x3F, 0x20, 0xA0, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x41, 0x1F, 0x00,
0xFC, 0x42, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0xFC, 0xFF, 0x41,
0x41, 0x40, 0x78, 0x48, 0x40, 0x41, 0x41, 0xFF, 0xFF, 0x41, 0x40, 0x40,
0x78, 0x48, 0x40, 0x40, 0x40, 0xF8, 0x3F, 0x20, 0xA0, 0x10, 0x08, 0x04,
0x3E, 0x05, 0x02, 0x41, 0x1F, 0x80, 0xE3, 0x20, 0x90, 0x48, 0x27, 0xF2,
0x09, 0x04, 0x82, 0x41, 0x73, 0xC0, 0xFE, 0x20, 0x40, 0x81, 0x02, 0x04,
0x08, 0x11, 0xFC, 0x3F, 0x02, 0x01, 0x00, 0x80, 0x40, 0x22, 0x11, 0x08,
0x84, 0x3C, 0x00, 0xF3, 0xA1, 0x11, 0x09, 0x05, 0x83, 0x21, 0x08, 0x84,
0x42, 0x78, 0xC0, 0xF8, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x82, 0x41,
0x20, 0xFF, 0xC0, 0xE0, 0xCC, 0x29, 0x45, 0x29, 0x24, 0xA4, 0x94, 0x91,
0x12, 0x02, 0x40, 0x5E, 0x3C, 0x61, 0xCC, 0x23, 0x08, 0xA2, 0x24, 0x89,
0x22, 0x28, 0x8A, 0x21, 0x9C, 0x20, 0x3E, 0x20, 0xA0, 0x50, 0x18, 0x0C,
0x06, 0x03, 0x02, 0x41, 0x1F, 0x00, 0xFE, 0x41, 0x41, 0x41, 0x42, 0x7C,
0x40, 0x40, 0x40, 0xF8, 0x3E, 0x20, 0xA0, 0x50, 0x18, 0x0C, 0x06, 0x03,
0x02, 0x41, 0x1B, 0x06, 0x07, 0xF0, 0xFE, 0x10, 0x44, 0x11, 0x04, 0x42,
0x1F, 0x04, 0x21, 0x08, 0x41, 0x3C, 0x30, 0x7F, 0x06, 0x0C, 0x07, 0x01,
0x80, 0x81, 0x83, 0xF8, 0xFF, 0xC4, 0x42, 0x01, 0x00, 0x80, 0x40, 0x20,
0x10, 0x08, 0x1F, 0x00, 0xE3, 0xA0, 0x90, 0x48, 0x24, 0x12, 0x09, 0x04,
0x82, 0x41, 0x1F, 0x00, 0xF1, 0xE8, 0x10, 0x82, 0x10, 0x82, 0x10, 0x22,
0x04, 0x80, 0x50, 0x0C, 0x00, 0x80, 0xF1, 0xD0, 0x14, 0x49, 0x32, 0x4A,
0x92, 0xA3, 0x28, 0xCA, 0x31, 0x88, 0x60, 0xE3, 0xA1, 0x08, 0x82, 0x80,
0x80, 0xC0, 0x50, 0x44, 0x41, 0x71, 0xC0, 0xE3, 0xA1, 0x08, 0x82, 0x81,
0x80, 0x40, 0x20, 0x10, 0x08, 0x1F, 0x00, 0xFD, 0x0A, 0x20, 0x41, 0x04,
0x10, 0xA1, 0x83, 0xFC, 0xF2, 0x49, 0x24, 0x92, 0x4E, 0x81, 0x01, 0x02,
0x02, 0x04, 0x04, 0x08, 0x08, 0x10, 0x10, 0x20, 0x20, 0xE4, 0x92, 0x49,
0x24, 0x9E, 0x10, 0x51, 0x24, 0x28, 0x20, 0xFF, 0xE0, 0x11, 0x80, 0x1C,
0x31, 0x00, 0x83, 0xC6, 0x24, 0x12, 0x08, 0xFB, 0xE0, 0x08, 0x02, 0x00,
0xB8, 0x31, 0x88, 0x22, 0x04, 0x81, 0x20, 0x48, 0x2F, 0xF0, 0x38, 0x46,
0x82, 0x80, 0x80, 0x80, 0x81, 0x7E, 0x03, 0x00, 0x40, 0x10, 0x64, 0x67,
0x20, 0x48, 0x12, 0x04, 0x81, 0x10, 0xC3, 0xD8, 0x1C, 0x62, 0x81, 0x81,
0xFE, 0x80, 0x40, 0x3F, 0x1F, 0x20, 0x20, 0x7C, 0x20, 0x20, 0x20, 0x20,
0x20, 0x20, 0xFE, 0x39, 0xA3, 0xA0, 0x50, 0x28, 0x14, 0x09, 0x0C, 0x7A,
0x01, 0x01, 0x07, 0x80, 0xC0, 0x20, 0x10, 0x0B, 0x86, 0x22, 0x09, 0x04,
0x82, 0x41, 0x20, 0xB8, 0xE0, 0x10, 0x20, 0x03, 0x81, 0x02, 0x04, 0x08,
0x10, 0x23, 0xF8, 0x10, 0x81, 0xF0, 0x84, 0x21, 0x08, 0x42, 0x10, 0xFC,
0xC0, 0x20, 0x10, 0x08, 0xC4, 0x42, 0x41, 0x40, 0x50, 0x44, 0x21, 0x31,
0xE0, 0xF0, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x10, 0x23, 0xF8, 0x59,
0x8C, 0xD1, 0x11, 0x22, 0x24, 0x44, 0x88, 0x91, 0x17, 0x33, 0x5C, 0x31,
0x10, 0x48, 0x24, 0x12, 0x09, 0x05, 0xC7, 0x1C, 0x31, 0x10, 0x50, 0x28,
0x14, 0x09, 0x04, 0x7C, 0x6E, 0x0C, 0x62, 0x08, 0x81, 0x20, 0x48, 0x23,
0x08, 0xBC, 0x20, 0x08, 0x07, 0x00, 0x1D, 0x98, 0xC8, 0x12, 0x04, 0x81,
0x20, 0x44, 0x30, 0xF4, 0x01, 0x00, 0x40, 0x38, 0xC6, 0x59, 0x20, 0x40,
0x40, 0x40, 0x40, 0xFC, 0x39, 0x8E, 0x03, 0x01, 0xC0, 0x60, 0xFE, 0x40,
0x40, 0xFC, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x3F, 0xC3, 0x20, 0x90,
0x48, 0x24, 0x12, 0x09, 0x0C, 0x7B, 0xE3, 0xA0, 0x90, 0x84, 0x42, 0x21,
0x20, 0x50, 0x10, 0xC1, 0xC0, 0xA2, 0x49, 0x25, 0x52, 0xB1, 0x98, 0x44,
0x63, 0x20, 0x88, 0x83, 0x81, 0x81, 0x21, 0x09, 0xC7, 0xC3, 0x20, 0x90,
0x44, 0x42, 0x20, 0xA0, 0x50, 0x10, 0x08, 0x08, 0x1E, 0x00, 0xFF, 0x0A,
0x20, 0x81, 0x04, 0x10, 0xFF, 0x12, 0x22, 0x22, 0xC2, 0x22, 0x22, 0x30,
0xFF, 0xF8, 0xC1, 0x08, 0x42, 0x10, 0x64, 0x21, 0x08, 0x44, 0x00, 0xE2,
0x38, 0xFF, 0xF0, 0x06, 0xFC, 0xD0, 0x58, 0x0B, 0x01, 0x60, 0xCC, 0x21,
0x84, 0x30, 0x06, 0x10, 0xC7, 0x18, 0x03, 0xFF, 0xC0, 0x00, 0xC0, 0x55,
0x54, 0x10, 0x43, 0x91, 0x82, 0x08, 0x11, 0x38, 0x41, 0x00, 0x3C, 0x20,
0x20, 0x20, 0xF8, 0x20, 0x20, 0x20, 0x21, 0x7F, 0x3F, 0xA0, 0x90, 0x10,
0x0F, 0xE7, 0xE2, 0x00, 0x80, 0x20, 0x8F, 0x80, 0xE3, 0xA1, 0x08, 0x82,
0x81, 0x83, 0xF8, 0x20, 0x7C, 0x08, 0x1F, 0x00, 0x6C, 0x20, 0x03, 0xF8,
0x30, 0x60, 0x38, 0x0C, 0x04, 0x0C, 0x1F, 0xC0, 0x1F, 0x10, 0x88, 0x4C,
0x09, 0x82, 0x30, 0x84, 0x31, 0x07, 0x01, 0x10, 0x8F, 0x80, 0x00, 0xD8,
0x40, 0x03, 0x98, 0xE0, 0x30, 0x1C, 0x06, 0x0F, 0xE0, 0x3F, 0x08, 0x12,
0x3D, 0x48, 0x9A, 0x03, 0x40, 0x64, 0x4A, 0x72, 0x40, 0x87, 0xE0, 0xF0,
0xBD, 0x2F, 0x80, 0x00, 0x08, 0x88, 0x88, 0xCC, 0x43, 0x10, 0x44, 0x11,
0xFF, 0x80, 0x40, 0x20, 0x10, 0x00, 0x7F, 0x00, 0x3F, 0x08, 0x12, 0x79,
0x49, 0x19, 0x23, 0x3C, 0x64, 0x8A, 0xCA, 0x40, 0x87, 0xE0, 0xF8, 0xF4,
0x63, 0x17, 0x00, 0x00, 0x04, 0x02, 0x01, 0x0F, 0xF0, 0x40, 0x20, 0x10,
0x00, 0x7F, 0x80, 0x79, 0x12, 0x4F, 0x71, 0x13, 0x1F, 0x04, 0xD0, 0x40,
0x0F, 0xD0, 0xA2, 0x04, 0x10, 0x41, 0x0A, 0x18, 0x3F, 0xC0, 0xC3, 0x20,
0x90, 0x48, 0x24, 0x12, 0x09, 0x0C, 0xDB, 0x50, 0x20, 0x10, 0x08, 0x00,
0x3F, 0x6A, 0xCA, 0xCA, 0x4A, 0x7A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x3B,
0x50, 0x00, 0xD8, 0x40, 0x0F, 0xF0, 0xA2, 0x08, 0x10, 0x41, 0x0F, 0xF0,
0x62, 0x22, 0x27, 0xF4, 0x63, 0x17, 0x00, 0x00, 0x22, 0x18, 0x86, 0x61,
0x11, 0x11, 0x11, 0x10, 0x3F, 0xC8, 0x8A, 0x11, 0x42, 0x08, 0x71, 0x0A,
0x21, 0x02, 0x21, 0x44, 0x27, 0xFC, 0x31, 0x92, 0x98, 0x46, 0x11, 0x87,
0xA1, 0x04, 0xC1, 0xEF, 0x22, 0x11, 0x00, 0x1C, 0x74, 0x21, 0x10, 0x50,
0x30, 0x08, 0x04, 0x02, 0x01, 0x03, 0xE0, 0x10, 0xE0, 0x00, 0x10, 0xC4,
0x20, 0x82, 0x17, 0xC0, 0x10, 0x01, 0x00, 0x10, 0x00, 0x03, 0xC0, 0x14,
0x04, 0x80, 0x90, 0x11, 0x04, 0x20, 0xFC, 0x10, 0x44, 0x09, 0xE3, 0xC0,
0x02, 0x00, 0x80, 0x20, 0x00, 0x03, 0xC0, 0x14, 0x04, 0x80, 0x90, 0x11,
0x04, 0x20, 0xFC, 0x10, 0x44, 0x09, 0xE3, 0xC0, 0x04, 0x01, 0x40, 0x44,
0x00, 0x03, 0xC0, 0x14, 0x04, 0x80, 0x90, 0x11, 0x04, 0x20, 0xFC, 0x10,
0x44, 0x09, 0xE3, 0xC0, 0x08, 0x02, 0xE0, 0x00, 0x1E, 0x00, 0xA0, 0x24,
0x04, 0x80, 0x88, 0x21, 0x07, 0xE0, 0x82, 0x20, 0x4F, 0x1E, 0x11, 0x02,
0x20, 0x00, 0x1E, 0x00, 0xA0, 0x24, 0x04, 0x80, 0x88, 0x21, 0x07, 0xE0,
0x82, 0x20, 0x4F, 0x1E, 0x0E, 0x02, 0x40, 0x30, 0x00, 0x03, 0xC0, 0x14,
0x04, 0x80, 0x90, 0x11, 0x04, 0x20, 0xFC, 0x10, 0x44, 0x09, 0xE3, 0xC0,
0x3F, 0xC2, 0x88, 0x51, 0x0A, 0x02, 0x70, 0x4A, 0x0F, 0x01, 0x21, 0x44,
0x3D, 0xFC, 0x3F, 0x20, 0xA0, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x41,
0x1B, 0x02, 0x00, 0x81, 0xC0, 0x20, 0x10, 0x08, 0x00, 0xFF, 0x41, 0x41,
0x40, 0x78, 0x48, 0x40, 0x41, 0x41, 0xFF, 0x04, 0x08, 0x10, 0x00, 0xFF,
0x41, 0x41, 0x40, 0x78, 0x48, 0x40, 0x41, 0x41, 0xFF, 0x00, 0x1C, 0x22,
0x00, 0xFF, 0x41, 0x41, 0x40, 0x78, 0x48, 0x40, 0x41, 0x41, 0xFF, 0x22,
0x66, 0x00, 0xFF, 0x41, 0x41, 0x40, 0x78, 0x48, 0x40, 0x41, 0x41, 0xFF,
0x20, 0x20, 0x00, 0x0F, 0xE2, 0x04, 0x08, 0x10, 0x20, 0x40, 0x81, 0x1F,
0xC0, 0x04, 0x10, 0x40, 0x0F, 0xE2, 0x04, 0x08, 0x10, 0x20, 0x40, 0x81,
0x1F, 0xC0, 0x10, 0x51, 0x10, 0x0F, 0xE2, 0x04, 0x08, 0x10, 0x20, 0x40,
0x81, 0x1F, 0xC0, 0x44, 0x88, 0x07, 0xF1, 0x02, 0x04, 0x08, 0x10, 0x20,
0x40, 0x8F, 0xE0, 0x7E, 0x10, 0x88, 0x24, 0x1F, 0xC9, 0x04, 0x82, 0x41,
0x21, 0x3F, 0x00, 0x19, 0x09, 0x80, 0x01, 0x87, 0x30, 0x8C, 0x22, 0x88,
0x92, 0x24, 0x88, 0xA2, 0x28, 0x86, 0x70, 0x80, 0x20, 0x0C, 0x00, 0x00,
0x03, 0xE2, 0x0A, 0x05, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x24, 0x11, 0xF0,
0x04, 0x04, 0x04, 0x00, 0x03, 0xE2, 0x0A, 0x05, 0x01, 0x80, 0xC0, 0x60,
0x30, 0x24, 0x11, 0xF0, 0x08, 0x0A, 0x08, 0x80, 0x03, 0xE2, 0x0A, 0x05,
0x01, 0x80, 0xC0, 0x60, 0x30, 0x24, 0x11, 0xF0, 0x10, 0x17, 0x00, 0x07,
0xC4, 0x14, 0x0A, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x48, 0x23, 0xE0, 0x22,
0x11, 0x00, 0x07, 0xC4, 0x14, 0x0A, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x48,
0x23, 0xE0, 0x84, 0x90, 0xC1, 0x84, 0x90, 0x80, 0x00, 0x9F, 0x90, 0x50,
0x68, 0x4C, 0x46, 0x43, 0x21, 0xA1, 0x20, 0xAD, 0x81, 0x00, 0x20, 0x0C,
0x00, 0x00, 0x0E, 0x3A, 0x09, 0x04, 0x82, 0x41, 0x20, 0x90, 0x48, 0x24,
0x11, 0xF0, 0x04, 0x04, 0x04, 0x00, 0x0E, 0x3A, 0x09, 0x04, 0x82, 0x41,
0x20, 0x90, 0x48, 0x24, 0x11, 0xF0, 0x08, 0x0A, 0x08, 0x80, 0x0E, 0x3A,
0x09, 0x04, 0x82, 0x41, 0x20, 0x90, 0x48, 0x24, 0x11, 0xF0, 0x22, 0x11,
0x00, 0x1C, 0x74, 0x12, 0x09, 0x04, 0x82, 0x41, 0x20, 0x90, 0x48, 0x23,
0xE0, 0x04, 0x04, 0x04, 0x00, 0x0E, 0x3A, 0x10, 0x88, 0x28, 0x18, 0x04,
0x02, 0x01, 0x00, 0x81, 0xF0, 0xF8, 0x40, 0x7C, 0x43, 0x41, 0x41, 0x41,
0x7E, 0x40, 0xF8, 0x1C, 0x11, 0x08, 0x84, 0x42, 0xC1, 0x18, 0x82, 0x41,
0x20, 0x94, 0x79, 0xC0, 0x20, 0x08, 0x02, 0x00, 0x01, 0xC3, 0x10, 0x08,
0x3C, 0x62, 0x41, 0x20, 0x8F, 0xB0, 0x00, 0x06, 0x04, 0x00, 0x01, 0xC3,
0x10, 0x08, 0x3C, 0x62, 0x41, 0x20, 0x8F, 0xB0, 0x18, 0x12, 0x00, 0x03,
0x86, 0x20, 0x10, 0x78, 0xC4, 0x82, 0x41, 0x1F, 0x60, 0x3E, 0x00, 0x07,
0x0C, 0x40, 0x20, 0xF1, 0x89, 0x04, 0x82, 0x3E, 0xC0, 0x22, 0x33, 0x00,
0x03, 0x86, 0x20, 0x10, 0x78, 0xC4, 0x82, 0x41, 0x1F, 0x60, 0x1C, 0x12,
0x05, 0x01, 0x01, 0xC3, 0x10, 0x08, 0x3C, 0x62, 0x41, 0x20, 0x8F, 0xB0,
0x39, 0x91, 0x90, 0x44, 0xF1, 0x47, 0xA1, 0x08, 0x41, 0xFF, 0x38, 0x46,
0x82, 0x80, 0x80, 0x80, 0x81, 0x6E, 0x10, 0x08, 0x38, 0x00, 0x10, 0x08,
0x00, 0x1C, 0x62, 0x81, 0x81, 0xFE, 0x80, 0x40, 0x3F, 0x00, 0x04, 0x18,
0x00, 0x1C, 0x62, 0x81, 0x81, 0xFE, 0x80, 0x40, 0x3F, 0x08, 0x36, 0x00,
0x1C, 0x62, 0x81, 0x81, 0xFE, 0x80, 0x40, 0x3F, 0x22, 0x22, 0x00, 0x1C,
0x62, 0x81, 0x81, 0xFE, 0x80, 0x40, 0x3F, 0x40, 0x40, 0x40, 0x07, 0x02,
0x04, 0x08, 0x10, 0x20, 0x47, 0xF0, 0x00, 0x30, 0x80, 0x07, 0x02, 0x04,
0x08, 0x10, 0x20, 0x47, 0xF0, 0x30, 0x90, 0x03, 0x81, 0x02, 0x04, 0x08,
0x10, 0x23, 0xF8, 0x45, 0x98, 0x03, 0x81, 0x02, 0x04, 0x08, 0x10, 0x23,
0xF8, 0x36, 0x0E, 0x00, 0x83, 0xC6, 0x32, 0x0A, 0x05, 0x02, 0x81, 0x20,
0x8F, 0x80, 0x3E, 0x00, 0x17, 0x0C, 0x44, 0x12, 0x09, 0x04, 0x82, 0x41,
0x71, 0xC0, 0x00, 0x08, 0x02, 0x00, 0x01, 0xC3, 0x11, 0x05, 0x02, 0x81,
0x40, 0x90, 0x47, 0xC0, 0x00, 0x02, 0x06, 0x00, 0x01, 0xC3, 0x11, 0x05,
0x02, 0x81, 0x40, 0x90, 0x47, 0xC0, 0x18, 0x13, 0x00, 0x03, 0x86, 0x22,
0x0A, 0x05, 0x02, 0x81, 0x20, 0x8F, 0x80, 0x3E, 0x00, 0x07, 0x0C, 0x44,
0x14, 0x0A, 0x05, 0x02, 0x41, 0x1F, 0x00, 0x22, 0x11, 0x00, 0x03, 0x86,
0x22, 0x0A, 0x05, 0x02, 0x81, 0x20, 0x8F, 0x80, 0x08, 0x04, 0x00, 0x00,
0x00, 0x03, 0xF8, 0x00, 0x00, 0x08, 0x04, 0x00, 0x1D, 0x31, 0x10, 0xD0,
0xA8, 0x94, 0x89, 0x84, 0xEC, 0x88, 0x00, 0x00, 0x08, 0x02, 0x00, 0x0C,
0x32, 0x09, 0x04, 0x82, 0x41, 0x20, 0x90, 0xC7, 0xB0, 0x04, 0x04, 0x04,
0x00, 0x0C, 0x32, 0x09, 0x04, 0x82, 0x41, 0x20, 0x90, 0xC7, 0xB0, 0x18,
0x12, 0x00, 0x18, 0x64, 0x12, 0x09, 0x04, 0x82, 0x41, 0x21, 0x8F, 0x60,
0x22, 0x33, 0x00, 0x18, 0x64, 0x12, 0x09, 0x04, 0x82, 0x41, 0x21, 0x8F,
0x60, 0x02, 0x02, 0x02, 0x00, 0x0C, 0x32, 0x09, 0x04, 0x44, 0x22, 0x0A,
0x05, 0x01, 0x00, 0x80, 0x81, 0xE0, 0xE0, 0x08, 0x02, 0x00, 0xB8, 0x31,
0x88, 0x22, 0x04, 0x81, 0x20, 0x8C, 0x22, 0xF0, 0x80, 0x20, 0x1C, 0x00,
0x22, 0x11, 0x00, 0x18, 0x64, 0x12, 0x08, 0x88, 0x44, 0x14, 0x0A, 0x02,
0x01, 0x01, 0x03, 0xC0 };
const GFXglyph FreeMono9pt8bGlyphs[] PROGMEM = {
{ 0, 1, 1, 11, 0, 0 }, // 0x20 ' ' U+0020
{ 1, 2, 11, 11, 4, -10 }, // 0x21 '!' U+0021
{ 4, 5, 5, 11, 3, -10 }, // 0x22 '"' U+0022
{ 8, 7, 13, 11, 2, -11 }, // 0x23 '#' U+0023
{ 20, 7, 14, 11, 2, -11 }, // 0x24 '$' U+0024
{ 33, 7, 11, 11, 2, -10 }, // 0x25 '%' U+0025
{ 43, 7, 9, 11, 2, -8 }, // 0x26 '&' U+0026
{ 51, 3, 5, 11, 4, -10 }, // 0x27 ''' U+0027
{ 53, 3, 13, 11, 5, -10 }, // 0x28 '(' U+0028
{ 58, 3, 13, 11, 3, -10 }, // 0x29 ')' U+0029
{ 63, 7, 7, 11, 2, -10 }, // 0x2a '*' U+002A
{ 70, 9, 9, 11, 1, -9 }, // 0x2b '+' U+002B
{ 81, 4, 6, 11, 2, -2 }, // 0x2c ',' U+002C
{ 84, 9, 1, 11, 1, -5 }, // 0x2d '-' U+002D
{ 86, 3, 2, 11, 4, -1 }, // 0x2e '.' U+002E
{ 87, 7, 13, 11, 2, -11 }, // 0x2f '/' U+002F
{ 99, 7, 11, 11, 2, -10 }, // 0x30 '0' U+0030
{ 109, 7, 11, 11, 2, -10 }, // 0x31 '1' U+0031
{ 119, 7, 11, 11, 2, -10 }, // 0x32 '2' U+0032
{ 129, 7, 11, 11, 2, -10 }, // 0x33 '3' U+0033
{ 139, 7, 11, 11, 2, -10 }, // 0x34 '4' U+0034
{ 149, 7, 11, 11, 2, -10 }, // 0x35 '5' U+0035
{ 159, 7, 11, 11, 2, -10 }, // 0x36 '6' U+0036
{ 169, 7, 11, 11, 2, -10 }, // 0x37 '7' U+0037
{ 179, 7, 11, 11, 2, -10 }, // 0x38 '8' U+0038
{ 189, 7, 11, 11, 2, -10 }, // 0x39 '9' U+0039
{ 199, 3, 8, 11, 4, -7 }, // 0x3a ':' U+003A
{ 202, 4, 11, 11, 2, -7 }, // 0x3b ';' U+003B
{ 208, 8, 8, 11, 1, -8 }, // 0x3c '<' U+003C
{ 216, 9, 4, 11, 1, -6 }, // 0x3d '=' U+003D
{ 221, 9, 8, 11, 1, -8 }, // 0x3e '>' U+003E
{ 230, 7, 10, 11, 2, -9 }, // 0x3f '?' U+003F
{ 239, 7, 12, 11, 2, -10 }, // 0x40 '@' U+0040
{ 250, 11, 10, 11, 0, -9 }, // 0x41 'A' U+0041
{ 264, 9, 10, 11, 1, -9 }, // 0x42 'B' U+0042
{ 276, 9, 10, 11, 1, -9 }, // 0x43 'C' U+0043
{ 288, 8, 10, 11, 1, -9 }, // 0x44 'D' U+0044
{ 298, 8, 10, 11, 1, -9 }, // 0x45 'E' U+0045
{ 308, 8, 10, 11, 1, -9 }, // 0x46 'F' U+0046
{ 318, 9, 10, 11, 1, -9 }, // 0x47 'G' U+0047
{ 330, 9, 10, 11, 1, -9 }, // 0x48 'H' U+0048
{ 342, 7, 10, 11, 2, -9 }, // 0x49 'I' U+0049
{ 351, 9, 10, 11, 2, -9 }, // 0x4a 'J' U+004A
{ 363, 9, 10, 11, 1, -9 }, // 0x4b 'K' U+004B
{ 375, 9, 10, 11, 1, -9 }, // 0x4c 'L' U+004C
{ 387, 11, 10, 11, 0, -9 }, // 0x4d 'M' U+004D
{ 401, 10, 10, 11, 0, -9 }, // 0x4e 'N' U+004E
{ 414, 9, 10, 11, 1, -9 }, // 0x4f 'O' U+004F
{ 426, 8, 10, 11, 1, -9 }, // 0x50 'P' U+0050
{ 436, 9, 12, 11, 1, -9 }, // 0x51 'Q' U+0051
{ 450, 10, 10, 11, 1, -9 }, // 0x52 'R' U+0052
{ 463, 7, 10, 11, 2, -9 }, // 0x53 'S' U+0053
{ 472, 9, 10, 11, 1, -9 }, // 0x54 'T' U+0054
{ 484, 9, 10, 11, 1, -9 }, // 0x55 'U' U+0055
{ 496, 11, 10, 11, 0, -9 }, // 0x56 'V' U+0056
{ 510, 10, 10, 11, 0, -9 }, // 0x57 'W' U+0057
{ 523, 9, 10, 11, 1, -9 }, // 0x58 'X' U+0058
{ 535, 9, 10, 11, 1, -9 }, // 0x59 'Y' U+0059
{ 547, 7, 10, 11, 2, -9 }, // 0x5a 'Z' U+005A
{ 556, 3, 13, 11, 5, -10 }, // 0x5b '[' U+005B
{ 561, 7, 13, 11, 2, -11 }, // 0x5c '\' U+005C
{ 573, 3, 13, 11, 3, -10 }, // 0x5d ']' U+005D
{ 578, 7, 5, 11, 2, -10 }, // 0x5e '^' U+005E
{ 583, 11, 1, 11, 0, 2 }, // 0x5f '_' U+005F
{ 585, 3, 3, 11, 3, -11 }, // 0x60 '`' U+0060
{ 587, 9, 8, 11, 1, -7 }, // 0x61 'a' U+0061
{ 596, 10, 11, 11, 0, -10 }, // 0x62 'b' U+0062
{ 610, 8, 8, 11, 2, -7 }, // 0x63 'c' U+0063
{ 618, 10, 11, 11, 1, -10 }, // 0x64 'd' U+0064
{ 632, 8, 8, 11, 1, -7 }, // 0x65 'e' U+0065
{ 640, 8, 11, 11, 2, -10 }, // 0x66 'f' U+0066
{ 651, 9, 11, 11, 1, -7 }, // 0x67 'g' U+0067
{ 664, 9, 11, 11, 1, -10 }, // 0x68 'h' U+0068
{ 677, 7, 11, 11, 2, -10 }, // 0x69 'i' U+0069
{ 687, 5, 14, 11, 3, -10 }, // 0x6a 'j' U+006A
{ 696, 9, 11, 11, 1, -10 }, // 0x6b 'k' U+006B
{ 709, 7, 11, 11, 2, -10 }, // 0x6c 'l' U+006C
{ 719, 11, 8, 11, 0, -7 }, // 0x6d 'm' U+006D
{ 730, 9, 8, 11, 1, -7 }, // 0x6e 'n' U+006E
{ 739, 9, 8, 11, 1, -7 }, // 0x6f 'o' U+006F
{ 748, 10, 11, 11, 0, -7 }, // 0x70 'p' U+0070
{ 762, 10, 11, 11, 1, -7 }, // 0x71 'q' U+0071
{ 776, 8, 8, 11, 2, -7 }, // 0x72 'r' U+0072
{ 784, 7, 8, 11, 2, -7 }, // 0x73 's' U+0073
{ 791, 8, 10, 11, 1, -9 }, // 0x74 't' U+0074
{ 801, 9, 8, 11, 1, -7 }, // 0x75 'u' U+0075
{ 810, 9, 8, 11, 1, -7 }, // 0x76 'v' U+0076
{ 819, 9, 8, 11, 1, -7 }, // 0x77 'w' U+0077
{ 828, 9, 8, 11, 1, -7 }, // 0x78 'x' U+0078
{ 837, 9, 11, 11, 1, -7 }, // 0x79 'y' U+0079
{ 850, 7, 8, 11, 2, -7 }, // 0x7a 'z' U+007A
{ 857, 4, 13, 11, 3, -10 }, // 0x7b '{' U+007B
{ 864, 1, 13, 11, 5, -10 }, // 0x7c '|' U+007C
{ 866, 5, 13, 11, 3, -10 }, // 0x7d '}' U+007D
{ 875, 7, 2, 11, 2, -5 }, // 0x7e '~' U+007E
{ 877, 11, 14, 11, 0, -11 }, // 0x7f 'REPLACEMENT CHARACTER *' U+2370
{ 897, 1, 1, 11, 0, 0 }, // 0x80 'NO-BREAK SPACE' U+00A0
{ 898, 2, 11, 11, 4, -7 }, // 0x81 'INVERTED EXCLAMATION MARK' U+00A1
{ 901, 6, 11, 11, 2, -10 }, // 0x82 'CENT SIGN' U+00A2
{ 910, 8, 10, 11, 1, -9 }, // 0x83 'POUND SIGN' U+00A3
{ 920, 9, 10, 11, 1, -9 }, // 0x84 'EURO SIGN *' U+20AC
{ 932, 9, 10, 11, 1, -9 }, // 0x85 'YEN SIGN' U+00A5
{ 944, 7, 13, 11, 2, -12 }, // 0x86 'LATIN CAPITAL LETTER S WITH CARON *' U+0160
{ 956, 9, 12, 11, 1, -10 }, // 0x87 'SECTION SIGN' U+00A7
{ 970, 7, 12, 11, 2, -11 }, // 0x88 'LATIN SMALL LETTER S WITH CARON *' U+0161
{ 981, 11, 10, 11, 0, -9 }, // 0x89 'COPYRIGHT SIGN' U+00A9
{ 995, 5, 5, 11, 3, -9 }, // 0x8a 'FEMININE ORDINAL INDICATOR' U+00AA
{ 999, 9, 8, 11, 1, -7 }, // 0x8b 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00AB
{ 1008, 9, 5, 11, 1, -7 }, // 0x8c 'NOT SIGN' U+00AC
{ 1014, 9, 1, 11, 1, -5 }, // 0x8d 'SOFT HYPHEN' U+00AD
{ 1016, 11, 10, 11, 0, -9 }, // 0x8e 'REGISTERED SIGN' U+00AE
{ 1030, 5, 1, 11, 3, -10 }, // 0x8f 'MACRON' U+00AF
{ 1031, 5, 5, 11, 3, -10 }, // 0x90 'DEGREE SIGN' U+00B0
{ 1035, 9, 10, 11, 1, -9 }, // 0x91 'PLUS-MINUS SIGN' U+00B1
{ 1047, 4, 6, 11, 3, -10 }, // 0x92 'SUPERSCRIPT TWO' U+00B2
{ 1050, 4, 6, 11, 3, -10 }, // 0x93 'SUPERSCRIPT THREE' U+00B3
{ 1053, 7, 14, 11, 2, -13 }, // 0x94 'LATIN CAPITAL LETTER Z WITH CARON *' U+017D
{ 1066, 9, 12, 11, 1, -7 }, // 0x95 'MICRO SIGN' U+00B5
{ 1080, 8, 12, 11, 1, -10 }, // 0x96 'PILCROW SIGN' U+00B6
{ 1092, 2, 2, 11, 4, -4 }, // 0x97 'MIDDLE DOT' U+00B7
{ 1093, 7, 12, 11, 2, -11 }, // 0x98 'LATIN SMALL LETTER Z WITH CARON *' U+017E
{ 1104, 4, 6, 11, 3, -10 }, // 0x99 'SUPERSCRIPT ONE' U+00B9
{ 1107, 5, 5, 11, 3, -9 }, // 0x9a 'MASCULINE ORDINAL INDICATOR' U+00BA
{ 1111, 9, 8, 11, 1, -7 }, // 0x9b 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00BB
{ 1120, 11, 10, 11, 0, -9 }, // 0x9c 'LATIN CAPITAL LIGATURE OE *' U+0152
{ 1134, 10, 8, 11, 0, -7 }, // 0x9d 'LATIN SMALL LIGATURE OE *' U+0153
{ 1144, 9, 13, 11, 1, -12 }, // 0x9e 'LATIN CAPITAL LETTER Y WITH DIAERESIS *' U+0178
{ 1159, 6, 11, 11, 2, -7 }, // 0x9f 'INVERTED QUESTION MARK' U+00BF
{ 1168, 11, 14, 11, 0, -13 }, // 0xa0 'LATIN CAPITAL LETTER A WITH GRAVE' U+00C0
{ 1188, 11, 14, 11, 0, -13 }, // 0xa1 'LATIN CAPITAL LETTER A WITH ACUTE' U+00C1
{ 1208, 11, 14, 11, 0, -13 }, // 0xa2 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' U+00C2
{ 1228, 11, 13, 11, 0, -12 }, // 0xa3 'LATIN CAPITAL LETTER A WITH TILDE' U+00C3
{ 1246, 11, 13, 11, 0, -12 }, // 0xa4 'LATIN CAPITAL LETTER A WITH DIAERESIS' U+00C4
{ 1264, 11, 14, 11, 0, -13 }, // 0xa5 'LATIN CAPITAL LETTER A WITH RING ABOVE' U+00C5
{ 1284, 11, 10, 11, 0, -9 }, // 0xa6 'LATIN CAPITAL LETTER AE' U+00C6
{ 1298, 9, 13, 11, 1, -9 }, // 0xa7 'LATIN CAPITAL LETTER C WITH CEDILLA' U+00C7
{ 1313, 8, 14, 11, 1, -13 }, // 0xa8 'LATIN CAPITAL LETTER E WITH GRAVE' U+00C8
{ 1327, 8, 14, 11, 1, -13 }, // 0xa9 'LATIN CAPITAL LETTER E WITH ACUTE' U+00C9
{ 1341, 8, 14, 11, 1, -13 }, // 0xaa 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' U+00CA
{ 1355, 8, 13, 11, 1, -12 }, // 0xab 'LATIN CAPITAL LETTER E WITH DIAERESIS' U+00CB
{ 1368, 7, 14, 11, 2, -13 }, // 0xac 'LATIN CAPITAL LETTER I WITH GRAVE' U+00CC
{ 1381, 7, 14, 11, 2, -13 }, // 0xad 'LATIN CAPITAL LETTER I WITH ACUTE' U+00CD
{ 1394, 7, 14, 11, 2, -13 }, // 0xae 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' U+00CE
{ 1407, 7, 13, 11, 2, -12 }, // 0xaf 'LATIN CAPITAL LETTER I WITH DIAERESIS' U+00CF
{ 1419, 9, 10, 11, 0, -9 }, // 0xb0 'LATIN CAPITAL LETTER ETH' U+00D0
{ 1431, 10, 13, 11, 0, -12 }, // 0xb1 'LATIN CAPITAL LETTER N WITH TILDE' U+00D1
{ 1448, 9, 14, 11, 1, -13 }, // 0xb2 'LATIN CAPITAL LETTER O WITH GRAVE' U+00D2
{ 1464, 9, 14, 11, 1, -13 }, // 0xb3 'LATIN CAPITAL LETTER O WITH ACUTE' U+00D3
{ 1480, 9, 14, 11, 1, -13 }, // 0xb4 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' U+00D4
{ 1496, 9, 13, 11, 1, -12 }, // 0xb5 'LATIN CAPITAL LETTER O WITH TILDE' U+00D5
{ 1511, 9, 13, 11, 1, -12 }, // 0xb6 'LATIN CAPITAL LETTER O WITH DIAERESIS' U+00D6
{ 1526, 7, 6, 11, 2, -7 }, // 0xb7 'MULTIPLICATION SIGN' U+00D7
{ 1532, 9, 12, 11, 1, -10 }, // 0xb8 'LATIN CAPITAL LETTER O WITH STROKE' U+00D8
{ 1546, 9, 14, 11, 1, -13 }, // 0xb9 'LATIN CAPITAL LETTER U WITH GRAVE' U+00D9
{ 1562, 9, 14, 11, 1, -13 }, // 0xba 'LATIN CAPITAL LETTER U WITH ACUTE' U+00DA
{ 1578, 9, 14, 11, 1, -13 }, // 0xbb 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' U+00DB
{ 1594, 9, 13, 11, 1, -12 }, // 0xbc 'LATIN CAPITAL LETTER U WITH DIAERESIS' U+00DC
{ 1609, 9, 14, 11, 1, -13 }, // 0xbd 'LATIN CAPITAL LETTER Y WITH ACUTE' U+00DD
{ 1625, 8, 10, 11, 1, -9 }, // 0xbe 'LATIN CAPITAL LETTER THORN' U+00DE
{ 1635, 9, 11, 11, 0, -10 }, // 0xbf 'LATIN SMALL LETTER SHARP S' U+00DF
{ 1648, 9, 12, 11, 1, -11 }, // 0xc0 'LATIN SMALL LETTER A WITH GRAVE' U+00E0
{ 1662, 9, 12, 11, 1, -11 }, // 0xc1 'LATIN SMALL LETTER A WITH ACUTE' U+00E1
{ 1676, 9, 11, 11, 1, -10 }, // 0xc2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' U+00E2
{ 1689, 9, 10, 11, 1, -9 }, // 0xc3 'LATIN SMALL LETTER A WITH TILDE' U+00E3
{ 1701, 9, 11, 11, 1, -10 }, // 0xc4 'LATIN SMALL LETTER A WITH DIAERESIS' U+00E4
{ 1714, 9, 12, 11, 1, -11 }, // 0xc5 'LATIN SMALL LETTER A WITH RING ABOVE' U+00E5
{ 1728, 10, 8, 11, 0, -7 }, // 0xc6 'LATIN SMALL LETTER AE' U+00E6
{ 1738, 8, 11, 11, 2, -7 }, // 0xc7 'LATIN SMALL LETTER C WITH CEDILLA' U+00E7
{ 1749, 8, 12, 11, 1, -11 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8
{ 1761, 8, 12, 11, 1, -11 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9
{ 1773, 8, 11, 11, 1, -10 }, // 0xca 'LATIN SMALL LETTER E WITH CIRCUMFLEX' U+00EA
{ 1784, 8, 11, 11, 1, -10 }, // 0xcb 'LATIN SMALL LETTER E WITH DIAERESIS' U+00EB
{ 1795, 7, 12, 11, 2, -11 }, // 0xcc 'LATIN SMALL LETTER I WITH GRAVE' U+00EC
{ 1806, 7, 12, 11, 2, -11 }, // 0xcd 'LATIN SMALL LETTER I WITH ACUTE' U+00ED
{ 1817, 7, 11, 11, 2, -10 }, // 0xce 'LATIN SMALL LETTER I WITH CIRCUMFLEX' U+00EE
{ 1827, 7, 11, 11, 2, -10 }, // 0xcf 'LATIN SMALL LETTER I WITH DIAERESIS' U+00EF
{ 1837, 9, 11, 11, 1, -10 }, // 0xd0 'LATIN SMALL LETTER ETH' U+00F0
{ 1850, 9, 10, 11, 1, -9 }, // 0xd1 'LATIN SMALL LETTER N WITH TILDE' U+00F1
{ 1862, 9, 12, 11, 1, -11 }, // 0xd2 'LATIN SMALL LETTER O WITH GRAVE' U+00F2
{ 1876, 9, 12, 11, 1, -11 }, // 0xd3 'LATIN SMALL LETTER O WITH ACUTE' U+00F3
{ 1890, 9, 11, 11, 1, -10 }, // 0xd4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' U+00F4
{ 1903, 9, 10, 11, 1, -9 }, // 0xd5 'LATIN SMALL LETTER O WITH TILDE' U+00F5
{ 1915, 9, 11, 11, 1, -10 }, // 0xd6 'LATIN SMALL LETTER O WITH DIAERESIS' U+00F6
{ 1928, 9, 10, 11, 1, -9 }, // 0xd7 'DIVISION SIGN' U+00F7
{ 1940, 9, 9, 11, 1, -7 }, // 0xd8 'LATIN SMALL LETTER O WITH STROKE' U+00F8
{ 1951, 9, 12, 11, 1, -11 }, // 0xd9 'LATIN SMALL LETTER U WITH GRAVE' U+00F9
{ 1965, 9, 12, 11, 1, -11 }, // 0xda 'LATIN SMALL LETTER U WITH ACUTE' U+00FA
{ 1979, 9, 11, 11, 1, -10 }, // 0xdb 'LATIN SMALL LETTER U WITH CIRCUMFLEX' U+00FB
{ 1992, 9, 11, 11, 1, -10 }, // 0xdc 'LATIN SMALL LETTER U WITH DIAERESIS' U+00FC
{ 2005, 9, 15, 11, 1, -11 }, // 0xdd 'LATIN SMALL LETTER Y WITH ACUTE' U+00FD
{ 2022, 10, 14, 11, 0, -10 }, // 0xde 'LATIN SMALL LETTER THORN' U+00FE
{ 2040, 9, 14, 11, 1, -10 } }; // 0xdf 'LATIN SMALL LETTER Y WITH DIAERESIS' U+000FF
const GFXfont FreeMono9pt8b PROGMEM = {
(uint8_t *)FreeMono9pt8bBitmaps,
(GFXglyph *)FreeMono9pt8bGlyphs,
0x20, 0xDF, 19 };
// Approx. 3407 bytes

View File

View File

@ -0,0 +1,596 @@
const uint8_t FreeSans12pt8bBitmaps[] PROGMEM = {
0x00, 0xFF, 0xFF, 0xFF, 0xF3, 0xF0, 0xCF, 0x3C, 0xF3, 0x4D, 0x10, 0x06,
0x30, 0x31, 0x01, 0x18, 0x18, 0xC0, 0xC6, 0x3F, 0xFD, 0xFF, 0xE1, 0x98,
0x18, 0xC0, 0xC6, 0x06, 0x31, 0xFF, 0xE1, 0x88, 0x08, 0xC0, 0xC6, 0x06,
0x30, 0x31, 0x80, 0x04, 0x03, 0xF1, 0xFF, 0x32, 0x7C, 0x47, 0x88, 0xF1,
0x07, 0x20, 0x7C, 0x07, 0xF0, 0x1F, 0x02, 0x70, 0x47, 0x88, 0xF1, 0x1E,
0x23, 0x64, 0xE7, 0xF8, 0x10, 0x02, 0x00, 0x40, 0x00, 0x06, 0x07, 0xC0,
0x40, 0xE6, 0x0C, 0x0C, 0x30, 0x80, 0x83, 0x18, 0x0C, 0x31, 0x00, 0xC6,
0x30, 0x07, 0xE2, 0x00, 0x38, 0x60, 0x00, 0x0C, 0x38, 0x00, 0xC7, 0xC0,
0x18, 0xC6, 0x01, 0x18, 0x30, 0x31, 0x83, 0x02, 0x08, 0x20, 0x60, 0xC6,
0x04, 0x07, 0xC0, 0x0F, 0x00, 0x7E, 0x03, 0x8C, 0x0C, 0x30, 0x30, 0xC0,
0x67, 0x01, 0xF8, 0x03, 0xC0, 0x1E, 0x00, 0xCC, 0x66, 0x39, 0xB0, 0x7C,
0xC0, 0xF3, 0x01, 0x8E, 0x0F, 0x1C, 0xFE, 0x3F, 0x9C, 0x10, 0x00, 0xFF,
0x50, 0x08, 0x8C, 0x46, 0x31, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC2,
0x18, 0x43, 0x08, 0x60, 0x86, 0x18, 0xC3, 0x18, 0x43, 0x18, 0xC6, 0x31,
0x8C, 0x63, 0x11, 0x8C, 0x46, 0x23, 0x00, 0x10, 0x22, 0x4F, 0xF3, 0x85,
0x1B, 0x00, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x0F, 0xFF, 0x06,
0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0xFF, 0x97, 0xA0, 0xFC, 0xFF,
0x80, 0x06, 0x0C, 0x10, 0x20, 0xC1, 0x02, 0x0C, 0x10, 0x20, 0xC1, 0x02,
0x0C, 0x10, 0x20, 0xC1, 0x00, 0x1F, 0x07, 0xF1, 0xC7, 0x30, 0x6E, 0x0F,
0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0, 0x3E, 0x06, 0xC1,
0xD8, 0x31, 0xCE, 0x3F, 0x80, 0x80, 0x04, 0x30, 0xFF, 0xFC, 0x30, 0xC3,
0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x1F, 0x07, 0xF9, 0xC3, 0xB0,
0x3C, 0x07, 0x80, 0xC0, 0x18, 0x07, 0x01, 0xC0, 0xF0, 0x78, 0x1C, 0x07,
0x01, 0xC0, 0x30, 0x07, 0xFF, 0xFF, 0xE0, 0x1F, 0x0F, 0xF9, 0x83, 0x70,
0x7C, 0x0E, 0x01, 0xC0, 0x30, 0x7C, 0x0F, 0x80, 0x38, 0x01, 0x80, 0x3C,
0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x80, 0x80, 0x01, 0x80, 0x70, 0x0E,
0x03, 0xC0, 0xD8, 0x33, 0x06, 0x61, 0x8C, 0x61, 0x8C, 0x33, 0x06, 0x7F,
0xFF, 0xFE, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x3F, 0xCF, 0xF9, 0x80,
0x30, 0x06, 0x00, 0xC0, 0x1F, 0xE3, 0xFE, 0xE0, 0xE0, 0x0C, 0x01, 0x80,
0x30, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x80, 0x80, 0x0F, 0x07, 0xF9,
0xC3, 0x30, 0x36, 0x01, 0x80, 0x30, 0x86, 0xFC, 0xF9, 0xDC, 0x1F, 0x81,
0xE0, 0x3C, 0x06, 0xC0, 0xD8, 0x3B, 0xCE, 0x3F, 0x80, 0x80, 0xFF, 0xFF,
0xFC, 0x01, 0x80, 0x60, 0x0C, 0x03, 0x00, 0xC0, 0x18, 0x06, 0x00, 0xC0,
0x30, 0x06, 0x00, 0xC0, 0x30, 0x06, 0x00, 0xC0, 0x38, 0x00, 0x1F, 0x07,
0xF9, 0xC3, 0x30, 0x7E, 0x06, 0xC1, 0xD8, 0x31, 0xFC, 0x3F, 0x8E, 0x1B,
0x81, 0xE0, 0x3C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x3F, 0x80, 0x80, 0x1F,
0x07, 0xF1, 0x87, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x07, 0x60, 0xEF,
0xFC, 0x7D, 0x80, 0x30, 0x07, 0x81, 0x98, 0x33, 0x9C, 0x3F, 0x00, 0x80,
0xFC, 0x00, 0x0F, 0xC0, 0xFC, 0x00, 0x0F, 0xD7, 0x00, 0x00, 0x30, 0x1F,
0x07, 0x81, 0xE0, 0xF8, 0x0E, 0x00, 0x78, 0x01, 0xE0, 0x07, 0xC0, 0x0F,
0x00, 0x30, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xF0, 0xC0, 0x0F,
0x80, 0x1E, 0x00, 0x78, 0x01, 0xF0, 0x03, 0x01, 0xE0, 0x78, 0x3E, 0x0F,
0x00, 0xC0, 0x00, 0x1E, 0x1F, 0xE6, 0x1F, 0x03, 0xC0, 0xF0, 0x30, 0x0C,
0x06, 0x03, 0x01, 0x80, 0xC0, 0x30, 0x0C, 0x00, 0x00, 0x00, 0x30, 0x0C,
0x03, 0x00, 0x00, 0x7C, 0x00, 0x0F, 0xFE, 0x00, 0x70, 0x3C, 0x07, 0x00,
0x38, 0x38, 0x00, 0x70, 0xC0, 0x40, 0xE6, 0x0F, 0xD9, 0x98, 0x71, 0xC7,
0xC3, 0x87, 0x0F, 0x0C, 0x0C, 0x3C, 0x30, 0x70, 0xF1, 0xC1, 0x86, 0xC7,
0x06, 0x1B, 0x1C, 0x38, 0xEC, 0x39, 0xE7, 0x18, 0x7D, 0xF8, 0x70, 0x01,
0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x03, 0xF3, 0xC0, 0x03, 0xFF, 0x00,
0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x06, 0x60, 0x06, 0x60,
0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8,
0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x70, 0x0E, 0x60, 0x06, 0x60, 0x07,
0xFE, 0x07, 0xFF, 0x30, 0x79, 0x80, 0xEC, 0x03, 0x60, 0x1B, 0x00, 0xD8,
0x0C, 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x7C, 0x01, 0xE0, 0x0F, 0x00,
0xF8, 0x0E, 0xFF, 0xF7, 0xFE, 0x00, 0x03, 0xC0, 0x1F, 0xF0, 0xF0, 0xE1,
0x80, 0xE7, 0x00, 0xEC, 0x01, 0xF8, 0x00, 0x70, 0x00, 0xC0, 0x01, 0x80,
0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x6C, 0x01, 0xDC, 0x03, 0x9C, 0x0E,
0x1F, 0x78, 0x1F, 0xE0, 0x04, 0x00, 0xFE, 0x03, 0xFF, 0x8C, 0x1F, 0x30,
0x0E, 0xC0, 0x1B, 0x00, 0x6C, 0x01, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C,
0x00, 0xF0, 0x03, 0xC0, 0x1F, 0x00, 0x6C, 0x03, 0xB0, 0x1C, 0xFF, 0xE3,
0xFF, 0x00, 0xFF, 0xF7, 0xFF, 0xB0, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03,
0x00, 0x18, 0x00, 0xFF, 0xF7, 0xFF, 0xB0, 0x01, 0x80, 0x0C, 0x00, 0x60,
0x03, 0x00, 0x18, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xC0,
0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xEF, 0xFE, 0xC0,
0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x03,
0xE0, 0x0F, 0xF8, 0x1E, 0x3C, 0x38, 0x0E, 0x70, 0x06, 0x60, 0x07, 0xE0,
0x00, 0xC0, 0x00, 0xC0, 0x00, 0xC0, 0x7F, 0xC0, 0x7F, 0xE0, 0x03, 0xE0,
0x03, 0x60, 0x03, 0x70, 0x07, 0x38, 0x0F, 0x1F, 0x7B, 0x0F, 0xF3, 0x00,
0x80, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00,
0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F,
0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFC, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03,
0x00, 0xC0, 0x30, 0x0C, 0x03, 0xE0, 0xF8, 0x3E, 0x0D, 0x87, 0x73, 0x8F,
0xE0, 0x40, 0xC0, 0x1F, 0x00, 0xEC, 0x07, 0x30, 0x38, 0xC1, 0xC3, 0x0E,
0x0C, 0x70, 0x33, 0x80, 0xDE, 0x03, 0xFC, 0x0F, 0x38, 0x38, 0x60, 0xC1,
0xC3, 0x03, 0x8C, 0x06, 0x30, 0x1C, 0xC0, 0x3B, 0x00, 0x60, 0xC0, 0x18,
0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03,
0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xFF, 0xFF, 0xFC, 0xE0,
0x07, 0xE0, 0x07, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xD8, 0x1B, 0xD8,
0x1B, 0xD8, 0x1B, 0xCC, 0x33, 0xCC, 0x33, 0xCC, 0x33, 0xC6, 0x63, 0xC6,
0x63, 0xC6, 0x63, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC1, 0x83, 0xC0,
0x1F, 0x80, 0x7F, 0x01, 0xFC, 0x07, 0xF8, 0x1F, 0x60, 0x7C, 0xC1, 0xF3,
0x87, 0xC6, 0x1F, 0x0C, 0x7C, 0x39, 0xF0, 0x67, 0xC1, 0xDF, 0x03, 0xFC,
0x07, 0xF0, 0x1F, 0xC0, 0x3F, 0x00, 0x70, 0x03, 0xE0, 0x07, 0xFC, 0x0F,
0x8F, 0x07, 0x01, 0xC7, 0x00, 0x73, 0x00, 0x1B, 0x80, 0x0D, 0x80, 0x07,
0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x7E, 0x00, 0x33, 0x00,
0x19, 0xC0, 0x1C, 0x70, 0x1C, 0x1F, 0x7C, 0x07, 0xFC, 0x00, 0x20, 0x00,
0xFE, 0x07, 0xFF, 0x30, 0x7D, 0x80, 0x6C, 0x03, 0xE0, 0x1F, 0x00, 0xF8,
0x07, 0xC0, 0xF7, 0xFF, 0x3F, 0xC1, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00,
0x18, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x03, 0xE0, 0x07, 0xFC, 0x0F, 0x8F,
0x07, 0x01, 0xC7, 0x00, 0x73, 0x00, 0x1B, 0x80, 0x0D, 0x80, 0x07, 0xC0,
0x01, 0xE0, 0x00, 0xF0, 0x00, 0xF8, 0x00, 0x7E, 0x00, 0x33, 0x01, 0x19,
0xC0, 0xDC, 0x70, 0x3C, 0x1F, 0x7E, 0x07, 0xFF, 0x80, 0x20, 0xC0, 0xFF,
0x03, 0xFF, 0xCC, 0x0F, 0xB0, 0x06, 0xC0, 0x1F, 0x00, 0x7C, 0x01, 0xB0,
0x06, 0xC0, 0xF3, 0xFF, 0x8C, 0x0F, 0x30, 0x06, 0xC0, 0x1B, 0x00, 0x6C,
0x01, 0xB0, 0x06, 0xC0, 0x1F, 0x00, 0x70, 0x07, 0x80, 0xFF, 0x87, 0x87,
0x18, 0x0E, 0x60, 0x19, 0x80, 0x66, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F,
0xC0, 0x0F, 0x80, 0x07, 0xC0, 0x1F, 0x00, 0x3E, 0x01, 0xDC, 0x06, 0x7E,
0xF8, 0x7F, 0x80, 0x20, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x38, 0x00, 0xE0,
0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38,
0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E,
0x00, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00,
0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F,
0x80, 0x6C, 0x07, 0x7D, 0xF1, 0xFF, 0x00, 0x80, 0xC0, 0x0F, 0x00, 0x3E,
0x01, 0xD8, 0x06, 0x70, 0x18, 0xC0, 0xE3, 0x03, 0x0E, 0x0C, 0x18, 0x70,
0x61, 0x81, 0xC6, 0x03, 0x38, 0x0C, 0xC0, 0x3B, 0x00, 0x6C, 0x01, 0xE0,
0x07, 0x80, 0x0E, 0x00, 0xC0, 0x70, 0x1E, 0x03, 0x80, 0xF0, 0x1C, 0x0F,
0xC1, 0xE0, 0x76, 0x0D, 0x83, 0x30, 0x6C, 0x19, 0x83, 0x61, 0xCE, 0x33,
0x8E, 0x31, 0x8C, 0x61, 0x8C, 0x63, 0x0C, 0xE3, 0x18, 0x76, 0x0D, 0xC1,
0xB0, 0x6C, 0x0D, 0x83, 0x60, 0x78, 0x1F, 0x03, 0xC0, 0x78, 0x0E, 0x03,
0x80, 0x70, 0x1C, 0x00, 0xE0, 0x0C, 0xC0, 0x39, 0xC0, 0xE1, 0xC1, 0x81,
0x87, 0x03, 0x9C, 0x03, 0xB0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x1E,
0x00, 0x76, 0x01, 0xCE, 0x03, 0x0C, 0x0E, 0x0C, 0x38, 0x1C, 0x60, 0x1D,
0xC0, 0x38, 0xE0, 0x07, 0x70, 0x06, 0x30, 0x0E, 0x38, 0x1C, 0x18, 0x18,
0x1C, 0x38, 0x0E, 0x30, 0x06, 0x70, 0x07, 0x60, 0x03, 0xC0, 0x01, 0xC0,
0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
0x01, 0x80, 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0xE0, 0x06, 0x00, 0x70,
0x07, 0x00, 0x70, 0x07, 0x00, 0x38, 0x03, 0x80, 0x38, 0x03, 0x80, 0x18,
0x01, 0xC0, 0x1C, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0, 0x81, 0x03, 0x02, 0x04,
0x0C, 0x08, 0x10, 0x30, 0x20, 0x40, 0xC0, 0x81, 0x03, 0x02, 0x04, 0x0C,
0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0,
0x0C, 0x0E, 0x05, 0x86, 0xC3, 0x21, 0x19, 0x8C, 0x83, 0xC1, 0x80, 0xFF,
0xFE, 0xC3, 0x0C, 0x20, 0x1F, 0x07, 0xFC, 0x60, 0xC6, 0x0C, 0x00, 0xC0,
0x1C, 0x3F, 0xC7, 0x8C, 0xE0, 0xCC, 0x0C, 0xC0, 0xCE, 0x3E, 0x7E, 0x70,
0x02, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xF0, 0xDF, 0xCF,
0x0E, 0xE0, 0x6E, 0x06, 0xC0, 0x6C, 0x07, 0xC0, 0x7C, 0x06, 0xE0, 0x6E,
0x0E, 0xF9, 0xCD, 0xF8, 0x00, 0x00, 0x1F, 0x0F, 0xE6, 0x1F, 0x83, 0xC0,
0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x3E, 0x0D, 0xC7, 0x3F, 0x81, 0x00,
0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x3C, 0xDF, 0xDB, 0x8F, 0xE0,
0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3F, 0x07, 0x7B, 0xE7,
0xEC, 0x10, 0x00, 0x0F, 0x07, 0xF9, 0xC3, 0x30, 0x3C, 0x07, 0x80, 0xFF,
0xFE, 0x00, 0xC0, 0x18, 0x0D, 0x83, 0xBC, 0xE3, 0xF8, 0x08, 0x00, 0x04,
0x73, 0x0C, 0x33, 0xFF, 0xCC, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30,
0xC0, 0x1E, 0x67, 0xED, 0x87, 0xF0, 0x7C, 0x0F, 0x80, 0xF0, 0x1E, 0x03,
0xC0, 0x78, 0x1F, 0x83, 0xB9, 0xF3, 0xF6, 0x00, 0xF0, 0x37, 0x06, 0x7F,
0xC7, 0xE0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xCF, 0xFB, 0x87,
0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0,
0x30, 0xFC, 0x3F, 0xFF, 0xFF, 0xF0, 0x33, 0x30, 0x03, 0x33, 0x33, 0x33,
0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C,
0x01, 0x81, 0xB0, 0x66, 0x18, 0xC6, 0x19, 0x83, 0x70, 0x7B, 0x0E, 0x71,
0x86, 0x30, 0xE6, 0x0C, 0xC0, 0xD8, 0x1C, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0,
0xDE, 0x1E, 0xFF, 0x7F, 0xE3, 0xC3, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83,
0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83,
0xC1, 0x83, 0xCF, 0x3F, 0xEE, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03,
0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, 0x1F, 0x07, 0xF1, 0xC7, 0x70,
0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x0D, 0x83, 0xBD, 0xE3,
0xF8, 0x08, 0x00, 0xCF, 0x0D, 0xFC, 0xF0, 0xCE, 0x06, 0xE0, 0x6C, 0x06,
0xC0, 0x7C, 0x07, 0xC0, 0x6E, 0x06, 0xE0, 0xEF, 0x9C, 0xDF, 0x8C, 0x00,
0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x1E, 0x6F, 0xFD, 0xC7, 0xF0, 0x7C,
0x07, 0x80, 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x1F, 0x83, 0xBD, 0xF3, 0xF6,
0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0C, 0xDF, 0xFF, 0x30, 0xC3, 0x0C,
0x30, 0xC3, 0x0C, 0x30, 0xC0, 0x1E, 0x1F, 0xEE, 0x1B, 0x06, 0xC0, 0x3C,
0x07, 0xF0, 0x3E, 0x01, 0xF0, 0x3C, 0x0F, 0xCE, 0x7F, 0x82, 0x00, 0x30,
0xC3, 0x3F, 0xFC, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0F, 0x08,
0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC1, 0xF0,
0x7C, 0x1F, 0x8F, 0x7E, 0xC4, 0x00, 0xE0, 0x76, 0x06, 0x60, 0x67, 0x0C,
0x30, 0xC3, 0x0C, 0x39, 0x81, 0x98, 0x19, 0x81, 0xB0, 0x0F, 0x00, 0xE0,
0x0E, 0x00, 0xC1, 0xC1, 0xF0, 0xE1, 0xD8, 0x70, 0xCC, 0x2C, 0x66, 0x36,
0x31, 0x9B, 0x38, 0xCD, 0x98, 0x64, 0x6C, 0x36, 0x36, 0x0F, 0x1E, 0x07,
0x8F, 0x03, 0x83, 0x80, 0xC1, 0x80, 0x60, 0x6E, 0x1C, 0xC3, 0x0C, 0xC1,
0xF0, 0x1E, 0x01, 0x80, 0x78, 0x0F, 0x03, 0x30, 0xC7, 0x38, 0x66, 0x06,
0x60, 0x6C, 0x0D, 0x81, 0x98, 0x63, 0x0C, 0x63, 0x86, 0x60, 0xCC, 0x1B,
0x81, 0xE0, 0x3C, 0x07, 0x00, 0x60, 0x0C, 0x03, 0x00, 0x60, 0x78, 0x0F,
0x00, 0xFF, 0xFF, 0xF0, 0x18, 0x0E, 0x07, 0x03, 0x80, 0xC0, 0x60, 0x30,
0x1C, 0x0E, 0x03, 0xFF, 0xFF, 0xC0, 0x04, 0xF3, 0x0C, 0x30, 0xC3, 0x0C,
0x30, 0xC6, 0x30, 0xE1, 0x83, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xE1, 0xC0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x87, 0x18, 0x42, 0x10, 0x84, 0x21,
0x8C, 0x31, 0x98, 0xC4, 0x21, 0x08, 0x42, 0x73, 0x80, 0x00, 0x3E, 0x1D,
0xC6, 0x1F, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0x80, 0x3C, 0x01,
0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C,
0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0xFC,
0xFF, 0xFF, 0xFF, 0xF0, 0x04, 0x00, 0x80, 0x3C, 0x1F, 0xE7, 0x4E, 0xC8,
0xD9, 0x1E, 0x20, 0xC4, 0x18, 0x83, 0x10, 0x32, 0x36, 0x46, 0x7B, 0x87,
0xE0, 0x30, 0x04, 0x00, 0x80, 0x00, 0x01, 0xF8, 0x79, 0xC6, 0x0E, 0xE0,
0x6E, 0x06, 0xE0, 0x06, 0x00, 0x70, 0x0F, 0xF0, 0x38, 0x01, 0x80, 0x18,
0x01, 0x00, 0x30, 0x06, 0x00, 0xFF, 0xEE, 0xFF, 0x00, 0x80, 0x01, 0xF0,
0x1F, 0xF0, 0xE0, 0x87, 0x00, 0x18, 0x00, 0xC0, 0x0F, 0xFF, 0x3F, 0xFC,
0x30, 0x03, 0xFF, 0x8F, 0xFC, 0x0C, 0x00, 0x38, 0x00, 0x60, 0x01, 0xC0,
0x03, 0xC6, 0x07, 0xF8, 0x00, 0x00, 0x60, 0x1B, 0x01, 0x8C, 0x0C, 0x60,
0xC1, 0x86, 0x0C, 0x60, 0x36, 0x00, 0xF0, 0x07, 0xF1, 0xFF, 0x80, 0xC0,
0x06, 0x03, 0xFF, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x0C, 0xC0,
0x16, 0x00, 0x78, 0x00, 0x00, 0x07, 0x80, 0xFF, 0x87, 0x87, 0x18, 0x0E,
0x60, 0x19, 0x80, 0x66, 0x00, 0x1E, 0x00, 0x3F, 0x80, 0x3F, 0xC0, 0x0F,
0x80, 0x07, 0xC0, 0x1F, 0x00, 0x3E, 0x01, 0xDC, 0x06, 0x7E, 0xF8, 0x7F,
0x80, 0x20, 0x00, 0x00, 0x03, 0xF0, 0xE7, 0x18, 0x63, 0x0C, 0x60, 0x0E,
0x01, 0xF0, 0x67, 0x98, 0x7B, 0x03, 0xE0, 0x37, 0x06, 0xF0, 0xC7, 0x18,
0x7E, 0x07, 0x80, 0x31, 0x87, 0x30, 0x67, 0x1C, 0x7F, 0x07, 0xC0, 0x21,
0x0C, 0xC1, 0xE0, 0x70, 0x00, 0x07, 0x87, 0xFB, 0x86, 0xC1, 0xB0, 0x0F,
0x01, 0xFC, 0x0F, 0x80, 0x7C, 0x0F, 0x03, 0xF3, 0x9F, 0xE0, 0x80, 0x03,
0xE0, 0x03, 0xFF, 0x01, 0x80, 0xE0, 0xC0, 0x0C, 0x61, 0xE1, 0xB0, 0xCC,
0x6C, 0x61, 0x8F, 0x10, 0x63, 0x8C, 0x00, 0xE3, 0x00, 0x38, 0xC0, 0x0F,
0x10, 0x63, 0xC6, 0x18, 0x90, 0xFC, 0x66, 0x1E, 0x30, 0xC0, 0x1C, 0x1C,
0x0E, 0x01, 0xFE, 0x00, 0x08, 0x00, 0x39, 0xDB, 0x10, 0x67, 0x50, 0xA3,
0x7F, 0x01, 0xFF, 0xF8, 0x00, 0x18, 0xDC, 0xF8, 0xC8, 0x47, 0x31, 0xCE,
0x23, 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30,
0xFC, 0x03, 0xE0, 0x03, 0xFF, 0x01, 0x80, 0xE0, 0xC0, 0x0C, 0x67, 0xF1,
0xB1, 0x8E, 0x6C, 0x61, 0x8F, 0x18, 0x63, 0x86, 0x38, 0xE1, 0xFC, 0x38,
0x61, 0x8F, 0x18, 0x63, 0xC6, 0x18, 0x91, 0x86, 0x66, 0x60, 0xF0, 0xC0,
0x1C, 0x1C, 0x0E, 0x01, 0xFE, 0x00, 0x08, 0x00, 0xFF, 0xF0, 0x7D, 0x8A,
0x1C, 0x38, 0x7F, 0x8E, 0x00, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06,
0x0F, 0xFF, 0xFF, 0xF0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x00,
0x00, 0x00, 0xFF, 0xF0, 0x3C, 0x66, 0x43, 0x43, 0x02, 0x0E, 0x38, 0x60,
0x40, 0xFF, 0x3C, 0x66, 0x42, 0x02, 0x1E, 0x1E, 0x03, 0xC3, 0x43, 0x7E,
0x00, 0x08, 0xC0, 0x6C, 0x01, 0xC0, 0x00, 0x0F, 0xFF, 0xFF, 0xFC, 0x00,
0xE0, 0x0E, 0x00, 0x60, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x03, 0x80,
0x38, 0x03, 0x80, 0x38, 0x01, 0x80, 0x1C, 0x01, 0xC0, 0x0F, 0xFF, 0xFF,
0xFC, 0xC0, 0xD8, 0x1B, 0x03, 0x60, 0x6C, 0x0D, 0x81, 0xB0, 0x36, 0x06,
0xC1, 0xD8, 0x3B, 0x07, 0x71, 0xEF, 0xEF, 0xA0, 0x30, 0x06, 0x00, 0xC0,
0x18, 0x00, 0x03, 0xF3, 0xFF, 0x7E, 0x47, 0xE4, 0xFE, 0x4F, 0xE4, 0xFE,
0x4F, 0xE4, 0x7E, 0x47, 0xE4, 0x3E, 0x40, 0xE4, 0x06, 0x40, 0x64, 0x06,
0x40, 0x64, 0x06, 0x40, 0x64, 0x06, 0x40, 0x64, 0x06, 0x40, 0x64, 0xFF,
0x80, 0x21, 0x0C, 0xC1, 0xE0, 0x30, 0x00, 0x3F, 0xFF, 0xFC, 0x06, 0x03,
0x81, 0xC0, 0xE0, 0x30, 0x18, 0x0C, 0x07, 0x03, 0x80, 0xFF, 0xFF, 0xF0,
0x33, 0xF3, 0x33, 0x33, 0x33, 0x38, 0xDB, 0x1C, 0x38, 0x70, 0xF1, 0x3E,
0x01, 0xFF, 0xF8, 0xC4, 0x73, 0x8C, 0xE2, 0x13, 0x1F, 0x3B, 0x18, 0x00,
0x07, 0x8F, 0xFC, 0x7F, 0xBF, 0xF3, 0x87, 0xC0, 0x1C, 0x0F, 0x00, 0x60,
0x1C, 0x01, 0x80, 0x30, 0x0E, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xC0, 0x0F,
0xFF, 0x00, 0x3F, 0xFC, 0x00, 0xC0, 0x30, 0x03, 0x00, 0xE0, 0x0C, 0x01,
0x80, 0x30, 0x06, 0x01, 0xC0, 0x1C, 0x0F, 0x00, 0x3F, 0xEF, 0xFC, 0x3F,
0x3F, 0xF0, 0x1F, 0x07, 0x83, 0xF9, 0xFE, 0x71, 0xF8, 0x7E, 0x0F, 0x03,
0xC0, 0x60, 0x3C, 0x06, 0x03, 0xC0, 0x7F, 0xFC, 0x06, 0x00, 0xC0, 0x60,
0x0C, 0x07, 0x03, 0x60, 0xF0, 0x77, 0xBF, 0xCE, 0x3F, 0x9F, 0xC0, 0x40,
0x20, 0x06, 0x60, 0x06, 0x60, 0x00, 0x00, 0xE0, 0x07, 0x70, 0x06, 0x30,
0x0E, 0x38, 0x1C, 0x18, 0x18, 0x1C, 0x38, 0x0E, 0x30, 0x06, 0x70, 0x07,
0x60, 0x03, 0xC0, 0x01, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x0C, 0x03, 0x00, 0xC0, 0x00,
0x00, 0x03, 0x00, 0xC0, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x00, 0xC0, 0xF0,
0x3C, 0x0F, 0x86, 0x7F, 0x87, 0x80, 0x07, 0x00, 0x03, 0x00, 0x01, 0x80,
0x00, 0x00, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x06, 0x60,
0x06, 0x60, 0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x38, 0x18, 0x18,
0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x70, 0x0E, 0x60, 0x06,
0x60, 0x07, 0x00, 0xE0, 0x00, 0xC0, 0x01, 0x80, 0x00, 0x00, 0x01, 0xC0,
0x03, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x06, 0x60, 0x06, 0x60, 0x0E, 0x70,
0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8, 0x3F, 0xFC,
0x30, 0x0C, 0x30, 0x0C, 0x70, 0x0E, 0x60, 0x06, 0x60, 0x07, 0x03, 0xC0,
0x03, 0xC0, 0x06, 0x60, 0x00, 0x00, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
0x03, 0xE0, 0x06, 0x60, 0x06, 0x60, 0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x30,
0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C,
0x70, 0x0E, 0x60, 0x06, 0x60, 0x07, 0x07, 0xB0, 0x0D, 0xE0, 0x00, 0x00,
0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xE0, 0x06, 0x60, 0x06, 0x60,
0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8,
0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x70, 0x0E, 0x60, 0x06, 0x60, 0x07,
0x06, 0x60, 0x06, 0x60, 0x00, 0x00, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
0x03, 0xE0, 0x06, 0x60, 0x06, 0x60, 0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x30,
0x1C, 0x38, 0x18, 0x18, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C,
0x70, 0x0E, 0x60, 0x06, 0x60, 0x07, 0x03, 0xC0, 0x03, 0x40, 0x06, 0x60,
0x03, 0xC0, 0x01, 0x80, 0x01, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xE0,
0x06, 0x60, 0x06, 0x60, 0x0E, 0x70, 0x0C, 0x30, 0x0C, 0x30, 0x1C, 0x38,
0x18, 0x18, 0x1F, 0xF8, 0x3F, 0xFC, 0x30, 0x0C, 0x30, 0x0C, 0x70, 0x0E,
0x60, 0x06, 0x60, 0x07, 0x01, 0xFF, 0xFC, 0x03, 0xFF, 0xF8, 0x06, 0x60,
0x00, 0x18, 0xC0, 0x00, 0x31, 0x80, 0x00, 0xE3, 0x00, 0x01, 0x86, 0x00,
0x03, 0x0C, 0x00, 0x0C, 0x1F, 0xFC, 0x18, 0x3F, 0xF8, 0x70, 0x60, 0x00,
0xFF, 0xC0, 0x01, 0xFF, 0x80, 0x07, 0x03, 0x00, 0x0C, 0x06, 0x00, 0x38,
0x0C, 0x00, 0x60, 0x1F, 0xFF, 0xC0, 0x3F, 0xFC, 0x03, 0xC0, 0x1F, 0xF0,
0xF0, 0xE1, 0x80, 0xE7, 0x00, 0xEC, 0x01, 0xF8, 0x00, 0x70, 0x00, 0xC0,
0x01, 0x80, 0x03, 0x00, 0x07, 0x00, 0x06, 0x00, 0x6C, 0x01, 0xDC, 0x03,
0x9C, 0x0E, 0x1F, 0x78, 0x1F, 0xE0, 0x04, 0x00, 0x0C, 0x00, 0x0C, 0x00,
0x18, 0x01, 0xF0, 0x00, 0x1C, 0x00, 0x30, 0x00, 0x80, 0x00, 0x0F, 0xFF,
0x7F, 0xFB, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0F,
0xFF, 0x7F, 0xFB, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80,
0x0F, 0xFF, 0xFF, 0xFC, 0x03, 0x80, 0x18, 0x01, 0x80, 0x00, 0x0F, 0xFF,
0x7F, 0xFB, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0F,
0xFF, 0x7F, 0xFB, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80,
0x0F, 0xFF, 0xFF, 0xFC, 0x07, 0x00, 0x6C, 0x06, 0x60, 0x00, 0x0F, 0xFF,
0x7F, 0xFB, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0F,
0xFF, 0x7F, 0xFB, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80,
0x0F, 0xFF, 0xFF, 0xFC, 0x1D, 0xC0, 0xEE, 0x00, 0x01, 0xFF, 0xEF, 0xFF,
0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0xFF, 0xEF,
0xFF, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0xFF,
0xFF, 0xFF, 0x80, 0xE1, 0x84, 0x13, 0x9C, 0xE7, 0x39, 0xCE, 0x73, 0x9C,
0xE7, 0x39, 0xCE, 0x73, 0x9C, 0x33, 0x30, 0x0E, 0x73, 0x9C, 0xE7, 0x39,
0xCE, 0x73, 0x9C, 0xE7, 0x39, 0xCE, 0x70, 0x38, 0xD9, 0x14, 0x03, 0x87,
0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1,
0xC3, 0x87, 0x00, 0xEF, 0xDC, 0x01, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70,
0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x80, 0x3F, 0x80,
0x3F, 0xF8, 0x30, 0x7C, 0x30, 0x0E, 0x30, 0x06, 0x30, 0x06, 0x30, 0x07,
0x30, 0x03, 0xFF, 0x03, 0xFF, 0x03, 0x30, 0x03, 0x30, 0x03, 0x30, 0x07,
0x30, 0x06, 0x30, 0x0E, 0x30, 0x1C, 0x3F, 0xF8, 0x3F, 0xF0, 0x0C, 0x40,
0x7F, 0x01, 0x08, 0x30, 0x07, 0xE0, 0x1F, 0xC0, 0x7F, 0x01, 0xFE, 0x07,
0xD8, 0x1F, 0x30, 0x7C, 0xE1, 0xF1, 0x87, 0xC3, 0x1F, 0x0E, 0x7C, 0x19,
0xF0, 0x77, 0xC0, 0xFF, 0x01, 0xFC, 0x07, 0xF0, 0x0F, 0xC0, 0x1C, 0x07,
0x00, 0x00, 0xC0, 0x00, 0x20, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7F, 0xC0,
0xF8, 0xF0, 0x70, 0x1C, 0x70, 0x07, 0x30, 0x01, 0xB8, 0x00, 0xD8, 0x00,
0x7C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x07, 0xE0, 0x03, 0x30,
0x01, 0x9C, 0x01, 0xC7, 0x01, 0xC1, 0xF7, 0xC0, 0x7F, 0xC0, 0x02, 0x00,
0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x7F,
0xC0, 0xF8, 0xF0, 0x70, 0x1C, 0x70, 0x07, 0x30, 0x01, 0xB8, 0x00, 0xD8,
0x00, 0x7C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x07, 0xE0, 0x03,
0x30, 0x01, 0x9C, 0x01, 0xC7, 0x01, 0xC1, 0xF7, 0xC0, 0x7F, 0xC0, 0x02,
0x00, 0x01, 0xC0, 0x01, 0xB0, 0x01, 0x88, 0x00, 0x00, 0x00, 0x3E, 0x00,
0x7F, 0xC0, 0xF8, 0xF0, 0x70, 0x1C, 0x70, 0x07, 0x30, 0x01, 0xB8, 0x00,
0xD8, 0x00, 0x7C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x07, 0x80, 0x07, 0xE0,
0x03, 0x30, 0x01, 0x9C, 0x01, 0xC7, 0x01, 0xC1, 0xF7, 0xC0, 0x7F, 0xC0,
0x02, 0x00, 0x07, 0xB0, 0x03, 0xF8, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFF,
0x81, 0xF1, 0xE0, 0xE0, 0x38, 0xE0, 0x0E, 0x60, 0x03, 0x70, 0x01, 0xB0,
0x00, 0xF8, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0xC0, 0x06,
0x60, 0x03, 0x38, 0x03, 0x8E, 0x03, 0x83, 0xEF, 0x80, 0xFF, 0x80, 0x04,
0x00, 0x07, 0x70, 0x03, 0xB8, 0x00, 0x00, 0x00, 0x7C, 0x00, 0xFF, 0x81,
0xF1, 0xE0, 0xE0, 0x38, 0xE0, 0x0E, 0x60, 0x03, 0x70, 0x01, 0xB0, 0x00,
0xF8, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0xC0, 0x06, 0x60,
0x03, 0x38, 0x03, 0x8E, 0x03, 0x83, 0xEF, 0x80, 0xFF, 0x80, 0x04, 0x00,
0x40, 0x98, 0x63, 0x30, 0x78, 0x0C, 0x07, 0x83, 0x31, 0x86, 0x40, 0x80,
0x03, 0xC1, 0x87, 0xFD, 0x8F, 0x8F, 0x86, 0x01, 0xC7, 0x00, 0xF7, 0x00,
0xDB, 0x00, 0xCD, 0x80, 0xC7, 0xC0, 0xC3, 0xE0, 0xC1, 0xF0, 0xC0, 0xF8,
0xC0, 0x7E, 0xC0, 0x33, 0xC0, 0x19, 0xC0, 0x18, 0x70, 0x1C, 0x5F, 0x7C,
0x67, 0xFC, 0x00, 0x20, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x00, 0x0C,
0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80,
0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF8, 0x06,
0xC0, 0x77, 0xDF, 0x1F, 0xF0, 0x08, 0x00, 0x01, 0xC0, 0x18, 0x00, 0x80,
0x08, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0,
0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00,
0xF8, 0x06, 0xC0, 0x77, 0xDF, 0x1F, 0xF0, 0x08, 0x00, 0x07, 0x00, 0x2C,
0x03, 0x30, 0x00, 0x0C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E,
0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0,
0x1E, 0x00, 0xF8, 0x06, 0xC0, 0x77, 0xDF, 0x1F, 0xF0, 0x08, 0x00, 0x0C,
0xC0, 0x66, 0x00, 0x01, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03,
0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78,
0x03, 0xC0, 0x1F, 0x00, 0xD8, 0x0E, 0xFB, 0xE3, 0xFE, 0x01, 0x00, 0x00,
0xE0, 0x00, 0xC0, 0x01, 0x80, 0x00, 0x00, 0xE0, 0x07, 0x70, 0x06, 0x30,
0x0E, 0x38, 0x1C, 0x18, 0x18, 0x1C, 0x38, 0x0E, 0x30, 0x06, 0x70, 0x07,
0x60, 0x03, 0xC0, 0x01, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0xC0, 0x06, 0x00, 0x30, 0x01,
0xFE, 0x0F, 0xFE, 0x60, 0x7B, 0x00, 0xD8, 0x07, 0xC0, 0x3E, 0x01, 0xF0,
0x0D, 0x83, 0xEF, 0xFE, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00,
0x00, 0x7F, 0x8F, 0x3C, 0xC0, 0xEC, 0x06, 0xC0, 0x6C, 0x0E, 0xC1, 0xCC,
0x78, 0xC7, 0xEC, 0x06, 0xC0, 0x7C, 0x07, 0xC0, 0x7C, 0x06, 0xC0, 0x6C,
0xFC, 0xCF, 0x80, 0x1C, 0x00, 0xC0, 0x06, 0x00, 0x00, 0x1F, 0x07, 0xFC,
0x60, 0xC6, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xC7, 0x8C, 0xE0, 0xCC, 0x0C,
0xC0, 0xCE, 0x3E, 0x7E, 0x70, 0x02, 0x03, 0x00, 0x60, 0x0C, 0x00, 0x00,
0x1F, 0x07, 0xFC, 0x60, 0xC6, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xC7, 0x8C,
0xE0, 0xCC, 0x0C, 0xC0, 0xCE, 0x3E, 0x7E, 0x70, 0x02, 0x0E, 0x01, 0xB0,
0x11, 0x80, 0x00, 0x1F, 0x07, 0xFC, 0x60, 0xC6, 0x0C, 0x00, 0xC0, 0x1C,
0x3F, 0xC7, 0x8C, 0xE0, 0xCC, 0x0C, 0xC0, 0xCE, 0x3E, 0x7E, 0x70, 0x02,
0x1C, 0x83, 0xF8, 0x00, 0x00, 0x00, 0x1F, 0x07, 0xFC, 0x60, 0xC6, 0x0C,
0x00, 0xC0, 0x1C, 0x3F, 0xC7, 0x8C, 0xE0, 0xCC, 0x0C, 0xC0, 0xCE, 0x3E,
0x7E, 0x70, 0x02, 0x3B, 0x83, 0xB8, 0x00, 0x00, 0x00, 0x1F, 0x07, 0xFC,
0x60, 0xC6, 0x0C, 0x00, 0xC0, 0x1C, 0x3F, 0xC7, 0x8C, 0xE0, 0xCC, 0x0C,
0xC0, 0xCE, 0x3E, 0x7E, 0x70, 0x02, 0x04, 0x00, 0xF0, 0x19, 0x01, 0xB0,
0x0F, 0x00, 0x00, 0x1F, 0x07, 0xFC, 0x60, 0xC6, 0x0C, 0x00, 0xC0, 0x1C,
0x3F, 0xC7, 0x8C, 0xE0, 0xCC, 0x0C, 0xC0, 0xCE, 0x3E, 0x7E, 0x70, 0x02,
0x1F, 0x0F, 0x0F, 0xF7, 0xF9, 0x83, 0xC3, 0xB0, 0x70, 0x30, 0x0C, 0x06,
0x03, 0x80, 0xCF, 0xFF, 0xFB, 0xC6, 0x00, 0xE0, 0xC0, 0x18, 0x1C, 0x0F,
0x03, 0x83, 0xF1, 0xDC, 0xE7, 0xE3, 0xF8, 0x00, 0x08, 0x00, 0x1F, 0x0F,
0xE6, 0x1F, 0x83, 0xC0, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x3E, 0x0D,
0xC7, 0x3F, 0x83, 0x00, 0xC0, 0x0C, 0x03, 0x0F, 0x80, 0x38, 0x03, 0x80,
0x30, 0x03, 0x00, 0x00, 0x1E, 0x0F, 0xF3, 0x86, 0x60, 0x78, 0x0F, 0x01,
0xFF, 0xFC, 0x01, 0x80, 0x30, 0x1B, 0x07, 0x79, 0xC7, 0xF0, 0x10, 0x00,
0x03, 0x80, 0x60, 0x18, 0x06, 0x00, 0x00, 0x1E, 0x0F, 0xF3, 0x86, 0x60,
0x78, 0x0F, 0x01, 0xFF, 0xFC, 0x01, 0x80, 0x30, 0x1B, 0x07, 0x79, 0xC7,
0xF0, 0x10, 0x00, 0x0F, 0x03, 0x60, 0x66, 0x00, 0x00, 0xF0, 0x7F, 0x9C,
0x33, 0x03, 0xC0, 0x78, 0x0F, 0xFF, 0xE0, 0x0C, 0x01, 0x80, 0xD8, 0x3B,
0xCE, 0x3F, 0x80, 0x80, 0x19, 0x83, 0x30, 0x00, 0x00, 0x00, 0xF0, 0x7F,
0x9C, 0x33, 0x03, 0xC0, 0x78, 0x0F, 0xFF, 0xE0, 0x0C, 0x01, 0x80, 0xD8,
0x3B, 0xCE, 0x3F, 0x80, 0x80, 0xE3, 0x0C, 0x30, 0x18, 0xC6, 0x31, 0x8C,
0x63, 0x18, 0xC6, 0x31, 0x80, 0x39, 0x99, 0x80, 0x31, 0x8C, 0x63, 0x18,
0xC6, 0x31, 0x8C, 0x63, 0x00, 0x30, 0xF1, 0xB6, 0x20, 0x06, 0x0C, 0x18,
0x30, 0x60, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xCF, 0x30, 0x00,
0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0x20, 0x07,
0xE0, 0x70, 0x3F, 0x00, 0x30, 0x3F, 0x0F, 0xF3, 0x8E, 0xE0, 0xF8, 0x0F,
0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1B, 0x07, 0x7B, 0xC7, 0xF0, 0x10,
0x00, 0x39, 0x1F, 0xC4, 0x20, 0x00, 0xCF, 0x3F, 0xEE, 0x1F, 0x03, 0xC0,
0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xC0, 0x38,
0x03, 0x00, 0x30, 0x03, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8,
0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1B, 0x07, 0x7B, 0xC7, 0xF0,
0x10, 0x00, 0x03, 0x80, 0xE0, 0x18, 0x06, 0x00, 0x00, 0x3E, 0x0F, 0xE3,
0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1B, 0x07,
0x7B, 0xC7, 0xF0, 0x10, 0x00, 0x0E, 0x01, 0xC0, 0x6C, 0x18, 0xC0, 0x00,
0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x3C, 0x07, 0x80,
0xF0, 0x1B, 0x07, 0x7B, 0xC7, 0xF0, 0x10, 0x00, 0x1C, 0x87, 0xF0, 0x00,
0x00, 0x01, 0xF0, 0x7F, 0x1C, 0x77, 0x07, 0xC0, 0x78, 0x0F, 0x01, 0xE0,
0x3C, 0x07, 0x80, 0xD8, 0x3B, 0xDE, 0x3F, 0x80, 0x80, 0x3B, 0x87, 0x70,
0xEE, 0x00, 0x00, 0x00, 0x3E, 0x0F, 0xE3, 0x8E, 0xE0, 0xF8, 0x0F, 0x01,
0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x1B, 0x07, 0x7B, 0xC7, 0xF0, 0x10, 0x00,
0x06, 0x00, 0x60, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
0x06, 0x00, 0x60, 0x06, 0x00, 0x0F, 0x80, 0xFE, 0x8E, 0x38, 0xE0, 0xE6,
0x0B, 0x30, 0x99, 0x8C, 0xCC, 0xC6, 0x6C, 0x33, 0x41, 0x8C, 0x1C, 0x79,
0xC5, 0xFC, 0x01, 0x00, 0x70, 0x0C, 0x01, 0x80, 0x30, 0x00, 0x30, 0x3C,
0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7C, 0x1F, 0x07,
0xE3, 0xDF, 0xB1, 0x00, 0x07, 0x03, 0x80, 0xC0, 0x60, 0x00, 0x30, 0x3C,
0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7C, 0x1F, 0x07,
0xE3, 0xDF, 0xB1, 0x00, 0x1C, 0x07, 0x03, 0x60, 0x8C, 0x00, 0x30, 0x3C,
0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7C, 0x1F, 0x07,
0xE3, 0xDF, 0xB1, 0x00, 0x77, 0x1D, 0xC7, 0x70, 0x00, 0x00, 0x30, 0x3C,
0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x7C, 0x1F, 0x07,
0xE3, 0xDF, 0xB1, 0x00, 0x03, 0x80, 0x60, 0x18, 0x06, 0x00, 0x00, 0xC0,
0xD8, 0x1B, 0x03, 0x30, 0xC6, 0x18, 0xC7, 0x0C, 0xC1, 0x98, 0x37, 0x03,
0xC0, 0x78, 0x0E, 0x00, 0xC0, 0x18, 0x06, 0x00, 0xC0, 0xF0, 0x1E, 0x00,
0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xCF, 0x0D, 0xFC, 0xF0, 0xCE, 0x06,
0xE0, 0x6C, 0x06, 0xC0, 0x7C, 0x07, 0xC0, 0x6E, 0x06, 0xE0, 0xEF, 0x9C,
0xDF, 0x8C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x19, 0x83, 0x30,
0x66, 0x00, 0x00, 0x00, 0xC0, 0xD8, 0x1B, 0x03, 0x30, 0xC6, 0x18, 0xC7,
0x0C, 0xC1, 0x98, 0x37, 0x03, 0xC0, 0x78, 0x0E, 0x00, 0xC0, 0x18, 0x06,
0x00, 0xC0, 0xF0, 0x1E, 0x00 };
const GFXglyph FreeSans12pt8bGlyphs[] PROGMEM = {
{ 0, 1, 1, 7, 0, 0 }, // 0x20 ' ' U+0020
{ 1, 2, 18, 7, 3, -17 }, // 0x21 '!' U+0021
{ 6, 6, 6, 9, 1, -16 }, // 0x22 '"' U+0022
{ 11, 13, 17, 13, 0, -16 }, // 0x23 '#' U+0023
{ 39, 11, 21, 13, 1, -17 }, // 0x24 '$' U+0024
{ 68, 20, 17, 21, 1, -16 }, // 0x25 '%' U+0025
{ 111, 14, 18, 16, 1, -16 }, // 0x26 '&' U+0026
{ 143, 2, 6, 5, 1, -16 }, // 0x27 ''' U+0027
{ 145, 5, 23, 8, 2, -17 }, // 0x28 '(' U+0028
{ 160, 5, 23, 8, 1, -17 }, // 0x29 ')' U+0029
{ 175, 7, 7, 9, 1, -17 }, // 0x2a '*' U+002A
{ 182, 12, 11, 14, 1, -10 }, // 0x2b '+' U+002B
{ 199, 3, 7, 7, 2, -2 }, // 0x2c ',' U+002C
{ 202, 6, 1, 8, 1, -6 }, // 0x2d '-' U+002D
{ 203, 3, 3, 7, 2, -2 }, // 0x2e '.' U+002E
{ 205, 7, 18, 7, 0, -17 }, // 0x2f '/' U+002F
{ 221, 11, 18, 13, 1, -16 }, // 0x30 '0' U+0030
{ 246, 6, 17, 13, 2, -16 }, // 0x31 '1' U+0031
{ 259, 11, 17, 13, 1, -16 }, // 0x32 '2' U+0032
{ 283, 11, 18, 13, 1, -16 }, // 0x33 '3' U+0033
{ 308, 11, 17, 13, 1, -16 }, // 0x34 '4' U+0034
{ 332, 11, 18, 13, 1, -16 }, // 0x35 '5' U+0035
{ 357, 11, 18, 13, 1, -16 }, // 0x36 '6' U+0036
{ 382, 11, 17, 13, 1, -16 }, // 0x37 '7' U+0037
{ 406, 11, 18, 13, 1, -16 }, // 0x38 '8' U+0038
{ 431, 11, 18, 13, 1, -16 }, // 0x39 '9' U+0039
{ 456, 2, 13, 7, 3, -12 }, // 0x3a ':' U+003A
{ 460, 2, 17, 7, 3, -12 }, // 0x3b ';' U+003B
{ 465, 12, 11, 14, 1, -10 }, // 0x3c '<' U+003C
{ 482, 12, 5, 14, 1, -7 }, // 0x3d '=' U+003D
{ 490, 12, 11, 14, 1, -10 }, // 0x3e '>' U+003E
{ 507, 10, 18, 13, 2, -17 }, // 0x3f '?' U+003F
{ 530, 22, 21, 24, 1, -17 }, // 0x40 '@' U+0040
{ 588, 16, 18, 16, 0, -17 }, // 0x41 'A' U+0041
{ 624, 13, 18, 16, 2, -17 }, // 0x42 'B' U+0042
{ 654, 15, 19, 17, 1, -17 }, // 0x43 'C' U+0043
{ 690, 14, 18, 17, 2, -17 }, // 0x44 'D' U+0044
{ 722, 13, 18, 16, 2, -17 }, // 0x45 'E' U+0045
{ 752, 12, 18, 15, 2, -17 }, // 0x46 'F' U+0046
{ 779, 16, 19, 19, 1, -17 }, // 0x47 'G' U+0047
{ 817, 13, 18, 17, 2, -17 }, // 0x48 'H' U+0048
{ 847, 3, 18, 7, 2, -17 }, // 0x49 'I' U+0049
{ 854, 10, 19, 12, 0, -17 }, // 0x4a 'J' U+004A
{ 878, 14, 18, 16, 2, -17 }, // 0x4b 'K' U+004B
{ 910, 11, 18, 13, 2, -17 }, // 0x4c 'L' U+004C
{ 935, 16, 18, 20, 2, -17 }, // 0x4d 'M' U+004D
{ 971, 14, 18, 17, 2, -17 }, // 0x4e 'N' U+004E
{ 1003, 17, 19, 19, 1, -17 }, // 0x4f 'O' U+004F
{ 1044, 13, 18, 16, 2, -17 }, // 0x50 'P' U+0050
{ 1074, 17, 19, 19, 1, -17 }, // 0x51 'Q' U+0051
{ 1115, 14, 18, 17, 2, -17 }, // 0x52 'R' U+0052
{ 1147, 14, 19, 16, 1, -17 }, // 0x53 'S' U+0053
{ 1181, 14, 18, 15, 0, -17 }, // 0x54 'T' U+0054
{ 1213, 13, 19, 17, 2, -17 }, // 0x55 'U' U+0055
{ 1244, 14, 18, 16, 1, -17 }, // 0x56 'V' U+0056
{ 1276, 21, 18, 23, 1, -17 }, // 0x57 'W' U+0057
{ 1324, 15, 18, 16, 1, -17 }, // 0x58 'X' U+0058
{ 1358, 16, 18, 16, 0, -17 }, // 0x59 'Y' U+0059
{ 1394, 13, 18, 15, 1, -17 }, // 0x5a 'Z' U+005A
{ 1424, 4, 23, 7, 2, -17 }, // 0x5b '[' U+005B
{ 1436, 7, 18, 7, 0, -17 }, // 0x5c '\' U+005C
{ 1452, 4, 23, 7, 1, -17 }, // 0x5d ']' U+005D
{ 1464, 9, 9, 11, 1, -16 }, // 0x5e '^' U+005E
{ 1475, 15, 1, 13, -1, 4 }, // 0x5f '_' U+005F
{ 1477, 5, 4, 8, 1, -17 }, // 0x60 '`' U+0060
{ 1480, 12, 14, 13, 1, -12 }, // 0x61 'a' U+0061
{ 1501, 12, 19, 13, 1, -17 }, // 0x62 'b' U+0062
{ 1530, 10, 14, 12, 1, -12 }, // 0x63 'c' U+0063
{ 1548, 11, 19, 13, 1, -17 }, // 0x64 'd' U+0064
{ 1575, 11, 14, 13, 1, -12 }, // 0x65 'e' U+0065
{ 1595, 6, 18, 7, 0, -17 }, // 0x66 'f' U+0066
{ 1609, 11, 18, 13, 1, -12 }, // 0x67 'g' U+0067
{ 1634, 10, 18, 13, 2, -17 }, // 0x68 'h' U+0068
{ 1657, 2, 18, 5, 2, -17 }, // 0x69 'i' U+0069
{ 1662, 4, 23, 5, 0, -17 }, // 0x6a 'j' U+006A
{ 1674, 11, 18, 12, 1, -17 }, // 0x6b 'k' U+006B
{ 1699, 2, 18, 5, 2, -17 }, // 0x6c 'l' U+006C
{ 1704, 16, 13, 20, 2, -12 }, // 0x6d 'm' U+006D
{ 1730, 10, 13, 13, 2, -12 }, // 0x6e 'n' U+006E
{ 1747, 11, 14, 13, 1, -12 }, // 0x6f 'o' U+006F
{ 1767, 12, 18, 13, 1, -12 }, // 0x70 'p' U+0070
{ 1794, 11, 18, 13, 1, -12 }, // 0x71 'q' U+0071
{ 1819, 6, 13, 8, 2, -12 }, // 0x72 'r' U+0072
{ 1829, 10, 14, 12, 1, -12 }, // 0x73 's' U+0073
{ 1847, 6, 17, 7, 0, -15 }, // 0x74 't' U+0074
{ 1860, 10, 14, 13, 2, -12 }, // 0x75 'u' U+0075
{ 1878, 12, 13, 12, 0, -12 }, // 0x76 'v' U+0076
{ 1898, 17, 13, 17, 0, -12 }, // 0x77 'w' U+0077
{ 1926, 11, 13, 12, 0, -12 }, // 0x78 'x' U+0078
{ 1944, 11, 18, 12, 0, -12 }, // 0x79 'y' U+0079
{ 1969, 10, 13, 12, 1, -12 }, // 0x7a 'z' U+007A
{ 1986, 6, 23, 8, 1, -17 }, // 0x7b '{' U+007B
{ 2004, 2, 23, 6, 2, -17 }, // 0x7c '|' U+007C
{ 2010, 5, 23, 8, 1, -17 }, // 0x7d '}' U+007D
{ 2025, 10, 5, 14, 2, -10 }, // 0x7e '~' U+007E
{ 2032, 13, 18, 17, 2, -17 }, // 0x7f 'REPLACEMENT CHARACTER *' U+2370
{ 2062, 1, 1, 7, 0, 0 }, // 0x80 'NO-BREAK SPACE' U+00A0
{ 2063, 2, 18, 7, 3, -12 }, // 0x81 'INVERTED EXCLAMATION MARK' U+00A1
{ 2068, 11, 18, 13, 1, -14 }, // 0x82 'CENT SIGN' U+00A2
{ 2093, 12, 19, 13, 1, -17 }, // 0x83 'POUND SIGN' U+00A3
{ 2122, 14, 18, 16, 1, -16 }, // 0x84 'EURO SIGN *' U+20AC
{ 2154, 13, 17, 13, 0, -16 }, // 0x85 'YEN SIGN' U+00A5
{ 2182, 14, 23, 16, 1, -21 }, // 0x86 'LATIN CAPITAL LETTER S WITH CARON *' U+0160
{ 2223, 11, 23, 13, 1, -17 }, // 0x87 'SECTION SIGN' U+00A7
{ 2255, 10, 19, 12, 1, -17 }, // 0x88 'LATIN SMALL LETTER S WITH CARON *' U+0161
{ 2279, 18, 19, 18, 0, -17 }, // 0x89 'COPYRIGHT SIGN' U+00A9
{ 2322, 7, 11, 9, 1, -17 }, // 0x8a 'FEMININE ORDINAL INDICATOR' U+00AA
{ 2332, 9, 8, 11, 1, -10 }, // 0x8b 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00AB
{ 2341, 12, 7, 14, 1, -8 }, // 0x8c 'NOT SIGN' U+00AC
{ 2352, 6, 1, 8, 1, -6 }, // 0x8d 'SOFT HYPHEN' U+00AD
{ 2353, 18, 19, 18, 0, -17 }, // 0x8e 'REGISTERED SIGN' U+00AE
{ 2396, 6, 2, 8, 1, -16 }, // 0x8f 'MACRON' U+00AF
{ 2398, 7, 7, 15, 4, -15 }, // 0x90 'DEGREE SIGN' U+00B0
{ 2405, 12, 15, 14, 1, -14 }, // 0x91 'PLUS-MINUS SIGN' U+00B1
{ 2428, 8, 10, 8, 0, -16 }, // 0x92 'SUPERSCRIPT TWO' U+00B2
{ 2438, 8, 11, 8, 0, -16 }, // 0x93 'SUPERSCRIPT THREE' U+00B3
{ 2449, 13, 22, 15, 1, -21 }, // 0x94 'LATIN CAPITAL LETTER Z WITH CARON *' U+017D
{ 2485, 11, 18, 13, 2, -12 }, // 0x95 'MICRO SIGN' U+00B5
{ 2510, 12, 22, 13, 1, -17 }, // 0x96 'PILCROW SIGN' U+00B6
{ 2543, 3, 3, 7, 2, -7 }, // 0x97 'MIDDLE DOT' U+00B7
{ 2545, 10, 18, 12, 1, -17 }, // 0x98 'LATIN SMALL LETTER Z WITH CARON *' U+017E
{ 2568, 4, 10, 8, 2, -16 }, // 0x99 'SUPERSCRIPT ONE' U+00B9
{ 2573, 7, 11, 9, 1, -17 }, // 0x9a 'MASCULINE ORDINAL INDICATOR' U+00BA
{ 2583, 9, 8, 11, 1, -9 }, // 0x9b 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00BB
{ 2592, 22, 18, 24, 1, -17 }, // 0x9c 'LATIN CAPITAL LIGATURE OE *' U+0152
{ 2642, 20, 14, 23, 1, -12 }, // 0x9d 'LATIN SMALL LIGATURE OE *' U+0153
{ 2677, 16, 21, 16, 0, -20 }, // 0x9e 'LATIN CAPITAL LETTER Y WITH DIAERESIS *' U+0178
{ 2719, 10, 18, 13, 3, -12 }, // 0x9f 'INVERTED QUESTION MARK' U+00BF
{ 2742, 16, 22, 16, 0, -21 }, // 0xa0 'LATIN CAPITAL LETTER A WITH GRAVE' U+00C0
{ 2786, 16, 22, 16, 0, -21 }, // 0xa1 'LATIN CAPITAL LETTER A WITH ACUTE' U+00C1
{ 2830, 16, 22, 16, 0, -21 }, // 0xa2 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' U+00C2
{ 2874, 16, 21, 16, 0, -20 }, // 0xa3 'LATIN CAPITAL LETTER A WITH TILDE' U+00C3
{ 2916, 16, 21, 16, 0, -20 }, // 0xa4 'LATIN CAPITAL LETTER A WITH DIAERESIS' U+00C4
{ 2958, 16, 23, 16, 0, -22 }, // 0xa5 'LATIN CAPITAL LETTER A WITH RING ABOVE' U+00C5
{ 3004, 23, 18, 24, 0, -17 }, // 0xa6 'LATIN CAPITAL LETTER AE' U+00C6
{ 3056, 15, 23, 17, 1, -17 }, // 0xa7 'LATIN CAPITAL LETTER C WITH CEDILLA' U+00C7
{ 3100, 13, 22, 16, 2, -21 }, // 0xa8 'LATIN CAPITAL LETTER E WITH GRAVE' U+00C8
{ 3136, 13, 22, 16, 2, -21 }, // 0xa9 'LATIN CAPITAL LETTER E WITH ACUTE' U+00C9
{ 3172, 13, 22, 16, 2, -21 }, // 0xaa 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' U+00CA
{ 3208, 13, 21, 16, 2, -20 }, // 0xab 'LATIN CAPITAL LETTER E WITH DIAERESIS' U+00CB
{ 3243, 5, 22, 7, 0, -21 }, // 0xac 'LATIN CAPITAL LETTER I WITH GRAVE' U+00CC
{ 3257, 5, 22, 7, 2, -21 }, // 0xad 'LATIN CAPITAL LETTER I WITH ACUTE' U+00CD
{ 3271, 7, 22, 7, 0, -21 }, // 0xae 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' U+00CE
{ 3291, 7, 21, 7, 0, -20 }, // 0xaf 'LATIN CAPITAL LETTER I WITH DIAERESIS' U+00CF
{ 3310, 16, 18, 17, 0, -17 }, // 0xb0 'LATIN CAPITAL LETTER ETH' U+00D0
{ 3346, 14, 21, 17, 2, -20 }, // 0xb1 'LATIN CAPITAL LETTER N WITH TILDE' U+00D1
{ 3383, 17, 23, 19, 1, -21 }, // 0xb2 'LATIN CAPITAL LETTER O WITH GRAVE' U+00D2
{ 3432, 17, 23, 19, 1, -21 }, // 0xb3 'LATIN CAPITAL LETTER O WITH ACUTE' U+00D3
{ 3481, 17, 23, 19, 1, -21 }, // 0xb4 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' U+00D4
{ 3530, 17, 22, 19, 1, -20 }, // 0xb5 'LATIN CAPITAL LETTER O WITH TILDE' U+00D5
{ 3577, 17, 22, 19, 1, -20 }, // 0xb6 'LATIN CAPITAL LETTER O WITH DIAERESIS' U+00D6
{ 3624, 10, 9, 14, 2, -9 }, // 0xb7 'MULTIPLICATION SIGN' U+00D7
{ 3636, 17, 19, 19, 1, -17 }, // 0xb8 'LATIN CAPITAL LETTER O WITH STROKE' U+00D8
{ 3677, 13, 23, 17, 2, -21 }, // 0xb9 'LATIN CAPITAL LETTER U WITH GRAVE' U+00D9
{ 3715, 13, 23, 17, 2, -21 }, // 0xba 'LATIN CAPITAL LETTER U WITH ACUTE' U+00DA
{ 3753, 13, 23, 17, 2, -21 }, // 0xbb 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' U+00DB
{ 3791, 13, 22, 17, 2, -20 }, // 0xbc 'LATIN CAPITAL LETTER U WITH DIAERESIS' U+00DC
{ 3827, 16, 22, 16, 0, -21 }, // 0xbd 'LATIN CAPITAL LETTER Y WITH ACUTE' U+00DD
{ 3871, 13, 18, 16, 2, -17 }, // 0xbe 'LATIN CAPITAL LETTER THORN' U+00DE
{ 3901, 12, 17, 15, 2, -16 }, // 0xbf 'LATIN SMALL LETTER SHARP S' U+00DF
{ 3927, 12, 18, 13, 1, -16 }, // 0xc0 'LATIN SMALL LETTER A WITH GRAVE' U+00E0
{ 3954, 12, 18, 13, 1, -16 }, // 0xc1 'LATIN SMALL LETTER A WITH ACUTE' U+00E1
{ 3981, 12, 18, 13, 1, -16 }, // 0xc2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' U+00E2
{ 4008, 12, 18, 13, 1, -16 }, // 0xc3 'LATIN SMALL LETTER A WITH TILDE' U+00E3
{ 4035, 12, 18, 13, 1, -16 }, // 0xc4 'LATIN SMALL LETTER A WITH DIAERESIS' U+00E4
{ 4062, 12, 20, 13, 1, -18 }, // 0xc5 'LATIN SMALL LETTER A WITH RING ABOVE' U+00E5
{ 4092, 19, 14, 22, 1, -12 }, // 0xc6 'LATIN SMALL LETTER AE' U+00E6
{ 4126, 10, 18, 12, 1, -12 }, // 0xc7 'LATIN SMALL LETTER C WITH CEDILLA' U+00E7
{ 4149, 11, 19, 13, 1, -17 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8
{ 4176, 11, 19, 13, 1, -17 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9
{ 4203, 11, 18, 13, 1, -16 }, // 0xca 'LATIN SMALL LETTER E WITH CIRCUMFLEX' U+00EA
{ 4228, 11, 18, 13, 1, -16 }, // 0xcb 'LATIN SMALL LETTER E WITH DIAERESIS' U+00EB
{ 4253, 5, 18, 6, 0, -17 }, // 0xcc 'LATIN SMALL LETTER I WITH GRAVE' U+00EC
{ 4265, 5, 18, 6, 1, -17 }, // 0xcd 'LATIN SMALL LETTER I WITH ACUTE' U+00ED
{ 4277, 7, 18, 6, 0, -17 }, // 0xce 'LATIN SMALL LETTER I WITH CIRCUMFLEX' U+00EE
{ 4293, 6, 17, 6, 0, -16 }, // 0xcf 'LATIN SMALL LETTER I WITH DIAERESIS' U+00EF
{ 4306, 11, 19, 13, 1, -17 }, // 0xd0 'LATIN SMALL LETTER ETH' U+00F0
{ 4333, 10, 17, 13, 2, -16 }, // 0xd1 'LATIN SMALL LETTER N WITH TILDE' U+00F1
{ 4355, 11, 19, 13, 1, -17 }, // 0xd2 'LATIN SMALL LETTER O WITH GRAVE' U+00F2
{ 4382, 11, 19, 13, 1, -17 }, // 0xd3 'LATIN SMALL LETTER O WITH ACUTE' U+00F3
{ 4409, 11, 19, 13, 1, -17 }, // 0xd4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' U+00F4
{ 4436, 11, 18, 13, 1, -16 }, // 0xd5 'LATIN SMALL LETTER O WITH TILDE' U+00F5
{ 4461, 11, 19, 13, 1, -17 }, // 0xd6 'LATIN SMALL LETTER O WITH DIAERESIS' U+00F6
{ 4488, 12, 11, 14, 1, -10 }, // 0xd7 'DIVISION SIGN' U+00F7
{ 4505, 13, 14, 15, 0, -12 }, // 0xd8 'LATIN SMALL LETTER O WITH STROKE' U+00F8
{ 4528, 10, 19, 13, 2, -17 }, // 0xd9 'LATIN SMALL LETTER U WITH GRAVE' U+00F9
{ 4552, 10, 19, 13, 2, -17 }, // 0xda 'LATIN SMALL LETTER U WITH ACUTE' U+00FA
{ 4576, 10, 19, 13, 2, -17 }, // 0xdb 'LATIN SMALL LETTER U WITH CIRCUMFLEX' U+00FB
{ 4600, 10, 19, 13, 2, -17 }, // 0xdc 'LATIN SMALL LETTER U WITH DIAERESIS' U+00FC
{ 4624, 11, 23, 12, 0, -17 }, // 0xdd 'LATIN SMALL LETTER Y WITH ACUTE' U+00FD
{ 4656, 12, 22, 13, 1, -16 }, // 0xde 'LATIN SMALL LETTER THORN' U+00FE
{ 4689, 11, 23, 12, 0, -17 } }; // 0xdf 'LATIN SMALL LETTER Y WITH DIAERESIS' U+000FF
const GFXfont FreeSans12pt8b PROGMEM = {
(uint8_t *)FreeSans12pt8bBitmaps,
(GFXglyph *)FreeSans12pt8bGlyphs,
0x20, 0xDF, 33 };
// Approx. 6072 bytes

View File

@ -0,0 +1,451 @@
const uint8_t FreeSans18pt7bBitmaps[] PROGMEM = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE9, 0x20, 0x3F, 0xFC, 0xE3, 0xF1,
0xF8, 0xFC, 0x7E, 0x3F, 0x1F, 0x8E, 0x82, 0x41, 0x00, 0x01, 0xC3, 0x80,
0x38, 0x70, 0x06, 0x0E, 0x00, 0xC1, 0x80, 0x38, 0x70, 0x07, 0x0E, 0x0F,
0xFF, 0xF9, 0xFF, 0xFF, 0x3F, 0xFF, 0xE0, 0xE1, 0xC0, 0x1C, 0x38, 0x03,
0x87, 0x00, 0x70, 0xE0, 0x0C, 0x18, 0x3F, 0xFF, 0xF7, 0xFF, 0xFE, 0xFF,
0xFF, 0xC1, 0xC3, 0x80, 0x30, 0x60, 0x06, 0x0C, 0x01, 0xC3, 0x80, 0x38,
0x70, 0x07, 0x0E, 0x00, 0xC1, 0x80, 0x03, 0x00, 0x0F, 0xC0, 0x3F, 0xF0,
0x3F, 0xF8, 0x7B, 0x3C, 0xF3, 0x1C, 0xE3, 0x0E, 0xE3, 0x0E, 0xE3, 0x0E,
0xE3, 0x00, 0xE3, 0x00, 0xF3, 0x00, 0x7B, 0x00, 0x7F, 0x80, 0x1F, 0xF0,
0x07, 0xFC, 0x03, 0x7E, 0x03, 0x0F, 0x03, 0x07, 0xE3, 0x07, 0xE3, 0x07,
0xE3, 0x07, 0xE3, 0x0F, 0x73, 0x3E, 0x7F, 0xFC, 0x3F, 0xF8, 0x0F, 0xE0,
0x03, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x78, 0x00,
0xE0, 0x0F, 0xF0, 0x06, 0x00, 0xFF, 0xC0, 0x70, 0x07, 0x0E, 0x07, 0x00,
0x70, 0x38, 0x38, 0x03, 0x00, 0xC3, 0x80, 0x18, 0x06, 0x1C, 0x00, 0xE0,
0x71, 0xC0, 0x03, 0x87, 0x8C, 0x00, 0x1F, 0xF8, 0xE0, 0x00, 0x7F, 0x86,
0x00, 0x01, 0xF8, 0x70, 0x00, 0x00, 0x03, 0x03, 0xC0, 0x00, 0x38, 0x7F,
0x80, 0x01, 0x87, 0xFE, 0x00, 0x1C, 0x38, 0x70, 0x00, 0xC3, 0x81, 0xC0,
0x0E, 0x18, 0x06, 0x00, 0xE0, 0xC0, 0x30, 0x07, 0x07, 0x03, 0x80, 0x70,
0x1C, 0x38, 0x03, 0x80, 0xFF, 0xC0, 0x38, 0x03, 0xFC, 0x01, 0x80, 0x07,
0x80, 0x01, 0xF0, 0x00, 0x7F, 0x80, 0x0F, 0xFC, 0x01, 0xE1, 0xE0, 0x1C,
0x0E, 0x01, 0xC0, 0xE0, 0x1C, 0x0E, 0x01, 0xE1, 0xE0, 0x0E, 0x3C, 0x00,
0x77, 0x80, 0x07, 0xF0, 0x00, 0x7C, 0x00, 0x0F, 0xE0, 0x03, 0xCF, 0x1C,
0x78, 0x79, 0xC7, 0x03, 0xDC, 0xE0, 0x1F, 0x8E, 0x00, 0xF8, 0xE0, 0x0F,
0x0E, 0x00, 0x70, 0xF0, 0x0F, 0x87, 0xC3, 0xFC, 0x7F, 0xFD, 0xC3, 0xFF,
0x0E, 0x0F, 0xC0, 0xF0, 0xFF, 0xFF, 0xFA, 0x40, 0x06, 0x06, 0x0C, 0x0C,
0x18, 0x18, 0x38, 0x30, 0x70, 0x70, 0x70, 0x60, 0xE0, 0xE0, 0xE0, 0xE0,
0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0x60, 0x70, 0x70, 0x70, 0x30, 0x38, 0x18,
0x18, 0x0C, 0x0C, 0x06, 0x03, 0xC0, 0x60, 0x30, 0x30, 0x38, 0x18, 0x1C,
0x0C, 0x0E, 0x0E, 0x0E, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x06, 0x0E, 0x0E, 0x0E, 0x0C, 0x1C, 0x18, 0x38, 0x30, 0x30,
0x60, 0xC0, 0x0C, 0x03, 0x00, 0xC3, 0xB7, 0xFF, 0xC7, 0x81, 0xE0, 0xEC,
0x73, 0x88, 0x40, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01,
0x80, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x80, 0x01,
0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0xFF,
0xF6, 0xDA, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0xC0, 0x30, 0x18,
0x06, 0x01, 0x80, 0xC0, 0x30, 0x0C, 0x06, 0x01, 0x80, 0x60, 0x30, 0x0C,
0x03, 0x00, 0xC0, 0x60, 0x18, 0x06, 0x03, 0x00, 0xC0, 0x30, 0x18, 0x06,
0x01, 0x80, 0xC0, 0x30, 0x00, 0x07, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, 0x3C,
0x3C, 0x78, 0x1E, 0x70, 0x0E, 0x70, 0x0E, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
0x07, 0xE0, 0x07, 0xE0, 0x0F, 0x70, 0x0E, 0x70, 0x0E, 0x78, 0x1E, 0x3C,
0x3C, 0x1F, 0xF8, 0x1F, 0xF0, 0x07, 0xE0, 0x03, 0x03, 0x07, 0x0F, 0x3F,
0xFF, 0xFF, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xE0, 0x1F, 0xF8,
0x3F, 0xFC, 0x7C, 0x3E, 0x70, 0x0F, 0xF0, 0x0F, 0xE0, 0x07, 0xE0, 0x07,
0x00, 0x07, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0xF8,
0x03, 0xF0, 0x07, 0xC0, 0x1F, 0x00, 0x3C, 0x00, 0x38, 0x00, 0x70, 0x00,
0x60, 0x00, 0xE0, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0x07, 0xFE, 0x07, 0xFF, 0x87, 0x83, 0xC3, 0x80, 0xF3, 0x80, 0x39, 0xC0,
0x1C, 0xE0, 0x0E, 0x00, 0x07, 0x00, 0x0F, 0x00, 0x7F, 0x00, 0x3F, 0x00,
0x1F, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x00, 0x03, 0xF0, 0x01,
0xF8, 0x00, 0xFE, 0x00, 0x77, 0x00, 0x73, 0xE0, 0xF8, 0xFF, 0xF8, 0x3F,
0xF8, 0x07, 0xF0, 0x00, 0x00, 0x38, 0x00, 0x38, 0x00, 0x78, 0x00, 0xF8,
0x00, 0xF8, 0x01, 0xF8, 0x03, 0xB8, 0x03, 0x38, 0x07, 0x38, 0x0E, 0x38,
0x1C, 0x38, 0x18, 0x38, 0x38, 0x38, 0x70, 0x38, 0x60, 0x38, 0xE0, 0x38,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x38, 0x00, 0x38, 0x00, 0x38,
0x00, 0x38, 0x00, 0x38, 0x00, 0x38, 0x1F, 0xFF, 0x0F, 0xFF, 0x8F, 0xFF,
0xC7, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x39,
0xF0, 0x3F, 0xFE, 0x1F, 0xFF, 0x8F, 0x83, 0xE7, 0x00, 0xF0, 0x00, 0x3C,
0x00, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xFC, 0x00,
0xEF, 0x00, 0x73, 0xC0, 0xF0, 0xFF, 0xF8, 0x3F, 0xF8, 0x07, 0xE0, 0x00,
0x03, 0xE0, 0x0F, 0xF8, 0x1F, 0xFC, 0x3C, 0x1E, 0x38, 0x0E, 0x70, 0x0E,
0x70, 0x00, 0x60, 0x00, 0xE0, 0x00, 0xE3, 0xE0, 0xEF, 0xF8, 0xFF, 0xFC,
0xFC, 0x3E, 0xF0, 0x0E, 0xF0, 0x0F, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07,
0x60, 0x07, 0x70, 0x0F, 0x70, 0x0E, 0x3C, 0x3E, 0x3F, 0xFC, 0x1F, 0xF8,
0x07, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x06, 0x00, 0x0E,
0x00, 0x1C, 0x00, 0x18, 0x00, 0x38, 0x00, 0x70, 0x00, 0x60, 0x00, 0xE0,
0x00, 0xC0, 0x01, 0xC0, 0x01, 0x80, 0x03, 0x80, 0x03, 0x80, 0x07, 0x00,
0x07, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0E, 0x00, 0x0C, 0x00,
0x1C, 0x00, 0x1C, 0x00, 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0xFF, 0x87, 0x83,
0xC7, 0x80, 0xF3, 0x80, 0x39, 0xC0, 0x1C, 0xE0, 0x0E, 0x78, 0x0F, 0x1E,
0x0F, 0x07, 0xFF, 0x01, 0xFF, 0x03, 0xFF, 0xE3, 0xE0, 0xF9, 0xC0, 0x1D,
0xC0, 0x0F, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0xF7, 0x00,
0x73, 0xE0, 0xF8, 0xFF, 0xF8, 0x3F, 0xF8, 0x07, 0xF0, 0x00, 0x07, 0xE0,
0x1F, 0xF8, 0x3F, 0xFC, 0x7C, 0x3C, 0x70, 0x0E, 0xF0, 0x0E, 0xE0, 0x06,
0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x0F, 0x70, 0x0F, 0x78, 0x3F,
0x3F, 0xFF, 0x1F, 0xF7, 0x07, 0xC7, 0x00, 0x07, 0x00, 0x06, 0x00, 0x0E,
0x70, 0x0E, 0x70, 0x1C, 0x78, 0x3C, 0x3F, 0xF8, 0x1F, 0xF0, 0x07, 0xC0,
0xFF, 0xF0, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x80, 0xFF, 0xF0, 0x00, 0x00,
0x00, 0x07, 0xFF, 0xB6, 0xD6, 0x00, 0x00, 0x80, 0x03, 0xC0, 0x07, 0xE0,
0x0F, 0xC0, 0x3F, 0x80, 0x7E, 0x00, 0xFC, 0x01, 0xF0, 0x00, 0xE0, 0x00,
0x7C, 0x00, 0x1F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x80, 0x07, 0xF0, 0x00,
0x7E, 0x00, 0x0F, 0x00, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x80, 0x80, 0x00, 0x70, 0x00, 0x3E, 0x00, 0x0F, 0xE0, 0x00, 0xFC,
0x00, 0x1F, 0xC0, 0x03, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0x80, 0x0F, 0xC0,
0x1F, 0x80, 0x7F, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x01, 0xC0, 0x00,
0x80, 0x00, 0x00, 0x0F, 0xC0, 0x7F, 0xE1, 0xFF, 0xE3, 0xC3, 0xEF, 0x01,
0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x07, 0x00, 0x0E, 0x00, 0x38, 0x00, 0xF0,
0x07, 0xC0, 0x1F, 0x00, 0x7C, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x0E,
0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xC0, 0x03, 0x80,
0x07, 0x00, 0x0E, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x3F, 0xFF, 0x00,
0x00, 0xFF, 0xFF, 0xC0, 0x01, 0xF8, 0x0F, 0xE0, 0x03, 0xE0, 0x01, 0xF0,
0x07, 0x80, 0x00, 0xF8, 0x0F, 0x00, 0x00, 0x3C, 0x1E, 0x00, 0x00, 0x1E,
0x3C, 0x03, 0xE0, 0x1E, 0x38, 0x0F, 0xF3, 0x8E, 0x78, 0x1E, 0x3F, 0x0F,
0x70, 0x38, 0x1F, 0x07, 0x70, 0x78, 0x0F, 0x07, 0xE0, 0x70, 0x0E, 0x07,
0xE0, 0x70, 0x0E, 0x07, 0xE0, 0xE0, 0x0E, 0x07, 0xE0, 0xE0, 0x1C, 0x07,
0xE0, 0xE0, 0x1C, 0x0E, 0xE0, 0xE0, 0x1C, 0x0E, 0xE0, 0xE0, 0x38, 0x1C,
0xF0, 0x70, 0x78, 0x3C, 0x70, 0x78, 0xFC, 0x78, 0x78, 0x3F, 0xDF, 0xF0,
0x38, 0x1F, 0x0F, 0xC0, 0x3C, 0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00,
0x0F, 0x80, 0x00, 0x00, 0x07, 0xF0, 0x0E, 0x00, 0x01, 0xFF, 0xFE, 0x00,
0x00, 0x7F, 0xFE, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0xF8, 0x00, 0x03,
0xE0, 0x00, 0x0F, 0xC0, 0x00, 0x7F, 0x00, 0x01, 0xDC, 0x00, 0x07, 0x78,
0x00, 0x3C, 0xE0, 0x00, 0xE3, 0x80, 0x03, 0x8F, 0x00, 0x1E, 0x1C, 0x00,
0x70, 0x70, 0x01, 0xC1, 0xE0, 0x0E, 0x03, 0x80, 0x38, 0x0E, 0x00, 0xE0,
0x3C, 0x07, 0xFF, 0xF0, 0x1F, 0xFF, 0xE0, 0xFF, 0xFF, 0x83, 0xC0, 0x0E,
0x0E, 0x00, 0x3C, 0x78, 0x00, 0xF1, 0xE0, 0x01, 0xC7, 0x00, 0x07, 0xBC,
0x00, 0x1E, 0xF0, 0x00, 0x3B, 0x80, 0x00, 0xF0, 0xFF, 0xFC, 0x1F, 0xFF,
0xE3, 0xFF, 0xFE, 0x70, 0x03, 0xCE, 0x00, 0x3D, 0xC0, 0x03, 0xB8, 0x00,
0x77, 0x00, 0x0E, 0xE0, 0x01, 0xDC, 0x00, 0x73, 0x80, 0x1E, 0x7F, 0xFF,
0x8F, 0xFF, 0xF1, 0xFF, 0xFF, 0x38, 0x00, 0xF7, 0x00, 0x0E, 0xE0, 0x00,
0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x03,
0xF8, 0x00, 0xF7, 0xFF, 0xFC, 0xFF, 0xFF, 0x1F, 0xFF, 0x80, 0x00, 0xFF,
0x00, 0x0F, 0xFF, 0x00, 0xFF, 0xFE, 0x07, 0xE0, 0x7C, 0x3E, 0x00, 0x78,
0xF0, 0x00, 0xE7, 0x80, 0x03, 0xDC, 0x00, 0x07, 0x70, 0x00, 0x03, 0x80,
0x00, 0x0E, 0x00, 0x00, 0x38, 0x00, 0x00, 0xE0, 0x00, 0x03, 0x80, 0x00,
0x0E, 0x00, 0x00, 0x38, 0x00, 0x00, 0xE0, 0x00, 0x1D, 0xC0, 0x00, 0x77,
0x00, 0x03, 0xDE, 0x00, 0x0E, 0x3C, 0x00, 0x78, 0xF8, 0x03, 0xC1, 0xF8,
0x1F, 0x03, 0xFF, 0xF8, 0x03, 0xFF, 0xC0, 0x03, 0xF8, 0x00, 0xFF, 0xF8,
0x0F, 0xFF, 0xE0, 0xFF, 0xFF, 0x0E, 0x00, 0xF8, 0xE0, 0x03, 0xCE, 0x00,
0x1C, 0xE0, 0x00, 0xEE, 0x00, 0x0E, 0xE0, 0x00, 0xFE, 0x00, 0x07, 0xE0,
0x00, 0x7E, 0x00, 0x07, 0xE0, 0x00, 0x7E, 0x00, 0x07, 0xE0, 0x00, 0x7E,
0x00, 0x07, 0xE0, 0x00, 0x7E, 0x00, 0x0F, 0xE0, 0x00, 0xEE, 0x00, 0x0E,
0xE0, 0x01, 0xEE, 0x00, 0x3C, 0xE0, 0x0F, 0x8F, 0xFF, 0xF0, 0xFF, 0xFE,
0x0F, 0xFF, 0x80, 0xFF, 0xFF, 0xBF, 0xFF, 0xEF, 0xFF, 0xFB, 0x80, 0x00,
0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x00, 0xE0, 0x00, 0x38,
0x00, 0x0E, 0x00, 0x03, 0xFF, 0xFE, 0xFF, 0xFF, 0xBF, 0xFF, 0xEE, 0x00,
0x03, 0x80, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x00,
0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x0E, 0x00,
0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38,
0x00, 0x1F, 0xFF, 0xCF, 0xFF, 0xE7, 0xFF, 0xF3, 0x80, 0x01, 0xC0, 0x00,
0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x07, 0x00,
0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x7F,
0x80, 0x03, 0xFF, 0xE0, 0x07, 0xFF, 0xF8, 0x0F, 0x80, 0xFC, 0x1E, 0x00,
0x3E, 0x3C, 0x00, 0x0E, 0x78, 0x00, 0x0F, 0x70, 0x00, 0x07, 0x70, 0x00,
0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x00, 0x00, 0xE0, 0x03,
0xFF, 0xE0, 0x03, 0xFF, 0xE0, 0x03, 0xFF, 0xE0, 0x00, 0x07, 0xF0, 0x00,
0x07, 0x70, 0x00, 0x07, 0x70, 0x00, 0x0F, 0x78, 0x00, 0x0F, 0x3C, 0x00,
0x1F, 0x1E, 0x00, 0x3F, 0x0F, 0xC0, 0xF7, 0x07, 0xFF, 0xE7, 0x03, 0xFF,
0xC3, 0x00, 0xFF, 0x03, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0,
0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0,
0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80,
0x03, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00,
0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1C, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFC, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00,
0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0,
0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1F, 0x80, 0x7E,
0x01, 0xF8, 0x07, 0xE0, 0x1F, 0xC0, 0xF7, 0x87, 0x9F, 0xFE, 0x3F, 0xF0,
0x3F, 0x00, 0xE0, 0x01, 0xEE, 0x00, 0x3C, 0xE0, 0x07, 0x8E, 0x00, 0xF0,
0xE0, 0x1E, 0x0E, 0x03, 0xE0, 0xE0, 0x7C, 0x0E, 0x0F, 0x80, 0xE1, 0xF0,
0x0E, 0x1E, 0x00, 0xE3, 0xC0, 0x0E, 0x7C, 0x00, 0xEF, 0xE0, 0x0F, 0xCE,
0x00, 0xF8, 0xF0, 0x0F, 0x07, 0x80, 0xE0, 0x3C, 0x0E, 0x03, 0xC0, 0xE0,
0x1E, 0x0E, 0x00, 0xF0, 0xE0, 0x0F, 0x0E, 0x00, 0x78, 0xE0, 0x03, 0xCE,
0x00, 0x3C, 0xE0, 0x01, 0xEE, 0x00, 0x0F, 0xE0, 0x01, 0xC0, 0x03, 0x80,
0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01,
0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70,
0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00,
0x38, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xF8, 0x00, 0x1F, 0xF8,
0x00, 0x1F, 0xF8, 0x00, 0x1F, 0xFC, 0x00, 0x3F, 0xFC, 0x00, 0x3F, 0xFC,
0x00, 0x3F, 0xEE, 0x00, 0x77, 0xEE, 0x00, 0x77, 0xEE, 0x00, 0x77, 0xE7,
0x00, 0xE7, 0xE7, 0x00, 0xE7, 0xE7, 0x00, 0xE7, 0xE3, 0x81, 0xC7, 0xE3,
0x81, 0xC7, 0xE3, 0x81, 0xC7, 0xE1, 0xC3, 0x87, 0xE1, 0xC3, 0x87, 0xE1,
0xC3, 0x87, 0xE0, 0xE7, 0x07, 0xE0, 0xE7, 0x07, 0xE0, 0xE7, 0x07, 0xE0,
0x7E, 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x7E, 0x07, 0xE0, 0x3C, 0x07, 0xE0,
0x3C, 0x07, 0xF0, 0x00, 0x7F, 0x00, 0x07, 0xF8, 0x00, 0x7F, 0xC0, 0x07,
0xFC, 0x00, 0x7F, 0xE0, 0x07, 0xEF, 0x00, 0x7E, 0x70, 0x07, 0xE7, 0x80,
0x7E, 0x3C, 0x07, 0xE1, 0xC0, 0x7E, 0x1E, 0x07, 0xE0, 0xE0, 0x7E, 0x0F,
0x07, 0xE0, 0x78, 0x7E, 0x03, 0x87, 0xE0, 0x3C, 0x7E, 0x01, 0xE7, 0xE0,
0x0E, 0x7E, 0x00, 0xF7, 0xE0, 0x07, 0xFE, 0x00, 0x3F, 0xE0, 0x03, 0xFE,
0x00, 0x1F, 0xE0, 0x01, 0xFE, 0x00, 0x0F, 0x00, 0x7F, 0x00, 0x01, 0xFF,
0xF0, 0x01, 0xFF, 0xFC, 0x01, 0xF0, 0x1F, 0x01, 0xE0, 0x03, 0xC1, 0xE0,
0x00, 0xF1, 0xE0, 0x00, 0x3C, 0xE0, 0x00, 0x0E, 0x70, 0x00, 0x07, 0x70,
0x00, 0x03, 0xF8, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x3F,
0x00, 0x00, 0x1F, 0x80, 0x00, 0x0F, 0xC0, 0x00, 0x07, 0xE0, 0x00, 0x03,
0xB8, 0x00, 0x03, 0x9C, 0x00, 0x01, 0xCF, 0x00, 0x01, 0xE3, 0xC0, 0x01,
0xE0, 0xF0, 0x01, 0xE0, 0x3E, 0x03, 0xE0, 0x0F, 0xFF, 0xE0, 0x03, 0xFF,
0xE0, 0x00, 0x3F, 0x80, 0x00, 0xFF, 0xFC, 0x3F, 0xFF, 0x8F, 0xFF, 0xF3,
0x80, 0x3E, 0xE0, 0x03, 0xF8, 0x00, 0x7E, 0x00, 0x1F, 0x80, 0x07, 0xE0,
0x01, 0xF8, 0x00, 0x7E, 0x00, 0x3F, 0x80, 0x1E, 0xFF, 0xFF, 0x3F, 0xFF,
0x8F, 0xFF, 0xC3, 0x80, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03,
0x80, 0x00, 0xE0, 0x00, 0x38, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x00, 0xE0,
0x00, 0x38, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x01, 0xFF, 0xF0, 0x01, 0xFF,
0xFC, 0x01, 0xF0, 0x1F, 0x01, 0xE0, 0x03, 0xC1, 0xE0, 0x00, 0xF1, 0xE0,
0x00, 0x3C, 0xE0, 0x00, 0x0E, 0x70, 0x00, 0x07, 0x70, 0x00, 0x01, 0xF8,
0x00, 0x00, 0xFC, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x1F,
0x80, 0x00, 0x0F, 0xC0, 0x00, 0x07, 0xE0, 0x00, 0x07, 0xB8, 0x00, 0x03,
0x9C, 0x00, 0x01, 0xCF, 0x00, 0x39, 0xE3, 0xC0, 0x1F, 0xE0, 0xF0, 0x07,
0xE0, 0x3E, 0x03, 0xF0, 0x0F, 0xFF, 0xFC, 0x03, 0xFF, 0xEE, 0x00, 0x3F,
0x83, 0x80, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x20, 0xFF, 0xFE, 0x0F, 0xFF,
0xF8, 0xFF, 0xFF, 0xCE, 0x00, 0x3C, 0xE0, 0x01, 0xEE, 0x00, 0x0E, 0xE0,
0x00, 0xEE, 0x00, 0x0E, 0xE0, 0x00, 0xEE, 0x00, 0x0E, 0xE0, 0x01, 0xCE,
0x00, 0x3C, 0xFF, 0xFF, 0x8F, 0xFF, 0xF0, 0xFF, 0xFF, 0x8E, 0x00, 0x3C,
0xE0, 0x01, 0xEE, 0x00, 0x0E, 0xE0, 0x00, 0xEE, 0x00, 0x0E, 0xE0, 0x00,
0xEE, 0x00, 0x0E, 0xE0, 0x00, 0xEE, 0x00, 0x0E, 0xE0, 0x00, 0xFE, 0x00,
0x0F, 0x03, 0xFC, 0x00, 0xFF, 0xF0, 0x1F, 0xFF, 0x83, 0xE0, 0x7C, 0x38,
0x01, 0xE7, 0x00, 0x0E, 0x70, 0x00, 0xE7, 0x00, 0x00, 0x70, 0x00, 0x07,
0x80, 0x00, 0x3E, 0x00, 0x01, 0xFE, 0x00, 0x0F, 0xFE, 0x00, 0x3F, 0xF8,
0x00, 0x3F, 0xE0, 0x00, 0x3E, 0x00, 0x00, 0xF0, 0x00, 0x07, 0xE0, 0x00,
0x7E, 0x00, 0x07, 0xF0, 0x00, 0x77, 0x80, 0x0E, 0x7C, 0x03, 0xE3, 0xFF,
0xFC, 0x1F, 0xFF, 0x80, 0x3F, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0x80, 0x70, 0x00, 0x0E, 0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07,
0x00, 0x00, 0xE0, 0x00, 0x1C, 0x00, 0x03, 0x80, 0x00, 0x70, 0x00, 0x0E,
0x00, 0x01, 0xC0, 0x00, 0x38, 0x00, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x1C,
0x00, 0x03, 0x80, 0x00, 0x70, 0x00, 0x0E, 0x00, 0x01, 0xC0, 0x00, 0x38,
0x00, 0x07, 0x00, 0x00, 0xE0, 0x00, 0x1C, 0x00, 0xE0, 0x00, 0xFC, 0x00,
0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00,
0x3F, 0x00, 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00,
0x7E, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x3F, 0x00, 0x07, 0xE0, 0x00,
0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x7F, 0x00, 0x1E, 0xF0, 0x07,
0x9F, 0x01, 0xF1, 0xFF, 0xFC, 0x1F, 0xFE, 0x00, 0x7F, 0x00, 0xE0, 0x00,
0x7F, 0x80, 0x03, 0xFC, 0x00, 0x1C, 0xE0, 0x01, 0xE7, 0x80, 0x0F, 0x3C,
0x00, 0x70, 0xE0, 0x07, 0x87, 0x80, 0x3C, 0x1C, 0x01, 0xC0, 0xE0, 0x0E,
0x07, 0x80, 0xE0, 0x1C, 0x07, 0x00, 0xE0, 0x38, 0x07, 0x83, 0x80, 0x1C,
0x1C, 0x00, 0xE0, 0xE0, 0x07, 0x8E, 0x00, 0x1C, 0x70, 0x00, 0xE3, 0x80,
0x07, 0xB8, 0x00, 0x1D, 0xC0, 0x00, 0xEE, 0x00, 0x07, 0xE0, 0x00, 0x1F,
0x00, 0x00, 0xF8, 0x00, 0x03, 0x80, 0x00, 0x70, 0x03, 0xC0, 0x0F, 0x70,
0x03, 0xC0, 0x0F, 0x78, 0x03, 0xE0, 0x0F, 0x78, 0x03, 0xE0, 0x0E, 0x38,
0x07, 0xE0, 0x0E, 0x38, 0x07, 0xF0, 0x1E, 0x3C, 0x07, 0x70, 0x1E, 0x3C,
0x07, 0x70, 0x1C, 0x1C, 0x0E, 0x70, 0x1C, 0x1C, 0x0E, 0x38, 0x3C, 0x1C,
0x0E, 0x38, 0x3C, 0x1E, 0x1E, 0x38, 0x38, 0x0E, 0x1C, 0x38, 0x38, 0x0E,
0x1C, 0x1C, 0x38, 0x0E, 0x1C, 0x1C, 0x78, 0x0F, 0x3C, 0x1C, 0x70, 0x07,
0x38, 0x0E, 0x70, 0x07, 0x38, 0x0E, 0x70, 0x07, 0x38, 0x0E, 0x70, 0x07,
0x70, 0x0E, 0xE0, 0x03, 0xF0, 0x07, 0xE0, 0x03, 0xF0, 0x07, 0xE0, 0x03,
0xF0, 0x07, 0xE0, 0x03, 0xE0, 0x03, 0xC0, 0x01, 0xE0, 0x03, 0xC0, 0x01,
0xE0, 0x03, 0xC0, 0xF0, 0x00, 0x7B, 0xC0, 0x07, 0x8F, 0x00, 0x38, 0x78,
0x03, 0xC1, 0xE0, 0x3C, 0x07, 0x81, 0xC0, 0x3C, 0x1E, 0x00, 0xF1, 0xE0,
0x03, 0x8E, 0x00, 0x1E, 0xF0, 0x00, 0x7F, 0x00, 0x01, 0xF0, 0x00, 0x0F,
0x80, 0x00, 0x7C, 0x00, 0x07, 0xF0, 0x00, 0x3B, 0x80, 0x03, 0xDE, 0x00,
0x3C, 0x78, 0x01, 0xC1, 0xC0, 0x1E, 0x0F, 0x01, 0xE0, 0x3C, 0x0E, 0x00,
0xE0, 0xF0, 0x07, 0x8F, 0x00, 0x1E, 0x70, 0x00, 0xF7, 0x80, 0x03, 0xC0,
0xF0, 0x00, 0x3C, 0xF0, 0x00, 0x78, 0xF0, 0x01, 0xE1, 0xE0, 0x03, 0x81,
0xE0, 0x0F, 0x01, 0xC0, 0x1C, 0x03, 0xC0, 0x78, 0x03, 0xC1, 0xE0, 0x07,
0x83, 0x80, 0x07, 0x8F, 0x00, 0x07, 0x1C, 0x00, 0x0F, 0x78, 0x00, 0x0E,
0xE0, 0x00, 0x0F, 0x80, 0x00, 0x1F, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38,
0x00, 0x00, 0x70, 0x00, 0x00, 0xE0, 0x00, 0x01, 0xC0, 0x00, 0x03, 0x80,
0x00, 0x07, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x1C, 0x00, 0x00, 0x38, 0x00,
0x00, 0x70, 0x00, 0x7F, 0xFF, 0xEF, 0xFF, 0xFD, 0xFF, 0xFF, 0x80, 0x00,
0xF0, 0x00, 0x3C, 0x00, 0x0F, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E,
0x00, 0x07, 0x80, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x0F, 0x00, 0x03, 0xC0,
0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x7C, 0x00,
0x0F, 0x00, 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xF8, 0xE3, 0x8E, 0x38, 0xE3,
0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xE3,
0x8E, 0x38, 0xE3, 0x8F, 0xFF, 0xFC, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x60,
0x0C, 0x03, 0x00, 0xC0, 0x18, 0x06, 0x01, 0x80, 0x20, 0x0C, 0x03, 0x00,
0x40, 0x18, 0x06, 0x01, 0x80, 0x30, 0x0C, 0x03, 0x00, 0x60, 0x18, 0x06,
0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7,
0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7,
0x1C, 0x7F, 0xFF, 0xFC, 0x07, 0x00, 0x78, 0x03, 0xC0, 0x3F, 0x01, 0xD8,
0x0C, 0xE0, 0xE3, 0x06, 0x1C, 0x70, 0xE3, 0x83, 0x18, 0x1D, 0xC0, 0x6C,
0x03, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xF0, 0xF0, 0xE0, 0xE0,
0xE0, 0x07, 0xF0, 0x0F, 0xFC, 0x0F, 0xFF, 0x0F, 0x03, 0xC7, 0x00, 0xE0,
0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0xFE, 0x0F, 0xFF, 0x1F, 0xF3,
0x9F, 0x01, 0xCF, 0x00, 0xE7, 0x00, 0x73, 0x80, 0x79, 0xE0, 0xFC, 0x7F,
0xEF, 0x9F, 0xE3, 0xC7, 0xE1, 0xE0, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE3, 0xE0, 0xEF, 0xF8,
0xFF, 0xFC, 0xFC, 0x3E, 0xF8, 0x1E, 0xF0, 0x0E, 0xE0, 0x0F, 0xE0, 0x07,
0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0E,
0xF8, 0x1E, 0xFC, 0x3C, 0xEF, 0xFC, 0xEF, 0xF8, 0xE3, 0xE0, 0x07, 0xF0,
0x1F, 0xF8, 0x3F, 0xFC, 0x3C, 0x1E, 0x78, 0x0E, 0x70, 0x07, 0xE0, 0x00,
0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x07,
0x70, 0x07, 0x78, 0x0E, 0x7C, 0x1E, 0x3F, 0xFC, 0x1F, 0xF8, 0x07, 0xE0,
0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00,
0x1C, 0x00, 0x0E, 0x0F, 0xC7, 0x1F, 0xFB, 0x9F, 0xFF, 0xDF, 0x07, 0xEF,
0x01, 0xF7, 0x00, 0x7F, 0x80, 0x3F, 0x80, 0x0F, 0xC0, 0x07, 0xE0, 0x03,
0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x77, 0x00, 0x7B, 0xC0, 0x7D, 0xF0,
0x7E, 0x7F, 0xFB, 0x1F, 0xF9, 0x83, 0xF0, 0xC0, 0x07, 0xE0, 0x1F, 0xF8,
0x3F, 0xFC, 0x7C, 0x1E, 0x70, 0x0E, 0x60, 0x06, 0xE0, 0x07, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0x70, 0x07,
0x78, 0x0E, 0x3C, 0x1E, 0x3F, 0xFC, 0x1F, 0xF8, 0x07, 0xE0, 0x0E, 0x3C,
0xF9, 0xC3, 0x87, 0x0E, 0x7F, 0xFF, 0xFC, 0xE1, 0xC3, 0x87, 0x0E, 0x1C,
0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0x07, 0xC7, 0x1F,
0xF7, 0x3F, 0xFF, 0x3C, 0x3F, 0x78, 0x0F, 0x70, 0x0F, 0xE0, 0x07, 0xE0,
0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0x70,
0x0F, 0x78, 0x0F, 0x7C, 0x3F, 0x3F, 0xF7, 0x1F, 0xE7, 0x07, 0xC7, 0x00,
0x07, 0x00, 0x07, 0x00, 0x0E, 0x70, 0x0E, 0x78, 0x1E, 0x3F, 0xFC, 0x1F,
0xF8, 0x07, 0xE0, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00,
0x1C, 0x00, 0x38, 0x00, 0x71, 0xF8, 0xE7, 0xFD, 0xDF, 0xFB, 0xF0, 0xFF,
0xC0, 0xFF, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x07, 0xE0, 0x0F, 0xC0,
0x1F, 0x80, 0x3F, 0x00, 0x7E, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x07,
0xE0, 0x0F, 0xC0, 0x1C, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFC, 0x1C, 0x71, 0xC7, 0x00, 0x00, 0x07, 0x1C, 0x71, 0xC7, 0x1C,
0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C,
0x73, 0xFF, 0xFB, 0xC0, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00,
0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x3C, 0xE0, 0x78, 0xE0, 0xF0,
0xE1, 0xE0, 0xE3, 0xC0, 0xE7, 0x80, 0xEF, 0x00, 0xEF, 0x80, 0xFF, 0x80,
0xFB, 0xC0, 0xF1, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0xE0, 0x70, 0xE0, 0x78,
0xE0, 0x3C, 0xE0, 0x1C, 0xE0, 0x1E, 0xE0, 0x0E, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xE3, 0xE0, 0xF8, 0xE7, 0xF1, 0xFE,
0xEF, 0xFB, 0xFE, 0xF8, 0x7F, 0x0F, 0xF0, 0x3E, 0x07, 0xF0, 0x1C, 0x07,
0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07,
0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07,
0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07, 0xE0, 0x1C, 0x07,
0xE0, 0x1C, 0x07, 0xE3, 0xF1, 0xCF, 0xFB, 0xBF, 0xF7, 0xE1, 0xFF, 0x81,
0xFE, 0x01, 0xF8, 0x03, 0xF0, 0x07, 0xE0, 0x0F, 0xC0, 0x1F, 0x80, 0x3F,
0x00, 0x7E, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x07, 0xE0, 0x0F, 0xC0,
0x1F, 0x80, 0x38, 0x07, 0xF0, 0x0F, 0xFE, 0x0F, 0xFF, 0x87, 0x83, 0xC7,
0x80, 0xF3, 0x80, 0x3B, 0x80, 0x1F, 0xC0, 0x07, 0xE0, 0x03, 0xF0, 0x01,
0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x3B, 0x80, 0x39, 0xE0, 0x3C, 0x78,
0x3C, 0x3F, 0xFE, 0x0F, 0xFE, 0x01, 0xFC, 0x00, 0xE3, 0xE0, 0xE7, 0xF8,
0xEF, 0xFC, 0xFC, 0x3E, 0xF8, 0x1E, 0xF0, 0x0E, 0xE0, 0x0F, 0xE0, 0x07,
0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xF0, 0x0E,
0xF8, 0x1E, 0xFC, 0x3E, 0xFF, 0xFC, 0xEF, 0xF8, 0xE3, 0xE0, 0xE0, 0x00,
0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0x07, 0xE1,
0x8F, 0xFC, 0xCF, 0xFF, 0x67, 0x83, 0xF7, 0x80, 0xFB, 0x80, 0x3F, 0xC0,
0x1F, 0xC0, 0x07, 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E,
0x00, 0x3B, 0x80, 0x3D, 0xE0, 0x3E, 0xF8, 0x3F, 0x3F, 0xFF, 0x8F, 0xFD,
0xC1, 0xF8, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00,
0x07, 0x00, 0x03, 0x80, 0xE3, 0xF7, 0xFB, 0xFF, 0x8F, 0x07, 0x83, 0x81,
0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0, 0xE0, 0x70,
0x38, 0x00, 0x0F, 0xC0, 0xFF, 0x87, 0xFF, 0x3C, 0x1E, 0xE0, 0x3B, 0x80,
0x0E, 0x00, 0x3C, 0x00, 0x7F, 0x00, 0xFF, 0x80, 0xFF, 0x80, 0x7F, 0x00,
0x3F, 0x80, 0x7E, 0x01, 0xFC, 0x1F, 0x7F, 0xF8, 0xFF, 0xC1, 0xFC, 0x00,
0x38, 0x70, 0xE1, 0xCF, 0xFF, 0xFF, 0x9C, 0x38, 0x70, 0xE1, 0xC3, 0x87,
0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0xE7, 0xC7, 0x80, 0xE0, 0x0F, 0xC0,
0x1F, 0x80, 0x3F, 0x00, 0x7E, 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x07,
0xE0, 0x0F, 0xC0, 0x1F, 0x80, 0x3F, 0x00, 0x7E, 0x00, 0xFC, 0x03, 0xFC,
0x0F, 0xFC, 0x3F, 0x7F, 0xEE, 0xFF, 0x9C, 0x7E, 0x38, 0x70, 0x03, 0xB8,
0x03, 0x9C, 0x01, 0xC7, 0x00, 0xE3, 0x80, 0xE1, 0xC0, 0x70, 0x70, 0x38,
0x38, 0x38, 0x1C, 0x1C, 0x07, 0x0E, 0x03, 0x8E, 0x01, 0xC7, 0x00, 0x77,
0x00, 0x3B, 0x80, 0x1D, 0xC0, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xF0, 0x00,
0x70, 0x00, 0xF0, 0x1C, 0x03, 0xB8, 0x1F, 0x03, 0xDC, 0x0F, 0x81, 0xCE,
0x07, 0xC0, 0xE7, 0x83, 0xE0, 0x71, 0xC3, 0xB8, 0x70, 0xE1, 0xDC, 0x38,
0x70, 0xEE, 0x1C, 0x1C, 0x63, 0x0E, 0x0E, 0x71, 0xCE, 0x07, 0x38, 0xE7,
0x03, 0x9C, 0x73, 0x80, 0xEC, 0x19, 0x80, 0x7E, 0x0F, 0xC0, 0x3F, 0x07,
0xE0, 0x0F, 0x83, 0xF0, 0x07, 0x80, 0xF0, 0x03, 0xC0, 0x78, 0x01, 0xE0,
0x3C, 0x00, 0x70, 0x07, 0x38, 0x0E, 0x3C, 0x1C, 0x1C, 0x1C, 0x0E, 0x38,
0x0F, 0x70, 0x07, 0x70, 0x03, 0xE0, 0x03, 0xC0, 0x01, 0xC0, 0x03, 0xE0,
0x07, 0xE0, 0x07, 0x70, 0x0E, 0x78, 0x1E, 0x38, 0x1C, 0x1C, 0x38, 0x1E,
0x78, 0x0E, 0x70, 0x07, 0x70, 0x07, 0x38, 0x03, 0x9C, 0x01, 0xC7, 0x01,
0xC3, 0x80, 0xE1, 0xC0, 0x70, 0x70, 0x70, 0x38, 0x38, 0x1C, 0x3C, 0x07,
0x1C, 0x03, 0x8E, 0x01, 0xCE, 0x00, 0x77, 0x00, 0x3B, 0x80, 0x1F, 0x80,
0x07, 0xC0, 0x03, 0xE0, 0x01, 0xE0, 0x00, 0x70, 0x00, 0x38, 0x00, 0x38,
0x00, 0x1C, 0x00, 0x1E, 0x00, 0x0E, 0x00, 0x3F, 0x00, 0x1F, 0x00, 0x0F,
0x00, 0x00, 0x7F, 0xFC, 0xFF, 0xF9, 0xFF, 0xF0, 0x00, 0xE0, 0x03, 0x80,
0x0E, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x1C, 0x00, 0x70,
0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x78, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xF8, 0x07, 0x0F, 0x1F, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C,
0x1C, 0x1C, 0x1C, 0x1C, 0x38, 0xF8, 0xE0, 0xF8, 0x38, 0x1C, 0x1C, 0x1C,
0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1F, 0x0F, 0x07, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xE0, 0xF0, 0xF8, 0x38,
0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x1C, 0x1F,
0x07, 0x1F, 0x1C, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
0x38, 0x38, 0xF8, 0xF0, 0xE0, 0x38, 0x00, 0xFC, 0x03, 0xFC, 0x1F, 0x3E,
0x3C, 0x1F, 0xE0, 0x1F, 0x80, 0x1E, 0x00};
const GFXglyph FreeSans18pt7bGlyphs[] PROGMEM = {
{0, 0, 0, 9, 0, 1}, // 0x20 ' '
{0, 3, 26, 12, 4, -25}, // 0x21 '!'
{10, 9, 9, 12, 1, -24}, // 0x22 '"'
{21, 19, 24, 19, 0, -23}, // 0x23 '#'
{78, 16, 30, 19, 2, -26}, // 0x24 '$'
{138, 29, 25, 31, 1, -24}, // 0x25 '%'
{229, 20, 25, 23, 2, -24}, // 0x26 '&'
{292, 3, 9, 7, 2, -24}, // 0x27 '''
{296, 8, 33, 12, 3, -25}, // 0x28 '('
{329, 8, 33, 12, 1, -25}, // 0x29 ')'
{362, 10, 10, 14, 2, -25}, // 0x2A '*'
{375, 16, 16, 20, 2, -15}, // 0x2B '+'
{407, 3, 9, 10, 3, -3}, // 0x2C ','
{411, 8, 3, 12, 2, -10}, // 0x2D '-'
{414, 3, 4, 9, 3, -3}, // 0x2E '.'
{416, 10, 26, 10, 0, -25}, // 0x2F '/'
{449, 16, 25, 19, 2, -24}, // 0x30 '0'
{499, 8, 25, 19, 4, -24}, // 0x31 '1'
{524, 16, 25, 19, 2, -24}, // 0x32 '2'
{574, 17, 25, 19, 1, -24}, // 0x33 '3'
{628, 16, 25, 19, 1, -24}, // 0x34 '4'
{678, 17, 25, 19, 1, -24}, // 0x35 '5'
{732, 16, 25, 19, 2, -24}, // 0x36 '6'
{782, 16, 25, 19, 2, -24}, // 0x37 '7'
{832, 17, 25, 19, 1, -24}, // 0x38 '8'
{886, 16, 25, 19, 1, -24}, // 0x39 '9'
{936, 3, 19, 9, 3, -18}, // 0x3A ':'
{944, 3, 24, 9, 3, -18}, // 0x3B ';'
{953, 17, 17, 20, 2, -16}, // 0x3C '<'
{990, 17, 9, 20, 2, -12}, // 0x3D '='
{1010, 17, 17, 20, 2, -16}, // 0x3E '>'
{1047, 15, 26, 19, 3, -25}, // 0x3F '?'
{1096, 32, 31, 36, 1, -25}, // 0x40 '@'
{1220, 22, 26, 23, 1, -25}, // 0x41 'A'
{1292, 19, 26, 23, 3, -25}, // 0x42 'B'
{1354, 22, 26, 25, 1, -25}, // 0x43 'C'
{1426, 20, 26, 24, 3, -25}, // 0x44 'D'
{1491, 18, 26, 22, 3, -25}, // 0x45 'E'
{1550, 17, 26, 21, 3, -25}, // 0x46 'F'
{1606, 24, 26, 27, 1, -25}, // 0x47 'G'
{1684, 19, 26, 25, 3, -25}, // 0x48 'H'
{1746, 3, 26, 10, 4, -25}, // 0x49 'I'
{1756, 14, 26, 18, 1, -25}, // 0x4A 'J'
{1802, 20, 26, 24, 3, -25}, // 0x4B 'K'
{1867, 15, 26, 20, 3, -25}, // 0x4C 'L'
{1916, 24, 26, 30, 3, -25}, // 0x4D 'M'
{1994, 20, 26, 26, 3, -25}, // 0x4E 'N'
{2059, 25, 26, 27, 1, -25}, // 0x4F 'O'
{2141, 18, 26, 23, 3, -25}, // 0x50 'P'
{2200, 25, 28, 27, 1, -25}, // 0x51 'Q'
{2288, 20, 26, 25, 3, -25}, // 0x52 'R'
{2353, 20, 26, 23, 1, -25}, // 0x53 'S'
{2418, 19, 26, 22, 1, -25}, // 0x54 'T'
{2480, 19, 26, 25, 3, -25}, // 0x55 'U'
{2542, 21, 26, 23, 1, -25}, // 0x56 'V'
{2611, 32, 26, 33, 0, -25}, // 0x57 'W'
{2715, 21, 26, 23, 1, -25}, // 0x58 'X'
{2784, 23, 26, 24, 0, -25}, // 0x59 'Y'
{2859, 19, 26, 22, 1, -25}, // 0x5A 'Z'
{2921, 6, 33, 10, 2, -25}, // 0x5B '['
{2946, 10, 26, 10, 0, -25}, // 0x5C '\'
{2979, 6, 33, 10, 1, -25}, // 0x5D ']'
{3004, 13, 13, 16, 2, -24}, // 0x5E '^'
{3026, 21, 2, 19, -1, 5}, // 0x5F '_'
{3032, 7, 5, 9, 1, -25}, // 0x60 '`'
{3037, 17, 19, 19, 1, -18}, // 0x61 'a'
{3078, 16, 26, 20, 2, -25}, // 0x62 'b'
{3130, 16, 19, 18, 1, -18}, // 0x63 'c'
{3168, 17, 26, 20, 1, -25}, // 0x64 'd'
{3224, 16, 19, 19, 1, -18}, // 0x65 'e'
{3262, 7, 26, 10, 1, -25}, // 0x66 'f'
{3285, 16, 27, 19, 1, -18}, // 0x67 'g'
{3339, 15, 26, 19, 2, -25}, // 0x68 'h'
{3388, 3, 26, 8, 2, -25}, // 0x69 'i'
{3398, 6, 34, 9, 0, -25}, // 0x6A 'j'
{3424, 16, 26, 18, 2, -25}, // 0x6B 'k'
{3476, 3, 26, 7, 2, -25}, // 0x6C 'l'
{3486, 24, 19, 28, 2, -18}, // 0x6D 'm'
{3543, 15, 19, 19, 2, -18}, // 0x6E 'n'
{3579, 17, 19, 19, 1, -18}, // 0x6F 'o'
{3620, 16, 25, 20, 2, -18}, // 0x70 'p'
{3670, 17, 25, 20, 1, -18}, // 0x71 'q'
{3724, 9, 19, 12, 2, -18}, // 0x72 'r'
{3746, 14, 19, 17, 2, -18}, // 0x73 's'
{3780, 7, 23, 10, 1, -22}, // 0x74 't'
{3801, 15, 19, 19, 2, -18}, // 0x75 'u'
{3837, 17, 19, 17, 0, -18}, // 0x76 'v'
{3878, 25, 19, 25, 0, -18}, // 0x77 'w'
{3938, 16, 19, 17, 0, -18}, // 0x78 'x'
{3976, 17, 27, 17, 0, -18}, // 0x79 'y'
{4034, 15, 19, 17, 1, -18}, // 0x7A 'z'
{4070, 8, 33, 12, 1, -25}, // 0x7B '{'
{4103, 2, 33, 9, 3, -25}, // 0x7C '|'
{4112, 8, 33, 12, 3, -25}, // 0x7D '}'
{4145, 15, 7, 18, 1, -15}}; // 0x7E '~'
const GFXfont FreeSans18pt7b PROGMEM = {(uint8_t *)FreeSans18pt7bBitmaps,
(GFXglyph *)FreeSans18pt7bGlyphs, 0x20,
0x7E, 42};
// Approx. 4831 bytes

File diff suppressed because it is too large Load Diff

View File

View File

@ -0,0 +1,421 @@
const uint8_t FreeSans9pt8bBitmaps[] PROGMEM = {
0x00, 0xFF, 0xFF, 0x83, 0xC0, 0xDD, 0xD9, 0x90, 0x09, 0x82, 0x61, 0x90,
0x64, 0x7F, 0xC4, 0xC1, 0x30, 0xC8, 0xFF, 0xBF, 0xE2, 0x20, 0x98, 0x26,
0x00, 0x10, 0x3C, 0x76, 0xD3, 0x93, 0xD0, 0xF0, 0x7C, 0x17, 0x13, 0x91,
0x91, 0xD3, 0x7E, 0x10, 0x10, 0x00, 0x21, 0xE1, 0x08, 0x84, 0x23, 0x20,
0x8C, 0x83, 0x64, 0x07, 0x10, 0x00, 0x8E, 0x06, 0x6C, 0x13, 0x10, 0xCC,
0x42, 0x11, 0x18, 0x78, 0x1C, 0x0F, 0x86, 0x21, 0x88, 0x36, 0x0F, 0x03,
0x81, 0xB2, 0xC6, 0xA0, 0xEC, 0x13, 0x1E, 0x7C, 0xC0, 0xFE, 0x80, 0x12,
0x26, 0x44, 0xCC, 0xCC, 0x44, 0x46, 0x22, 0x10, 0x84, 0x46, 0x22, 0x23,
0x33, 0x22, 0x26, 0x4C, 0x80, 0x25, 0x5C, 0xA5, 0x00, 0x08, 0x04, 0x02,
0x01, 0x0F, 0xF8, 0x40, 0x20, 0x10, 0x08, 0x00, 0xF0, 0xFF, 0xC0, 0x08,
0x44, 0x21, 0x10, 0x84, 0x42, 0x11, 0x08, 0x00, 0x18, 0x7E, 0x42, 0xC3,
0xC3, 0x83, 0x83, 0x83, 0x83, 0xC3, 0xC3, 0x46, 0x7C, 0x11, 0x7F, 0x11,
0x11, 0x11, 0x11, 0x10, 0x3C, 0x7E, 0xC3, 0xC1, 0x81, 0x03, 0x06, 0x1C,
0x30, 0x60, 0xC0, 0xFF, 0xFF, 0x3C, 0x7E, 0xC3, 0xC3, 0x03, 0x06, 0x1C,
0x03, 0x03, 0x83, 0x83, 0xC3, 0x7E, 0x02, 0x03, 0x01, 0x81, 0x41, 0xA1,
0x90, 0x88, 0xC4, 0xC2, 0x7F, 0xC0, 0x80, 0x40, 0x20, 0x7F, 0x7F, 0x40,
0x40, 0xC0, 0xFE, 0xC3, 0x03, 0x01, 0x01, 0x83, 0xC3, 0x7E, 0x1C, 0x7E,
0x43, 0xC3, 0xC0, 0xBC, 0xFE, 0xC3, 0xC1, 0xC1, 0xC3, 0x63, 0x7E, 0xFF,
0xFF, 0x03, 0x02, 0x04, 0x0C, 0x08, 0x18, 0x10, 0x30, 0x30, 0x20, 0x20,
0x3C, 0x7E, 0xC3, 0xC3, 0xC3, 0x66, 0x7C, 0xC3, 0x83, 0x81, 0x83, 0xC3,
0x7E, 0x38, 0x7E, 0xC2, 0x83, 0x83, 0x83, 0xC3, 0x7D, 0x13, 0x03, 0xC3,
0xC6, 0x7C, 0xC0, 0x03, 0xC0, 0xC0, 0x03, 0xD6, 0x00, 0x01, 0xC3, 0x8F,
0x0C, 0x07, 0x00, 0xF0, 0x0E, 0x01, 0x80, 0xFF, 0x80, 0x00, 0x1F, 0xF0,
0x00, 0x60, 0x1E, 0x01, 0xC0, 0x38, 0x3C, 0x71, 0xE0, 0x80, 0x00, 0x3E,
0x63, 0x43, 0xC1, 0x03, 0x06, 0x0C, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18,
0x07, 0xF0, 0x1C, 0x1C, 0x30, 0x0E, 0x60, 0x06, 0x43, 0xD3, 0xC6, 0x33,
0x8C, 0x31, 0x8C, 0x23, 0x88, 0x23, 0x8C, 0x62, 0xCC, 0xEC, 0xC7, 0x38,
0x60, 0x00, 0x38, 0x00, 0x0F, 0xF0, 0x01, 0x80, 0x06, 0x00, 0xF0, 0x0F,
0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x06, 0x60,
0x66, 0x06, 0xC0, 0x30, 0xFF, 0x30, 0x6C, 0x0F, 0x03, 0xC0, 0xB0, 0xEF,
0xFB, 0x03, 0xC0, 0xF0, 0x1C, 0x0F, 0x07, 0xFF, 0x00, 0x1F, 0x86, 0x19,
0x81, 0xE0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x78, 0x0D, 0x81,
0xB8, 0x63, 0xF8, 0xFE, 0x20, 0xC8, 0x1A, 0x03, 0x80, 0xE0, 0x38, 0x0E,
0x03, 0x80, 0xE0, 0x38, 0x1A, 0x1C, 0xFE, 0x00, 0xFF, 0xC0, 0x20, 0x10,
0x08, 0x04, 0x03, 0xFD, 0x00, 0x80, 0x40, 0x20, 0x10, 0x0F, 0xF8, 0xFF,
0x80, 0x80, 0x80, 0x80, 0x80, 0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
0x1F, 0x83, 0x0E, 0x60, 0x6C, 0x03, 0xC0, 0x0C, 0x00, 0x83, 0xFC, 0x3F,
0xC0, 0x3C, 0x03, 0x60, 0x77, 0x0F, 0x1F, 0x90, 0xC0, 0x78, 0x0F, 0x01,
0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFE, 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xE0,
0x3C, 0x06, 0xFF, 0xF8, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0xC3, 0xC3, 0x66, 0x7E, 0xC0, 0xD8, 0x33, 0x0C, 0x63, 0x0C, 0xC1,
0xB8, 0x3F, 0x07, 0x30, 0xC3, 0x18, 0x63, 0x06, 0x60, 0x6C, 0x04, 0xC0,
0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18,
0x0F, 0xF8, 0xE0, 0x3F, 0x01, 0xFC, 0x1F, 0xE0, 0xFD, 0x05, 0xEC, 0x6F,
0x63, 0x79, 0x13, 0xCD, 0x9E, 0x6C, 0xF1, 0x47, 0x8E, 0x3C, 0x71, 0x80,
0xE0, 0x7C, 0x0F, 0xC1, 0xEC, 0x3D, 0x87, 0x98, 0xF1, 0x1E, 0x33, 0xC3,
0x78, 0x2F, 0x07, 0xE0, 0x7C, 0x0E, 0x1F, 0x87, 0x0E, 0x60, 0x6C, 0x03,
0xC0, 0x38, 0x01, 0x80, 0x18, 0x01, 0xC0, 0x3C, 0x03, 0x60, 0x67, 0x0E,
0x1F, 0x80, 0xFF, 0x41, 0xE0, 0x70, 0x38, 0x1C, 0x0F, 0xFD, 0x00, 0x80,
0x40, 0x20, 0x10, 0x08, 0x00, 0x1F, 0x87, 0x0E, 0x60, 0x6C, 0x03, 0xC0,
0x38, 0x01, 0x80, 0x18, 0x01, 0xC0, 0x3C, 0x03, 0x61, 0xE7, 0x0E, 0x1F,
0xF0, 0x01, 0xFF, 0x20, 0x68, 0x0E, 0x03, 0x80, 0xE0, 0x6F, 0xF2, 0x06,
0x80, 0xA0, 0x28, 0x0A, 0x03, 0x80, 0xC0, 0x3F, 0x18, 0x6C, 0x0F, 0x03,
0xC0, 0x1E, 0x01, 0xF0, 0x0F, 0x00, 0xF0, 0x3C, 0x0D, 0x87, 0x3F, 0x80,
0xFF, 0xE0, 0x80, 0x10, 0x02, 0x00, 0x40, 0x08, 0x01, 0x00, 0x20, 0x04,
0x00, 0x80, 0x10, 0x02, 0x00, 0x40, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80,
0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x3C, 0x0B, 0x86, 0x7F, 0x00, 0xC0,
0x58, 0x1B, 0x03, 0x30, 0x46, 0x18, 0x43, 0x0C, 0x41, 0x98, 0x13, 0x03,
0x40, 0x78, 0x07, 0x00, 0xC0, 0x41, 0xC1, 0xB0, 0xE1, 0x98, 0x50, 0xCC,
0x28, 0x62, 0x36, 0x31, 0x9B, 0x10, 0xC8, 0x98, 0x64, 0x6C, 0x16, 0x36,
0x0B, 0x0A, 0x07, 0x07, 0x03, 0x83, 0x80, 0xC1, 0xC0, 0x60, 0x63, 0x0C,
0x30, 0xC1, 0x98, 0x0F, 0x00, 0x70, 0x06, 0x00, 0xF0, 0x09, 0x81, 0x98,
0x30, 0xC6, 0x06, 0x60, 0x60, 0x60, 0x36, 0x06, 0x30, 0xC1, 0x0C, 0x19,
0x80, 0xD0, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06,
0x00, 0x7F, 0xC0, 0x18, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x80, 0x60,
0x18, 0x06, 0x01, 0x80, 0x30, 0x0F, 0xFE, 0xFC, 0xCC, 0xCC, 0xCC, 0xCC,
0xCC, 0xCC, 0xCC, 0xF0, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10,
0x80, 0xF3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xF0, 0x30, 0x60,
0xA2, 0x44, 0xD8, 0xA1, 0x00, 0xFF, 0xC0, 0x63, 0x3C, 0x3F, 0x30, 0x80,
0x60, 0x73, 0xFB, 0x0D, 0x06, 0xC7, 0x7D, 0xC0, 0x80, 0x80, 0x80, 0x9C,
0xBE, 0xC3, 0xC1, 0xC1, 0x81, 0xC1, 0xC3, 0xE3, 0xBE, 0x38, 0x7E, 0xC2,
0x82, 0x80, 0x80, 0x80, 0x82, 0xC6, 0x7C, 0x01, 0x80, 0xC0, 0x63, 0xB3,
0xFB, 0x0D, 0x06, 0x83, 0xC1, 0xA0, 0xD0, 0x6C, 0x73, 0xE8, 0x18, 0x7E,
0xC3, 0xC3, 0x81, 0xFF, 0x80, 0xC3, 0xC3, 0x7E, 0x39, 0x08, 0x4F, 0x90,
0x84, 0x21, 0x08, 0x42, 0x00, 0x38, 0x7F, 0xC7, 0x83, 0x83, 0x83, 0x83,
0x83, 0xC7, 0x7F, 0x03, 0x82, 0xE6, 0x7C, 0xC0, 0xC0, 0xC0, 0xDC, 0xFE,
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xF0, 0xFF, 0xFF, 0xC0,
0x6C, 0x06, 0xDB, 0x6D, 0xB6, 0xDB, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC6,
0xCC, 0xD8, 0xF8, 0xD8, 0xCC, 0xC4, 0xC6, 0xC3, 0xFF, 0xFF, 0xFF, 0xC0,
0x1C, 0x67, 0xFF, 0xB0, 0xC7, 0x84, 0x3C, 0x21, 0xE1, 0x0F, 0x08, 0x78,
0x43, 0xC2, 0x1E, 0x10, 0xC0, 0x1C, 0xFE, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0x38, 0x7E, 0xC3, 0x83, 0x81, 0x81, 0x81, 0xC3, 0xC6,
0x7E, 0x1C, 0xBE, 0xC3, 0xC1, 0xC1, 0x81, 0xC1, 0xC3, 0xE3, 0xBE, 0x80,
0x80, 0x80, 0x80, 0x1C, 0x1F, 0x58, 0x68, 0x34, 0x1E, 0x0D, 0x06, 0x83,
0x63, 0x9F, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x1F, 0xF9, 0x8C, 0x63, 0x18,
0xC6, 0x00, 0x39, 0xFB, 0x1C, 0x0E, 0x07, 0x81, 0xC1, 0xC7, 0xF8, 0x21,
0x09, 0xF2, 0x10, 0x84, 0x21, 0x08, 0x70, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC7, 0x7F, 0x41, 0x31, 0x98, 0xC4, 0x42, 0x61, 0xA0, 0x50,
0x38, 0x18, 0x00, 0xC7, 0x12, 0x38, 0x99, 0x4C, 0xCA, 0x62, 0xDA, 0x14,
0xD0, 0xE3, 0x87, 0x18, 0x18, 0xC0, 0x63, 0x13, 0x0D, 0x03, 0x81, 0xC0,
0xE0, 0xD8, 0xC6, 0x43, 0x00, 0x41, 0x31, 0x98, 0xC4, 0x43, 0x61, 0xA0,
0x50, 0x38, 0x18, 0x0C, 0x06, 0x0E, 0x06, 0x00, 0xFE, 0x08, 0x30, 0xC3,
0x0C, 0x10, 0x60, 0xFE, 0x36, 0x66, 0x66, 0x4C, 0x84, 0x66, 0x66, 0x66,
0x30, 0xFF, 0xFF, 0x80, 0xC4, 0x44, 0x44, 0x46, 0x36, 0x44, 0x44, 0x44,
0x80, 0x60, 0x59, 0x8F, 0xFF, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03,
0x01, 0x80, 0xC0, 0x60, 0x30, 0x1F, 0xF8, 0x00, 0xF0, 0x7F, 0xFF, 0xC0,
0x08, 0x1C, 0x7E, 0x4B, 0xC9, 0xC8, 0xC8, 0xC8, 0xCB, 0x6B, 0x3E, 0x08,
0x08, 0x1F, 0x0C, 0xE6, 0x19, 0x82, 0x60, 0x08, 0x0F, 0xE0, 0x40, 0x10,
0x04, 0x03, 0x01, 0xF2, 0x7F, 0xC0, 0x07, 0x07, 0xF3, 0x04, 0x80, 0x60,
0x3F, 0xE6, 0x03, 0xFC, 0x60, 0x18, 0x03, 0x00, 0x61, 0x0F, 0xC0, 0xC0,
0xD0, 0x22, 0x18, 0xCC, 0x12, 0x07, 0x80, 0xC1, 0xFE, 0x0C, 0x1F, 0xE0,
0xC0, 0x30, 0x0C, 0x00, 0x12, 0x07, 0x80, 0xC0, 0x00, 0x3F, 0x18, 0x6C,
0x0F, 0x03, 0xC0, 0x1E, 0x01, 0xF0, 0x0F, 0x00, 0xF0, 0x3C, 0x0D, 0x87,
0x3F, 0x80, 0x3C, 0x66, 0x62, 0x62, 0x30, 0x5C, 0x8E, 0x83, 0xC1, 0x61,
0x3B, 0x1E, 0x06, 0xC6, 0x46, 0x7E, 0x38, 0x6C, 0x70, 0x41, 0xCF, 0xD8,
0xE0, 0x70, 0x3C, 0x0E, 0x0E, 0x3F, 0xC0, 0x1F, 0xC0, 0xC1, 0x86, 0x31,
0x33, 0x66, 0x88, 0xCA, 0x60, 0x29, 0x80, 0xE6, 0x12, 0x88, 0xCB, 0x36,
0x66, 0x71, 0x0C, 0x18, 0x1F, 0xC0, 0xF4, 0x9D, 0x29, 0x74, 0x1F, 0x21,
0x99, 0xA6, 0x4C, 0x90, 0xFF, 0xFF, 0xC0, 0x20, 0x10, 0x08, 0xFF, 0x1F,
0xC0, 0xC1, 0x86, 0xF1, 0x33, 0xF6, 0x88, 0x4A, 0x23, 0x28, 0xF8, 0xE2,
0x32, 0x88, 0xCB, 0x23, 0x66, 0x01, 0x0C, 0x18, 0x1F, 0xC0, 0xFF, 0xC0,
0xF4, 0x63, 0x17, 0x00, 0x08, 0x04, 0x02, 0x01, 0x0F, 0xF8, 0x40, 0x20,
0x10, 0x08, 0x00, 0x3F, 0xE0, 0x39, 0x34, 0x43, 0x19, 0x84, 0x3F, 0x31,
0x2C, 0xC6, 0x08, 0x1C, 0xDE, 0x11, 0x01, 0x60, 0x18, 0x00, 0x07, 0xFC,
0x01, 0x80, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x38, 0x06, 0x01, 0x80, 0x60,
0x18, 0x03, 0x00, 0xFF, 0xE0, 0xC3, 0x61, 0xB0, 0xD8, 0x6C, 0x36, 0x1B,
0x0D, 0x8E, 0xFF, 0xE0, 0x30, 0x18, 0x0C, 0x00, 0x3F, 0x7A, 0xFA, 0xFA,
0xFA, 0xFA, 0xFA, 0x7A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
0xC0, 0x6C, 0x50, 0x40, 0x0F, 0xE0, 0x83, 0x0C, 0x30, 0xC1, 0x06, 0x0F,
0xE0, 0x27, 0x92, 0x49, 0xF4, 0x63, 0x19, 0x38, 0x1F, 0x93, 0x26, 0x59,
0x98, 0x40, 0x3F, 0xFF, 0x71, 0xC0, 0x40, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
0x80, 0xC0, 0x80, 0xFF, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x40, 0xC0,
0x61, 0xC0, 0x3F, 0xFF, 0x38, 0x30, 0xFD, 0xFB, 0x0E, 0x1C, 0x18, 0x38,
0x10, 0x70, 0x3F, 0xE0, 0x40, 0x61, 0x83, 0xC7, 0x84, 0xFD, 0xF8, 0x19,
0x81, 0x98, 0x00, 0x06, 0x03, 0x60, 0x63, 0x0C, 0x10, 0xC1, 0x98, 0x0D,
0x00, 0xF0, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x18,
0x18, 0x00, 0x00, 0x18, 0x18, 0x30, 0x60, 0xC0, 0x83, 0xC2, 0xC6, 0x7C,
0x0C, 0x00, 0x40, 0x02, 0x00, 0x00, 0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90,
0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x06, 0x60, 0x66, 0x06,
0xC0, 0x30, 0x01, 0x80, 0x30, 0x06, 0x00, 0x00, 0x06, 0x00, 0xF0, 0x0F,
0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2, 0x06, 0x60,
0x66, 0x06, 0xC0, 0x30, 0x06, 0x00, 0xF0, 0x09, 0x00, 0x00, 0x06, 0x00,
0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C, 0x3F, 0xC2,
0x06, 0x60, 0x66, 0x06, 0xC0, 0x30, 0x0C, 0x81, 0x30, 0x00, 0x00, 0x60,
0x0F, 0x00, 0xF0, 0x09, 0x01, 0x98, 0x19, 0x81, 0x08, 0x30, 0xC3, 0xFC,
0x20, 0x66, 0x06, 0x60, 0x6C, 0x03, 0x19, 0x81, 0x98, 0x00, 0x00, 0x60,
0x0F, 0x00, 0xF0, 0x09, 0x01, 0x98, 0x19, 0x81, 0x08, 0x30, 0xC3, 0xFC,
0x20, 0x66, 0x06, 0x60, 0x6C, 0x03, 0x0F, 0x00, 0x90, 0x0F, 0x00, 0x00,
0x06, 0x00, 0xF0, 0x0F, 0x00, 0x90, 0x19, 0x81, 0x98, 0x10, 0x83, 0x0C,
0x3F, 0xC2, 0x06, 0x60, 0x66, 0x06, 0xC0, 0x30, 0x07, 0xFF, 0x83, 0x60,
0x01, 0x30, 0x01, 0x98, 0x00, 0x8C, 0x00, 0xC6, 0x00, 0x63, 0xFC, 0x61,
0x80, 0x3F, 0xC0, 0x10, 0x60, 0x18, 0x30, 0x0C, 0x18, 0x0C, 0x0F, 0xF8,
0x1F, 0x86, 0x19, 0x81, 0xE0, 0x3C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0,
0x78, 0x0D, 0x81, 0xB8, 0x63, 0xF8, 0x08, 0x01, 0x80, 0x10, 0x0E, 0x00,
0x30, 0x08, 0x02, 0x00, 0x0F, 0xFC, 0x02, 0x01, 0x00, 0x80, 0x40, 0x3F,
0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0xFF, 0x80, 0x04, 0x04, 0x04, 0x00,
0x0F, 0xFC, 0x02, 0x01, 0x00, 0x80, 0x40, 0x3F, 0xD0, 0x08, 0x04, 0x02,
0x01, 0x00, 0xFF, 0x80, 0x18, 0x0E, 0x09, 0x00, 0x0F, 0xFC, 0x02, 0x01,
0x00, 0x80, 0x40, 0x3F, 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0xFF, 0x80,
0x66, 0x33, 0x00, 0x1F, 0xF8, 0x04, 0x02, 0x01, 0x00, 0x80, 0x7F, 0xA0,
0x10, 0x08, 0x04, 0x02, 0x01, 0xFF, 0xC6, 0x20, 0x22, 0x22, 0x22, 0x22,
0x22, 0x22, 0x20, 0x36, 0x40, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x40,
0x32, 0xB2, 0x02, 0x10, 0x84, 0x21, 0x08, 0x42, 0x10, 0x84, 0x20, 0xCF,
0x30, 0x08, 0x20, 0x82, 0x08, 0x20, 0x82, 0x08, 0x20, 0x82, 0x08, 0x3F,
0x82, 0x0C, 0x20, 0x62, 0x03, 0x20, 0x32, 0x03, 0xFC, 0x32, 0x03, 0x20,
0x32, 0x03, 0x20, 0x62, 0x1C, 0x3F, 0x80, 0x09, 0x03, 0xE0, 0x00, 0x70,
0x3E, 0x07, 0xE0, 0xF6, 0x1E, 0xC3, 0xCC, 0x78, 0x8F, 0x19, 0xE1, 0xBC,
0x17, 0x83, 0xF0, 0x3E, 0x07, 0x0C, 0x00, 0x40, 0x02, 0x00, 0x00, 0x1F,
0x87, 0x0E, 0x60, 0x6C, 0x03, 0xC0, 0x38, 0x01, 0x80, 0x18, 0x01, 0xC0,
0x3C, 0x03, 0x60, 0x67, 0x0E, 0x1F, 0x80, 0x03, 0x00, 0x20, 0x04, 0x00,
0x00, 0x1F, 0x87, 0x0E, 0x60, 0x6C, 0x03, 0xC0, 0x38, 0x01, 0x80, 0x18,
0x01, 0xC0, 0x3C, 0x03, 0x60, 0x67, 0x0E, 0x1F, 0x80, 0x06, 0x00, 0xF0,
0x09, 0x00, 0x00, 0x1F, 0x87, 0x0E, 0x60, 0x6C, 0x03, 0xC0, 0x38, 0x01,
0x80, 0x18, 0x01, 0xC0, 0x3C, 0x03, 0x60, 0x67, 0x0E, 0x1F, 0x80, 0x0C,
0x81, 0x30, 0x00, 0x01, 0xF8, 0x70, 0xE6, 0x06, 0xC0, 0x3C, 0x03, 0x80,
0x18, 0x01, 0x80, 0x1C, 0x03, 0xC0, 0x36, 0x06, 0x70, 0xE1, 0xF8, 0x19,
0x81, 0x98, 0x00, 0x01, 0xF8, 0x70, 0xE6, 0x06, 0xC0, 0x3C, 0x03, 0x80,
0x18, 0x01, 0x80, 0x1C, 0x03, 0xC0, 0x36, 0x06, 0x70, 0xE1, 0xF8, 0x81,
0x89, 0xA1, 0x83, 0x89, 0xA1, 0x80, 0x00, 0x01, 0xF9, 0x70, 0xE6, 0x06,
0xC0, 0xBC, 0x13, 0x82, 0x18, 0x41, 0x8C, 0x1D, 0x83, 0xF0, 0x36, 0x06,
0x70, 0xE9, 0xF8, 0x30, 0x06, 0x00, 0x80, 0x00, 0x80, 0xE0, 0x38, 0x0E,
0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x3C, 0x0B, 0x86, 0x7F,
0x00, 0x06, 0x03, 0x00, 0x80, 0x00, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80,
0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x3C, 0x0B, 0x86, 0x7F, 0x00, 0x0C,
0x05, 0x03, 0x20, 0x00, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x38,
0x0E, 0x03, 0x80, 0xE0, 0x3C, 0x0B, 0x86, 0x7F, 0x00, 0x33, 0x0C, 0xC0,
0x02, 0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03,
0x80, 0xF0, 0x2E, 0x19, 0xFC, 0x01, 0x00, 0x20, 0x04, 0x00, 0x00, 0x60,
0x36, 0x06, 0x30, 0xC1, 0x0C, 0x19, 0x80, 0xD0, 0x0F, 0x00, 0x60, 0x06,
0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x80, 0x40, 0x3F, 0x9F, 0xE8,
0x1C, 0x0E, 0x07, 0x03, 0xFF, 0x7E, 0x20, 0x10, 0x08, 0x00, 0x3E, 0x31,
0xB0, 0x78, 0x3C, 0x16, 0x7B, 0x3D, 0x83, 0xC0, 0xE0, 0x70, 0x78, 0x7C,
0xF0, 0x30, 0x0C, 0x02, 0x07, 0x87, 0xE6, 0x10, 0x0C, 0x0E, 0x7F, 0x61,
0xA0, 0xD8, 0xEF, 0xB8, 0x0C, 0x0C, 0x04, 0x07, 0x87, 0xE6, 0x10, 0x0C,
0x0E, 0x7F, 0x61, 0xA0, 0xD8, 0xEF, 0xB8, 0x18, 0x16, 0x19, 0x07, 0x87,
0xE6, 0x10, 0x0C, 0x0E, 0x7F, 0x61, 0xA0, 0xD8, 0xEF, 0xB8, 0x32, 0x3E,
0x00, 0x07, 0x87, 0xE6, 0x10, 0x0C, 0x0E, 0x7F, 0x61, 0xA0, 0xD8, 0xEF,
0xB8, 0x66, 0x33, 0x00, 0x07, 0x87, 0xE6, 0x10, 0x0C, 0x0E, 0x7F, 0x61,
0xA0, 0xD8, 0xEF, 0xB8, 0x18, 0x12, 0x09, 0x03, 0x03, 0xC3, 0xF3, 0x08,
0x06, 0x07, 0x3F, 0xB0, 0xD0, 0x6C, 0x77, 0xDC, 0x38, 0x71, 0xFF, 0xEC,
0x30, 0xC0, 0xC1, 0x06, 0x05, 0xFF, 0xFC, 0x20, 0x20, 0xC1, 0xC7, 0x8F,
0xE7, 0xE0, 0x38, 0x7E, 0xC2, 0x82, 0x80, 0x80, 0x80, 0x83, 0xC6, 0x7C,
0x10, 0x18, 0x08, 0x78, 0x30, 0x18, 0x08, 0x18, 0x7E, 0xC3, 0xC3, 0x81,
0xFF, 0x80, 0xC3, 0xC3, 0x7E, 0x0C, 0x18, 0x10, 0x18, 0x7E, 0xC3, 0xC3,
0x81, 0xFF, 0x80, 0xC3, 0xC3, 0x7E, 0x18, 0x34, 0x22, 0x18, 0x7E, 0xC3,
0xC3, 0x81, 0xFF, 0x80, 0xC3, 0xC3, 0x7E, 0x66, 0x66, 0x00, 0x18, 0x7E,
0xC3, 0xC3, 0x81, 0xFF, 0x80, 0xC3, 0xC3, 0x7E, 0x42, 0x10, 0x22, 0x22,
0x22, 0x22, 0x20, 0x6C, 0x80, 0x44, 0x44, 0x44, 0x44, 0x40, 0x72, 0xA2,
0x02, 0x10, 0x84, 0x21, 0x08, 0x42, 0x00, 0xCF, 0x30, 0x00, 0x10, 0x41,
0x04, 0x10, 0x41, 0x04, 0x10, 0x6C, 0x30, 0x4C, 0x3E, 0x7E, 0xC3, 0x83,
0x81, 0x81, 0x81, 0xC3, 0xC6, 0x7E, 0x32, 0x7C, 0x00, 0x1C, 0xFE, 0xC3,
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x30, 0x10, 0x08, 0x38, 0x7E,
0xC3, 0x83, 0x81, 0x81, 0x81, 0xC3, 0xC6, 0x7E, 0x0C, 0x18, 0x10, 0x38,
0x7E, 0xC3, 0x83, 0x81, 0x81, 0x81, 0xC3, 0xC6, 0x7E, 0x18, 0x2C, 0x64,
0x38, 0x7E, 0xC3, 0x83, 0x81, 0x81, 0x81, 0xC3, 0xC6, 0x7E, 0x32, 0x5C,
0x00, 0x38, 0x7E, 0xC3, 0x83, 0x81, 0x81, 0x81, 0xC3, 0xC6, 0x7E, 0x66,
0x66, 0x00, 0x38, 0x7E, 0xC3, 0x83, 0x81, 0x81, 0x81, 0xC3, 0xC6, 0x7E,
0x18, 0x0C, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x30, 0x18, 0x00, 0x1C,
0x0F, 0xE6, 0x19, 0x8E, 0x44, 0x92, 0x25, 0x09, 0xC6, 0x63, 0x1F, 0xC0,
0x00, 0x30, 0x10, 0x08, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC7, 0x7F, 0x0C, 0x18, 0x10, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC7, 0x7F, 0x18, 0x2C, 0x64, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC7, 0x7F, 0x66, 0x66, 0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0xC7, 0x7F, 0x0C, 0x04, 0x04, 0x00, 0x04, 0x13, 0x19,
0x8C, 0x44, 0x36, 0x1A, 0x05, 0x03, 0x81, 0x80, 0xC0, 0x60, 0xE0, 0x60,
0x00, 0x80, 0x80, 0x80, 0x9C, 0xBE, 0xC3, 0xC1, 0xC1, 0x81, 0xC1, 0xC3,
0xE3, 0xBE, 0x80, 0x80, 0x80, 0x80, 0x22, 0x11, 0x00, 0x00, 0x04, 0x13,
0x19, 0x8C, 0x44, 0x36, 0x1A, 0x05, 0x03, 0x81, 0x80, 0xC0, 0x60, 0xE0,
0x60, 0x00 };
const GFXglyph FreeSans9pt8bGlyphs[] PROGMEM = {
{ 0, 1, 1, 5, 0, 0 }, // 0x20 ' ' U+0020
{ 1, 2, 13, 5, 2, -12 }, // 0x21 '!' U+0021
{ 5, 4, 5, 6, 1, -12 }, // 0x22 '"' U+0022
{ 8, 10, 13, 10, 0, -12 }, // 0x23 '#' U+0023
{ 25, 8, 16, 10, 1, -13 }, // 0x24 '$' U+0024
{ 41, 14, 13, 16, 1, -12 }, // 0x25 '%' U+0025
{ 64, 10, 13, 12, 1, -12 }, // 0x26 '&' U+0026
{ 81, 2, 5, 3, 1, -12 }, // 0x27 ''' U+0027
{ 83, 4, 17, 6, 1, -12 }, // 0x28 '(' U+0028
{ 92, 4, 17, 6, 1, -12 }, // 0x29 ')' U+0029
{ 101, 5, 5, 7, 1, -12 }, // 0x2a '*' U+002A
{ 105, 9, 9, 11, 1, -8 }, // 0x2b '+' U+002B
{ 116, 1, 5, 5, 2, -1 }, // 0x2c ',' U+002C
{ 117, 4, 2, 6, 1, -5 }, // 0x2d '-' U+002D
{ 118, 1, 2, 5, 2, -1 }, // 0x2e '.' U+002E
{ 119, 5, 13, 5, 0, -12 }, // 0x2f '/' U+002F
{ 128, 8, 13, 10, 1, -12 }, // 0x30 '0' U+0030
{ 141, 4, 13, 10, 2, -12 }, // 0x31 '1' U+0031
{ 148, 8, 13, 10, 1, -12 }, // 0x32 '2' U+0032
{ 161, 8, 13, 10, 1, -12 }, // 0x33 '3' U+0033
{ 174, 9, 13, 10, 0, -12 }, // 0x34 '4' U+0034
{ 189, 8, 13, 10, 1, -12 }, // 0x35 '5' U+0035
{ 202, 8, 13, 10, 1, -12 }, // 0x36 '6' U+0036
{ 215, 8, 13, 10, 1, -12 }, // 0x37 '7' U+0037
{ 228, 8, 13, 10, 1, -12 }, // 0x38 '8' U+0038
{ 241, 8, 13, 10, 1, -12 }, // 0x39 '9' U+0039
{ 254, 2, 9, 5, 2, -8 }, // 0x3a ':' U+003A
{ 257, 2, 12, 5, 2, -8 }, // 0x3b ';' U+003B
{ 260, 9, 9, 11, 1, -8 }, // 0x3c '<' U+003C
{ 271, 9, 4, 11, 1, -5 }, // 0x3d '=' U+003D
{ 276, 9, 9, 11, 1, -8 }, // 0x3e '>' U+003E
{ 287, 8, 13, 10, 1, -12 }, // 0x3f '?' U+003F
{ 300, 16, 16, 18, 1, -12 }, // 0x40 '@' U+0040
{ 332, 12, 13, 12, 0, -12 }, // 0x41 'A' U+0041
{ 352, 10, 13, 12, 1, -12 }, // 0x42 'B' U+0042
{ 369, 11, 13, 13, 1, -12 }, // 0x43 'C' U+0043
{ 387, 10, 13, 13, 2, -12 }, // 0x44 'D' U+0044
{ 404, 9, 13, 12, 2, -12 }, // 0x45 'E' U+0045
{ 419, 8, 13, 11, 2, -12 }, // 0x46 'F' U+0046
{ 432, 12, 13, 14, 1, -12 }, // 0x47 'G' U+0047
{ 452, 11, 13, 13, 1, -12 }, // 0x48 'H' U+0048
{ 470, 1, 13, 5, 2, -12 }, // 0x49 'I' U+0049
{ 472, 8, 13, 9, 0, -12 }, // 0x4a 'J' U+004A
{ 485, 11, 13, 12, 1, -12 }, // 0x4b 'K' U+004B
{ 503, 9, 13, 10, 1, -12 }, // 0x4c 'L' U+004C
{ 518, 13, 13, 15, 1, -12 }, // 0x4d 'M' U+004D
{ 540, 11, 13, 13, 1, -12 }, // 0x4e 'N' U+004E
{ 558, 12, 13, 14, 1, -12 }, // 0x4f 'O' U+004F
{ 578, 9, 13, 12, 2, -12 }, // 0x50 'P' U+0050
{ 593, 12, 14, 14, 1, -12 }, // 0x51 'Q' U+0051
{ 614, 10, 13, 13, 2, -12 }, // 0x52 'R' U+0052
{ 631, 10, 13, 12, 1, -12 }, // 0x53 'S' U+0053
{ 648, 11, 13, 11, 0, -12 }, // 0x54 'T' U+0054
{ 666, 10, 13, 13, 2, -12 }, // 0x55 'U' U+0055
{ 683, 11, 13, 12, 1, -12 }, // 0x56 'V' U+0056
{ 701, 17, 13, 17, 0, -12 }, // 0x57 'W' U+0057
{ 729, 12, 13, 12, 0, -12 }, // 0x58 'X' U+0058
{ 749, 12, 13, 12, 0, -12 }, // 0x59 'Y' U+0059
{ 769, 11, 13, 11, 0, -12 }, // 0x5a 'Z' U+005A
{ 787, 4, 17, 5, 1, -12 }, // 0x5b '[' U+005B
{ 796, 5, 13, 5, 0, -12 }, // 0x5c '\' U+005C
{ 805, 4, 17, 5, 0, -12 }, // 0x5d ']' U+005D
{ 814, 7, 7, 8, 1, -12 }, // 0x5e '^' U+005E
{ 821, 10, 1, 10, 0, 3 }, // 0x5f '_' U+005F
{ 823, 4, 2, 6, 0, -12 }, // 0x60 '`' U+0060
{ 824, 9, 10, 10, 1, -9 }, // 0x61 'a' U+0061
{ 836, 8, 13, 10, 1, -12 }, // 0x62 'b' U+0062
{ 849, 8, 10, 9, 1, -9 }, // 0x63 'c' U+0063
{ 859, 9, 13, 10, 0, -12 }, // 0x64 'd' U+0064
{ 874, 8, 10, 10, 1, -9 }, // 0x65 'e' U+0065
{ 884, 5, 13, 5, 0, -12 }, // 0x66 'f' U+0066
{ 893, 8, 14, 10, 1, -9 }, // 0x67 'g' U+0067
{ 907, 8, 13, 10, 1, -12 }, // 0x68 'h' U+0068
{ 920, 2, 13, 4, 1, -12 }, // 0x69 'i' U+0069
{ 924, 3, 17, 4, 0, -12 }, // 0x6a 'j' U+006A
{ 931, 8, 13, 9, 1, -12 }, // 0x6b 'k' U+006B
{ 944, 2, 13, 4, 1, -12 }, // 0x6c 'l' U+006C
{ 948, 13, 10, 15, 1, -9 }, // 0x6d 'm' U+006D
{ 965, 8, 10, 10, 1, -9 }, // 0x6e 'n' U+006E
{ 975, 8, 10, 10, 1, -9 }, // 0x6f 'o' U+006F
{ 985, 8, 14, 10, 1, -9 }, // 0x70 'p' U+0070
{ 999, 9, 14, 10, 0, -9 }, // 0x71 'q' U+0071
{ 1015, 5, 10, 6, 1, -9 }, // 0x72 'r' U+0072
{ 1022, 7, 10, 9, 1, -9 }, // 0x73 's' U+0073
{ 1031, 5, 12, 5, 0, -11 }, // 0x74 't' U+0074
{ 1039, 8, 9, 10, 1, -8 }, // 0x75 'u' U+0075
{ 1048, 9, 9, 9, 0, -8 }, // 0x76 'v' U+0076
{ 1059, 13, 9, 13, 0, -8 }, // 0x77 'w' U+0077
{ 1074, 9, 9, 9, 0, -8 }, // 0x78 'x' U+0078
{ 1085, 9, 13, 9, 0, -8 }, // 0x79 'y' U+0079
{ 1100, 7, 9, 9, 1, -8 }, // 0x7a 'z' U+007A
{ 1108, 4, 17, 6, 1, -12 }, // 0x7b '{' U+007B
{ 1117, 1, 17, 5, 2, -12 }, // 0x7c '|' U+007C
{ 1120, 4, 17, 6, 1, -12 }, // 0x7d '}' U+007D
{ 1129, 8, 3, 11, 1, -7 }, // 0x7e '~' U+007E
{ 1132, 9, 13, 13, 2, -12 }, // 0x7f 'REPLACEMENT CHARACTER *' U+2370
{ 1147, 1, 1, 5, 0, 0 }, // 0x80 'NO-BREAK SPACE' U+00A0
{ 1148, 2, 13, 5, 2, -8 }, // 0x81 'INVERTED EXCLAMATION MARK' U+00A1
{ 1152, 8, 13, 10, 1, -10 }, // 0x82 'CENT SIGN' U+00A2
{ 1165, 10, 13, 10, 0, -12 }, // 0x83 'POUND SIGN' U+00A3
{ 1182, 10, 13, 12, 1, -12 }, // 0x84 'EURO SIGN *' U+20AC
{ 1199, 10, 13, 10, 0, -12 }, // 0x85 'YEN SIGN' U+00A5
{ 1216, 10, 17, 12, 1, -16 }, // 0x86 'LATIN CAPITAL LETTER S WITH CARON *' U+0160
{ 1238, 8, 17, 10, 1, -12 }, // 0x87 'SECTION SIGN' U+00A7
{ 1255, 7, 13, 9, 1, -12 }, // 0x88 'LATIN SMALL LETTER S WITH CARON *' U+0161
{ 1267, 14, 13, 13, 0, -12 }, // 0x89 'COPYRIGHT SIGN' U+00A9
{ 1290, 5, 8, 7, 1, -12 }, // 0x8a 'FEMININE ORDINAL INDICATOR' U+00AA
{ 1295, 6, 6, 8, 1, -7 }, // 0x8b 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00AB
{ 1300, 9, 5, 11, 1, -6 }, // 0x8c 'NOT SIGN' U+00AC
{ 1306, 4, 2, 6, 1, -5 }, // 0x8d 'SOFT HYPHEN' U+00AD
{ 1307, 14, 13, 13, 0, -12 }, // 0x8e 'REGISTERED SIGN' U+00AE
{ 1330, 5, 2, 6, 0, -12 }, // 0x8f 'MACRON' U+00AF
{ 1332, 5, 5, 11, 3, -11 }, // 0x90 'DEGREE SIGN' U+00B0
{ 1336, 9, 11, 11, 1, -10 }, // 0x91 'PLUS-MINUS SIGN' U+00B1
{ 1349, 6, 8, 6, 0, -12 }, // 0x92 'SUPERSCRIPT TWO' U+00B2
{ 1355, 6, 8, 6, 0, -12 }, // 0x93 'SUPERSCRIPT THREE' U+00B3
{ 1361, 11, 17, 11, 0, -16 }, // 0x94 'LATIN CAPITAL LETTER Z WITH CARON *' U+017D
{ 1385, 9, 13, 10, 1, -8 }, // 0x95 'MICRO SIGN' U+00B5
{ 1400, 8, 16, 10, 1, -12 }, // 0x96 'PILCROW SIGN' U+00B6
{ 1416, 1, 2, 5, 2, -5 }, // 0x97 'MIDDLE DOT' U+00B7
{ 1417, 7, 13, 9, 1, -12 }, // 0x98 'LATIN SMALL LETTER Z WITH CARON *' U+017E
{ 1429, 3, 8, 6, 1, -12 }, // 0x99 'SUPERSCRIPT ONE' U+00B9
{ 1432, 5, 8, 7, 1, -12 }, // 0x9a 'MASCULINE ORDINAL INDICATOR' U+00BA
{ 1437, 6, 6, 8, 1, -7 }, // 0x9b 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' U+00BB
{ 1442, 16, 13, 18, 1, -12 }, // 0x9c 'LATIN CAPITAL LIGATURE OE *' U+0152
{ 1468, 15, 10, 17, 1, -9 }, // 0x9d 'LATIN SMALL LIGATURE OE *' U+0153
{ 1487, 12, 16, 12, 0, -15 }, // 0x9e 'LATIN CAPITAL LETTER Y WITH DIAERESIS *' U+0178
{ 1511, 8, 13, 10, 2, -8 }, // 0x9f 'INVERTED QUESTION MARK' U+00BF
{ 1524, 12, 17, 12, 0, -16 }, // 0xa0 'LATIN CAPITAL LETTER A WITH GRAVE' U+00C0
{ 1550, 12, 17, 12, 0, -16 }, // 0xa1 'LATIN CAPITAL LETTER A WITH ACUTE' U+00C1
{ 1576, 12, 17, 12, 0, -16 }, // 0xa2 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' U+00C2
{ 1602, 12, 16, 12, 0, -15 }, // 0xa3 'LATIN CAPITAL LETTER A WITH TILDE' U+00C3
{ 1626, 12, 16, 12, 0, -15 }, // 0xa4 'LATIN CAPITAL LETTER A WITH DIAERESIS' U+00C4
{ 1650, 12, 17, 12, 0, -16 }, // 0xa5 'LATIN CAPITAL LETTER A WITH RING ABOVE' U+00C5
{ 1676, 17, 13, 18, 0, -12 }, // 0xa6 'LATIN CAPITAL LETTER AE' U+00C6
{ 1704, 11, 17, 13, 1, -12 }, // 0xa7 'LATIN CAPITAL LETTER C WITH CEDILLA' U+00C7
{ 1728, 9, 17, 12, 2, -16 }, // 0xa8 'LATIN CAPITAL LETTER E WITH GRAVE' U+00C8
{ 1748, 9, 17, 12, 2, -16 }, // 0xa9 'LATIN CAPITAL LETTER E WITH ACUTE' U+00C9
{ 1768, 9, 17, 12, 2, -16 }, // 0xaa 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' U+00CA
{ 1788, 9, 16, 12, 2, -15 }, // 0xab 'LATIN CAPITAL LETTER E WITH DIAERESIS' U+00CB
{ 1806, 4, 17, 5, 0, -16 }, // 0xac 'LATIN CAPITAL LETTER I WITH GRAVE' U+00CC
{ 1815, 4, 17, 5, 1, -16 }, // 0xad 'LATIN CAPITAL LETTER I WITH ACUTE' U+00CD
{ 1824, 5, 17, 5, 0, -16 }, // 0xae 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' U+00CE
{ 1835, 6, 16, 5, 0, -15 }, // 0xaf 'LATIN CAPITAL LETTER I WITH DIAERESIS' U+00CF
{ 1847, 12, 13, 13, 0, -12 }, // 0xb0 'LATIN CAPITAL LETTER ETH' U+00D0
{ 1867, 11, 16, 13, 1, -15 }, // 0xb1 'LATIN CAPITAL LETTER N WITH TILDE' U+00D1
{ 1889, 12, 17, 14, 1, -16 }, // 0xb2 'LATIN CAPITAL LETTER O WITH GRAVE' U+00D2
{ 1915, 12, 17, 14, 1, -16 }, // 0xb3 'LATIN CAPITAL LETTER O WITH ACUTE' U+00D3
{ 1941, 12, 17, 14, 1, -16 }, // 0xb4 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' U+00D4
{ 1967, 12, 16, 14, 1, -15 }, // 0xb5 'LATIN CAPITAL LETTER O WITH TILDE' U+00D5
{ 1991, 12, 16, 14, 1, -15 }, // 0xb6 'LATIN CAPITAL LETTER O WITH DIAERESIS' U+00D6
{ 2015, 7, 7, 11, 2, -7 }, // 0xb7 'MULTIPLICATION SIGN' U+00D7
{ 2022, 12, 14, 14, 1, -13 }, // 0xb8 'LATIN CAPITAL LETTER O WITH STROKE' U+00D8
{ 2043, 10, 17, 13, 2, -16 }, // 0xb9 'LATIN CAPITAL LETTER U WITH GRAVE' U+00D9
{ 2065, 10, 17, 13, 2, -16 }, // 0xba 'LATIN CAPITAL LETTER U WITH ACUTE' U+00DA
{ 2087, 10, 17, 13, 2, -16 }, // 0xbb 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' U+00DB
{ 2109, 10, 16, 13, 2, -15 }, // 0xbc 'LATIN CAPITAL LETTER U WITH DIAERESIS' U+00DC
{ 2129, 12, 17, 12, 0, -16 }, // 0xbd 'LATIN CAPITAL LETTER Y WITH ACUTE' U+00DD
{ 2155, 9, 13, 12, 2, -12 }, // 0xbe 'LATIN CAPITAL LETTER THORN' U+00DE
{ 2170, 9, 13, 11, 1, -12 }, // 0xbf 'LATIN SMALL LETTER SHARP S' U+00DF
{ 2185, 9, 13, 10, 1, -12 }, // 0xc0 'LATIN SMALL LETTER A WITH GRAVE' U+00E0
{ 2200, 9, 13, 10, 1, -12 }, // 0xc1 'LATIN SMALL LETTER A WITH ACUTE' U+00E1
{ 2215, 9, 13, 10, 1, -12 }, // 0xc2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' U+00E2
{ 2230, 9, 13, 10, 1, -12 }, // 0xc3 'LATIN SMALL LETTER A WITH TILDE' U+00E3
{ 2245, 9, 13, 10, 1, -12 }, // 0xc4 'LATIN SMALL LETTER A WITH DIAERESIS' U+00E4
{ 2260, 9, 14, 10, 1, -13 }, // 0xc5 'LATIN SMALL LETTER A WITH RING ABOVE' U+00E5
{ 2276, 14, 10, 16, 1, -9 }, // 0xc6 'LATIN SMALL LETTER AE' U+00E6
{ 2294, 8, 14, 9, 1, -9 }, // 0xc7 'LATIN SMALL LETTER C WITH CEDILLA' U+00E7
{ 2308, 8, 13, 10, 1, -12 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8
{ 2321, 8, 13, 10, 1, -12 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9
{ 2334, 8, 13, 10, 1, -12 }, // 0xca 'LATIN SMALL LETTER E WITH CIRCUMFLEX' U+00EA
{ 2347, 8, 13, 10, 1, -12 }, // 0xcb 'LATIN SMALL LETTER E WITH DIAERESIS' U+00EB
{ 2360, 4, 13, 5, 0, -12 }, // 0xcc 'LATIN SMALL LETTER I WITH GRAVE' U+00EC
{ 2367, 4, 13, 5, 1, -12 }, // 0xcd 'LATIN SMALL LETTER I WITH ACUTE' U+00ED
{ 2374, 5, 13, 5, 0, -12 }, // 0xce 'LATIN SMALL LETTER I WITH CIRCUMFLEX' U+00EE
{ 2383, 6, 13, 5, -1, -12 }, // 0xcf 'LATIN SMALL LETTER I WITH DIAERESIS' U+00EF
{ 2393, 8, 13, 10, 1, -12 }, // 0xd0 'LATIN SMALL LETTER ETH' U+00F0
{ 2406, 8, 13, 10, 1, -12 }, // 0xd1 'LATIN SMALL LETTER N WITH TILDE' U+00F1
{ 2419, 8, 13, 10, 1, -12 }, // 0xd2 'LATIN SMALL LETTER O WITH GRAVE' U+00F2
{ 2432, 8, 13, 10, 1, -12 }, // 0xd3 'LATIN SMALL LETTER O WITH ACUTE' U+00F3
{ 2445, 8, 13, 10, 1, -12 }, // 0xd4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' U+00F4
{ 2458, 8, 13, 10, 1, -12 }, // 0xd5 'LATIN SMALL LETTER O WITH TILDE' U+00F5
{ 2471, 8, 13, 10, 1, -12 }, // 0xd6 'LATIN SMALL LETTER O WITH DIAERESIS' U+00F6
{ 2484, 9, 9, 11, 1, -8 }, // 0xd7 'DIVISION SIGN' U+00F7
{ 2495, 10, 11, 11, 0, -9 }, // 0xd8 'LATIN SMALL LETTER O WITH STROKE' U+00F8
{ 2509, 8, 13, 10, 1, -12 }, // 0xd9 'LATIN SMALL LETTER U WITH GRAVE' U+00F9
{ 2522, 8, 13, 10, 1, -12 }, // 0xda 'LATIN SMALL LETTER U WITH ACUTE' U+00FA
{ 2535, 8, 13, 10, 1, -12 }, // 0xdb 'LATIN SMALL LETTER U WITH CIRCUMFLEX' U+00FB
{ 2548, 8, 13, 10, 1, -12 }, // 0xdc 'LATIN SMALL LETTER U WITH DIAERESIS' U+00FC
{ 2561, 9, 17, 9, 0, -12 }, // 0xdd 'LATIN SMALL LETTER Y WITH ACUTE' U+00FD
{ 2581, 8, 17, 10, 1, -12 }, // 0xde 'LATIN SMALL LETTER THORN' U+00FE
{ 2598, 9, 17, 9, 0, -12 } }; // 0xdf 'LATIN SMALL LETTER Y WITH DIAERESIS' U+000FF
const GFXfont FreeSans9pt8b PROGMEM = {
(uint8_t *)FreeSans9pt8bBitmaps,
(GFXglyph *)FreeSans9pt8bGlyphs,
0x20, 0xDF, 25 };
// Approx. 3969 bytes

View File

View File

@ -0,0 +1,221 @@
const uint8_t Terminal11x16Bitmap[] = {
0x0C, 0x18, 0x78, 0xF1, 0xE3, 0xC7, 0x86, 0x0C, 0x18, 0x00, 0x00, 0xC1,
0x80, 0x33, 0x33, 0x33, 0x33, 0x0C, 0xC1, 0x98, 0x33, 0x3F, 0xF1, 0x98,
0x33, 0x0C, 0xC1, 0x98, 0xFF, 0x8C, 0xC1, 0x98, 0x33, 0x00, 0x0C, 0x06,
0x0F, 0xCF, 0xF6, 0xC3, 0x61, 0xFC, 0x7F, 0x0D, 0x86, 0xDF, 0xE7, 0xE0,
0xC0, 0x60, 0x00, 0x2E, 0x0D, 0xC3, 0xB8, 0xE0, 0x38, 0x0E, 0x03, 0x80,
0xE0, 0x38, 0x0E, 0x3B, 0x87, 0x60, 0xE0, 0x0E, 0x06, 0xC3, 0x30, 0xCC,
0x36, 0x07, 0x03, 0xC1, 0xF0, 0x66, 0xD9, 0xE6, 0x31, 0xDE, 0x3C, 0xC0,
0x1C, 0x71, 0xC3, 0x0C, 0x60, 0x07, 0x0C, 0x1C, 0x18, 0x38, 0x38, 0x38,
0x38, 0x38, 0x38, 0x18, 0x1C, 0x0C, 0x07, 0x38, 0x0C, 0x0E, 0x06, 0x07,
0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x0E, 0x0C, 0x38, 0x6D, 0xB6, 0xCF,
0xC3, 0xC7, 0xF8, 0xF0, 0xFC, 0xDB, 0x6D, 0x80, 0x0C, 0x06, 0x03, 0x0F,
0xF7, 0xF8, 0x60, 0x30, 0x18, 0x1C, 0x71, 0xC3, 0x18, 0x7F, 0xBF, 0xC0,
0x1C, 0x71, 0xC0, 0x00, 0x20, 0x0C, 0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03,
0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0x60, 0x00, 0x1F, 0x0F, 0xF9, 0x83,
0x60, 0x7C, 0x1F, 0x86, 0xF1, 0x9E, 0x63, 0xD8, 0x7E, 0x0F, 0x81, 0xB0,
0x67, 0xFC, 0x3E, 0x00, 0x06, 0x03, 0x83, 0xE0, 0xF8, 0x06, 0x01, 0x80,
0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x3F, 0xCF, 0xF0, 0x3F, 0x8F,
0xFB, 0x83, 0xE0, 0x3C, 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80,
0xE0, 0x38, 0x0F, 0xFF, 0xFF, 0xC0, 0x3F, 0x8F, 0xFB, 0x83, 0xE0, 0x30,
0x06, 0x01, 0xC7, 0xF0, 0xFC, 0x00, 0xC0, 0x0F, 0x01, 0xF0, 0x77, 0xFC,
0x7F, 0x00, 0x03, 0x80, 0xF0, 0x3E, 0x0E, 0xC3, 0x98, 0xE3, 0x38, 0x66,
0x0C, 0xFF, 0xFF, 0xFC, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0xFF, 0xFF,
0xFF, 0x00, 0x60, 0x0C, 0x01, 0xFF, 0x1F, 0xF0, 0x07, 0x00, 0x60, 0x0F,
0x01, 0xF0, 0x77, 0xFC, 0x7F, 0x00, 0x07, 0x81, 0xF0, 0x70, 0x1C, 0x07,
0x00, 0xC0, 0x3F, 0xE7, 0xFE, 0xE0, 0xF8, 0x0F, 0x01, 0xF0, 0x77, 0xFC,
0x7F, 0x00, 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0x60, 0x18, 0x03, 0x00, 0xC0,
0x18, 0x06, 0x00, 0xC0, 0x30, 0x06, 0x01, 0x80, 0x30, 0x00, 0x1F, 0x07,
0xF1, 0xC7, 0x30, 0x66, 0x0C, 0xE3, 0x8F, 0xE3, 0xFE, 0xE0, 0xF8, 0x0F,
0x01, 0xF0, 0x77, 0xFC, 0x7F, 0x00, 0x3F, 0x8F, 0xFB, 0x83, 0xE0, 0x3C,
0x07, 0xC1, 0xDF, 0xF9, 0xFF, 0x00, 0xC0, 0x38, 0x0E, 0x03, 0x83, 0xE0,
0x78, 0x00, 0x1C, 0x71, 0xC0, 0x00, 0x01, 0xC7, 0x1C, 0x1C, 0x71, 0xC0,
0x00, 0x01, 0xC7, 0x1C, 0x30, 0xC6, 0x01, 0x81, 0xC1, 0xC1, 0xC1, 0xC1,
0xC1, 0xC0, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x0C, 0x7F, 0xDF,
0xF0, 0x00, 0x00, 0x7F, 0xDF, 0xF0, 0x60, 0x38, 0x0E, 0x03, 0x80, 0xE0,
0x38, 0x0E, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x00, 0x3F, 0x1F,
0xEE, 0x1F, 0x03, 0xC1, 0xC0, 0xE0, 0x70, 0x38, 0x0C, 0x03, 0x00, 0xC0,
0x00, 0x0C, 0x03, 0x00, 0x3F, 0x8F, 0xF9, 0x83, 0x67, 0xBD, 0xF7, 0xB6,
0xF6, 0xDE, 0xDB, 0xDB, 0x7B, 0xFB, 0x3E, 0x70, 0x07, 0xF8, 0x3F, 0x00,
0x0C, 0x03, 0x01, 0xE0, 0x78, 0x1E, 0x0C, 0xC3, 0x30, 0xCC, 0x61, 0x9F,
0xE7, 0xFB, 0x03, 0xC0, 0xF0, 0x30, 0xFE, 0x3F, 0xCC, 0x3B, 0x06, 0xC1,
0xB0, 0xEF, 0xF3, 0xFE, 0xC1, 0xF0, 0x3C, 0x0F, 0x07, 0xFF, 0xBF, 0xC0,
0x1F, 0x0F, 0xE7, 0x1D, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30,
0x06, 0x0D, 0xC7, 0x3F, 0x87, 0xC0, 0xFE, 0x3F, 0xCC, 0x3B, 0x06, 0xC0,
0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x1B, 0x0E, 0xFF, 0x3F, 0x80,
0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0, 0x30, 0x0F, 0xF3, 0xFC, 0xC0, 0x30,
0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0,
0x30, 0x0F, 0xF3, 0xFC, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x00,
0x1F, 0x8F, 0xF7, 0x0D, 0x80, 0xC0, 0x30, 0x0C, 0x7F, 0x1F, 0xC0, 0xF0,
0x36, 0x0D, 0xC3, 0x3F, 0xC7, 0xF0, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0,
0xF0, 0x3F, 0xFF, 0xFF, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
0x3F, 0x3F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x3F, 0x3F, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03,
0x00, 0xF0, 0x3C, 0x0F, 0x86, 0x7F, 0x8F, 0xC0, 0xC0, 0xF0, 0x7C, 0x3B,
0x1C, 0xCE, 0x37, 0x0F, 0x83, 0xE0, 0xDC, 0x33, 0x8C, 0x73, 0x0E, 0xC1,
0xF0, 0x30, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00,
0xC0, 0x30, 0x0C, 0x03, 0x00, 0xFF, 0xFF, 0xF0, 0xC0, 0xF8, 0x7E, 0x1F,
0xCF, 0xF3, 0xF7, 0xBD, 0xEF, 0x33, 0xCC, 0xF0, 0x3C, 0x0F, 0x03, 0xC0,
0xF0, 0x30, 0xC0, 0xF8, 0x3E, 0x0F, 0xC3, 0xD8, 0xF6, 0x3C, 0xCF, 0x33,
0xC6, 0xF1, 0xBC, 0x3F, 0x07, 0xC1, 0xF0, 0x30, 0x1E, 0x0F, 0xC7, 0x39,
0x86, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x36, 0x19, 0xCE, 0x3F,
0x07, 0x80, 0xFF, 0x3F, 0xEC, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE,
0xFF, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x00, 0x1E, 0x0F, 0xC7, 0x39,
0x86, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF1, 0xB6, 0x79, 0xCE, 0x3F,
0xC7, 0xB0, 0xFF, 0x3F, 0xEC, 0x1F, 0x03, 0xC0, 0xF0, 0x3C, 0x1F, 0xFE,
0xFF, 0x33, 0x8C, 0x73, 0x0E, 0xC1, 0xF0, 0x30, 0x3F, 0x1F, 0xEE, 0x1F,
0x03, 0xC0, 0x38, 0x07, 0xF0, 0xFE, 0x01, 0xC0, 0x3C, 0x0F, 0x87, 0x7F,
0x8F, 0xC0, 0x7F, 0xBF, 0xC3, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C,
0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0,
0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0D, 0x86, 0x7F, 0x8F, 0xC0,
0xC0, 0xF0, 0x3C, 0x0D, 0x86, 0x61, 0x98, 0x63, 0x30, 0xCC, 0x33, 0x07,
0x81, 0xE0, 0x78, 0x0C, 0x03, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0,
0xF0, 0x3C, 0x0F, 0x33, 0xCC, 0xF7, 0xBF, 0x3F, 0x87, 0xE1, 0xF0, 0x30,
0xC0, 0xF0, 0x36, 0x19, 0x86, 0x33, 0x07, 0x80, 0xC0, 0x30, 0x1E, 0x0C,
0xC6, 0x19, 0x86, 0xC0, 0xF0, 0x30, 0xC0, 0xF0, 0x36, 0x19, 0x86, 0x33,
0x0C, 0xC1, 0xE0, 0x78, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00,
0xFF, 0xFF, 0xF0, 0x18, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x30, 0x18, 0x0C,
0x06, 0x01, 0x80, 0xFF, 0xFF, 0xF0, 0x3F, 0x3F, 0x30, 0x30, 0x30, 0x30,
0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3F, 0x3F, 0x80, 0x18, 0x03, 0x80,
0x38, 0x03, 0x80, 0x38, 0x03, 0x80, 0x38, 0x03, 0x80, 0x38, 0x03, 0x80,
0x30, 0x3F, 0x3F, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
0x03, 0x3F, 0x3F, 0x04, 0x01, 0xC0, 0x7C, 0x1D, 0xC7, 0x1D, 0xC1, 0xF0,
0x18, 0xFF, 0xFF, 0xFC, 0x0E, 0x1C, 0x38, 0x60, 0xC0, 0xC0, 0x3F, 0x9F,
0xF0, 0x0C, 0xFF, 0x7F, 0xF0, 0x3C, 0x0F, 0xFF, 0x7F, 0xC0, 0xC0, 0x30,
0x0C, 0x03, 0x00, 0xC0, 0x37, 0xCF, 0xFB, 0x87, 0xC0, 0xF0, 0x3C, 0x0F,
0x07, 0xFF, 0xBF, 0xC0, 0x3F, 0x1F, 0xEE, 0x0F, 0x00, 0xC0, 0x30, 0x0E,
0x0D, 0xFE, 0x3F, 0x00, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xCF, 0xB7,
0xFF, 0x8F, 0xC0, 0xF0, 0x3C, 0x0F, 0x83, 0x7F, 0xCF, 0xF0, 0x3F, 0x1F,
0xEE, 0x0F, 0xFF, 0xFF, 0xB0, 0x0E, 0x01, 0xFE, 0x3F, 0x00, 0x0F, 0x1F,
0x38, 0x30, 0x30, 0x30, 0xFE, 0xFE, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
0x3F, 0xDF, 0xFE, 0x0F, 0x03, 0xE1, 0xDF, 0xF3, 0xEC, 0x03, 0x01, 0xDF,
0xE7, 0xF0, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0xF3, 0xFD, 0xC7, 0xC1,
0xE0, 0xF0, 0x78, 0x3C, 0x1E, 0x0C, 0x0C, 0x0C, 0x00, 0x1C, 0x1C, 0x0C,
0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x3F, 0x03, 0x03, 0x00, 0x07, 0x07, 0x03,
0x03, 0x03, 0x03, 0x03, 0x03, 0x33, 0x3F, 0x1E, 0x60, 0x30, 0x18, 0x0C,
0x06, 0x03, 0x19, 0x9C, 0xDC, 0x7C, 0x3E, 0x1B, 0x8C, 0xE6, 0x3B, 0x0C,
0x1C, 0x1C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
0x3F, 0x3F, 0xB3, 0x3F, 0xEF, 0xFF, 0x33, 0xCC, 0xF3, 0x3C, 0xCF, 0x33,
0xCC, 0xC0, 0x7F, 0x1F, 0xE6, 0x1D, 0x83, 0x60, 0xD8, 0x36, 0x0D, 0x83,
0x60, 0xC0, 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, 0xF0, 0x3E, 0x1D, 0xFE,
0x3F, 0x00, 0xFF, 0x3F, 0xEC, 0x1F, 0x03, 0xC0, 0xF8, 0x7F, 0xFB, 0x7C,
0xC0, 0x30, 0x0C, 0x00, 0x3F, 0xDF, 0xFE, 0x0F, 0x03, 0xC0, 0xF8, 0x77,
0xFC, 0xFB, 0x00, 0xC0, 0x30, 0x0C, 0x6F, 0x9F, 0xF7, 0x0D, 0x80, 0x60,
0x18, 0x06, 0x01, 0x80, 0x60, 0x00, 0x7E, 0xFF, 0xC0, 0xFE, 0x7F, 0x03,
0x03, 0xFF, 0x7E, 0x30, 0x30, 0x30, 0x30, 0xFE, 0xFE, 0x30, 0x30, 0x30,
0x30, 0x30, 0x3F, 0x1F, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3E,
0x1D, 0xFF, 0x3E, 0xC0, 0xC0, 0xF0, 0x36, 0x19, 0x86, 0x33, 0x0C, 0xC1,
0xE0, 0x78, 0x0C, 0x00, 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0xCC, 0xF7, 0xB7,
0xF9, 0xCE, 0x21, 0x00, 0xC1, 0xF1, 0xDD, 0xC7, 0xC1, 0xC1, 0xF1, 0xDD,
0xC7, 0xC1, 0x80, 0x61, 0xB0, 0xCC, 0xC6, 0x61, 0xE0, 0xF0, 0x30, 0x18,
0x18, 0x0C, 0x0C, 0x00, 0xFF, 0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,
0xFF, 0xFF, 0x80, 0x07, 0x87, 0xC7, 0x03, 0x01, 0x80, 0xC0, 0xE0, 0xE0,
0x38, 0x0C, 0x06, 0x03, 0x01, 0xC0, 0x7C, 0x1E, 0x0C, 0x30, 0xC3, 0x0C,
0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0x78, 0x3E, 0x03, 0x80, 0xC0,
0x60, 0x30, 0x1C, 0x07, 0x07, 0x03, 0x01, 0x80, 0xC0, 0xE3, 0xE1, 0xE0,
0x38, 0xDB, 0x6C, 0x70, 0x0C, 0x07, 0x83, 0x31, 0x86, 0xC0, 0xF0, 0x3F,
0xFF, 0xFF,
};
const GFXglyph Terminal11x16Glyphs[] = {
{ 0, 6, 0, 7, 0, 4 }, // 0x20 ' '
{ 0, 7, 14, 8, 0, -12 }, // 0x21 '!'
{ 13, 8, 4, 9, 0, -10 }, // 0x22 '"'
{ 17, 11, 12, 12, 0, -11 }, // 0x23 '#'
{ 34, 9, 14, 10, 0, -12 }, // 0x24 '$'
{ 50, 11, 12, 12, 0, -10 }, // 0x25 '%'
{ 67, 10, 13, 11, 0, -11 }, // 0x26 '&'
{ 84, 6, 6, 7, 0, -12 }, // 0x27 '''
{ 89, 8, 14, 9, 0, -12 }, // 0x28 '('
{ 103, 8, 14, 9, 0, -12 }, // 0x29 ')'
{ 117, 9, 9, 10, 0, -9 }, // 0x2a '*'
{ 128, 9, 8, 10, 0, -8 }, // 0x2b '+'
{ 137, 6, 5, 7, 0, -1 }, // 0x2c ','
{ 141, 9, 2, 10, 0, -5 }, // 0x2d '-'
{ 144, 6, 3, 7, 0, -1 }, // 0x2e '.'
{ 147, 11, 12, 12, 0, -11 }, // 0x2f '/'
{ 164, 11, 14, 12, 0, -12 }, // 0x30 '0'
{ 184, 10, 14, 11, 0, -12 }, // 0x31 '1'
{ 202, 11, 14, 12, 0, -12 }, // 0x32 '2'
{ 222, 11, 14, 12, 0, -12 }, // 0x33 '3'
{ 242, 11, 14, 12, 0, -12 }, // 0x34 '4'
{ 262, 11, 14, 12, 0, -12 }, // 0x35 '5'
{ 282, 11, 14, 12, 0, -12 }, // 0x36 '6'
{ 302, 11, 14, 12, 0, -12 }, // 0x37 '7'
{ 322, 11, 14, 12, 0, -12 }, // 0x38 '8'
{ 342, 11, 14, 12, 0, -12 }, // 0x39 '9'
{ 362, 6, 9, 7, 0, -8 }, // 0x3a ':'
{ 369, 6, 12, 7, 0, -8 }, // 0x3b ';'
{ 378, 9, 14, 10, 0, -12 }, // 0x3c '<'
{ 394, 10, 6, 11, 0, -7 }, // 0x3d '='
{ 402, 9, 14, 10, 0, -12 }, // 0x3e '>'
{ 418, 10, 14, 11, 0, -12 }, // 0x3f '?'
{ 436, 11, 14, 12, 0, -12 }, // 0x40 '@'
{ 456, 10, 14, 11, 0, -12 }, // 0x41 'A'
{ 474, 10, 14, 11, 0, -12 }, // 0x42 'B'
{ 492, 10, 14, 11, 0, -12 }, // 0x43 'C'
{ 510, 10, 14, 11, 0, -12 }, // 0x44 'D'
{ 528, 10, 14, 11, 0, -12 }, // 0x45 'E'
{ 546, 10, 14, 11, 0, -12 }, // 0x46 'F'
{ 564, 10, 14, 11, 0, -12 }, // 0x47 'G'
{ 582, 10, 14, 11, 0, -12 }, // 0x48 'H'
{ 600, 8, 14, 9, 0, -12 }, // 0x49 'I'
{ 614, 10, 14, 11, 0, -12 }, // 0x4a 'J'
{ 632, 10, 14, 11, 0, -12 }, // 0x4b 'K'
{ 650, 10, 14, 11, 0, -12 }, // 0x4c 'L'
{ 668, 10, 14, 11, 0, -12 }, // 0x4d 'M'
{ 686, 10, 14, 11, 0, -12 }, // 0x4e 'N'
{ 704, 10, 14, 11, 0, -12 }, // 0x4f 'O'
{ 722, 10, 14, 11, 0, -12 }, // 0x50 'P'
{ 740, 10, 14, 11, 0, -12 }, // 0x51 'Q'
{ 758, 10, 14, 11, 0, -12 }, // 0x52 'R'
{ 776, 10, 14, 11, 0, -12 }, // 0x53 'S'
{ 794, 9, 14, 10, 0, -12 }, // 0x54 'T'
{ 810, 10, 14, 11, 0, -12 }, // 0x55 'U'
{ 828, 10, 14, 11, 0, -12 }, // 0x56 'V'
{ 846, 10, 14, 11, 0, -12 }, // 0x57 'W'
{ 864, 10, 14, 11, 0, -12 }, // 0x58 'X'
{ 882, 10, 14, 11, 0, -12 }, // 0x59 'Y'
{ 900, 10, 14, 11, 0, -12 }, // 0x5a 'Z'
{ 918, 8, 14, 9, 0, -12 }, // 0x5b '['
{ 932, 11, 12, 12, 0, -11 }, // 0x5c '\'
{ 949, 8, 14, 9, 0, -12 }, // 0x5d ']'
{ 963, 11, 7, 12, 0, -12 }, // 0x5e '^'
{ 973, 11, 2, 12, 0, 2 }, // 0x5f '_'
{ 976, 7, 6, 8, 0, -11 }, // 0x60 '`'
{ 982, 10, 9, 11, 0, -7 }, // 0x61 'a'
{ 994, 10, 14, 11, 0, -12 }, // 0x62 'b'
{ 1012, 10, 9, 11, 0, -7 }, // 0x63 'c'
{ 1024, 10, 14, 11, 0, -12 }, // 0x64 'd'
{ 1042, 10, 9, 11, 0, -7 }, // 0x65 'e'
{ 1054, 8, 14, 9, 0, -12 }, // 0x66 'f'
{ 1068, 10, 11, 11, 0, -7 }, // 0x67 'g'
{ 1082, 9, 14, 10, 0, -12 }, // 0x68 'h'
{ 1098, 8, 12, 9, 0, -10 }, // 0x69 'i'
{ 1110, 8, 14, 9, 0, -10 }, // 0x6a 'j'
{ 1124, 9, 14, 10, 0, -12 }, // 0x6b 'k'
{ 1140, 8, 14, 9, 0, -12 }, // 0x6c 'l'
{ 1154, 10, 9, 11, 0, -7 }, // 0x6d 'm'
{ 1166, 10, 9, 11, 0, -7 }, // 0x6e 'n'
{ 1178, 10, 9, 11, 0, -7 }, // 0x6f 'o'
{ 1190, 10, 11, 11, 0, -7 }, // 0x70 'p'
{ 1204, 10, 11, 11, 0, -7 }, // 0x71 'q'
{ 1218, 10, 9, 11, 0, -7 }, // 0x72 'r'
{ 1230, 8, 9, 9, 0, -7 }, // 0x73 's'
{ 1239, 8, 13, 9, 0, -11 }, // 0x74 't'
{ 1252, 10, 9, 11, 0, -7 }, // 0x75 'u'
{ 1264, 10, 9, 11, 0, -7 }, // 0x76 'v'
{ 1276, 10, 9, 11, 0, -7 }, // 0x77 'w'
{ 1288, 9, 9, 10, 0, -7 }, // 0x78 'x'
{ 1299, 9, 11, 10, 0, -7 }, // 0x79 'y'
{ 1312, 9, 9, 10, 0, -7 }, // 0x7a 'z'
{ 1323, 9, 15, 10, 0, -12 }, // 0x7b '{'
{ 1340, 6, 14, 7, 0, -12 }, // 0x7c '|'
{ 1351, 9, 15, 10, 0, -12 }, // 0x7d '}'
{ 1368, 10, 3, 11, 0, -10 }, // 0x7e '~'
{ 1372, 10, 8, 11, 0, -8 }, // 0x7f ''
};
const GFXfont Terminal11x16Font = {
(uint8_t *)Terminal11x16Bitmap,
(GFXglyph *)Terminal11x16Glyphs,
0x20, 0x7F, 18 };

View File

@ -0,0 +1,25 @@
#include <inttypes.h>
#include "gfxfont.h"
#define PROGMEM
#include "FreeMono12pt8b.h"
#include "FreeMono9pt8b.h"
#include "FreeSans12pt8b.h"
#include "FreeSans18pt8b.h"
#include "FreeSans9pt8b.h"
#include "Picopixel.h"
#include "Terminal11x16.h"
__attribute__((used)) const GFXfont * const allfonts[]={
(const GFXfont *)0x544E4F46,
&Terminal11x16Font,
&Terminal11x16Font,
&FreeSans9pt8b,
&FreeSans12pt8b,
&Picopixel,
&FreeSans18pt8b,
&FreeMono9pt8b,
&FreeMono12pt8b,
0,
};

View File

@ -0,0 +1,31 @@
// Font structures for newer Adafruit_GFX (1.1 and later).
// Example fonts are included in 'Fonts' directory.
// To use a font in your Arduino sketch, #include the corresponding .h
// file and pass address of GFXfont struct to setFont(). Pass NULL to
// revert to 'classic' fixed-space bitmap font.
#ifndef _GFXFONT_H_
#define _GFXFONT_H_
/// Font data stored PER GLYPH
typedef struct
{
uint16_t bitmapOffset; ///< Pointer into GFXfont->bitmap
uint8_t width; ///< Bitmap dimensions in pixels
uint8_t height; ///< Bitmap dimensions in pixels
uint8_t xAdvance; ///< Distance to advance cursor (x axis)
int8_t xOffset; ///< X dist from cursor pos to UL corner
int8_t yOffset; ///< Y dist from cursor pos to UL corner
} GFXglyph;
/// Data stored for FONT AS A WHOLE
typedef struct
{
uint8_t *bitmap; ///< Glyph bitmaps, concatenated
GFXglyph *glyph; ///< Glyph array
uint8_t first; ///< ASCII extents (first char)
uint8_t last; ///< ASCII extents (last char)
uint8_t yAdvance; ///< Newline distance (y axis)
} GFXfont;
#endif // _GFXFONT_H_

64
libraries/SondeLib/geteph.cpp → RX_FSK/src/geteph.cpp Executable file → Normal file
View File

@ -6,12 +6,15 @@
#include <inttypes.h>
#include <WiFi.h>
#include "Display.h"
#include "Sonde.h"
extern WiFiClient client;
static const char *ftpserver = "www.ngs.noaa.gov";
//static const char *ftpserver = "www.ngs.noaa.gov";
char outbuf[128];
uint8_t ephstate = EPH_NOTUSED;
//enum EPHSTATE { EPH_NOTUSED, EPH_PENDING, EPH_TIMEERR, EPH_ERROR, EPH_EPHERROR, EPH_GOOD };
const char *ephtxt[] = { "Disabled", "Pending", "Time error", "Fetch error", "Read error", "Good" };
uint8_t getreply() {
String s = client.readStringUntil('\n');
@ -45,10 +48,12 @@ void writeFully(File &file, uint8_t *buf, size_t len)
void geteph() {
// Set current time via network...
ephstate = EPH_PENDING;
struct tm tinfo;
configTime(0, 0, "pool.ntp.org");
bool ok = getLocalTime(&tinfo, 2000); // wait max 2 seconds to get current time via ntp
if(!ok) {
ephstate = EPH_TIMEERR;
Serial.println("Failed to get current date/time");
return;
}
@ -66,37 +71,39 @@ void geteph() {
if(tsstr && strlen(tsstr)>=9) {
if(strcmp(nowstr, ts.c_str())<=0) {
Serial.println("local brdc is up to date\n");
ephstate = EPH_GOOD;
return;
}
}
Serial.printf("now: %s, existing: %s => updating\n", nowstr, tsstr);
}
status.close();
disp.rdis->clear();
disp.rdis->setFont(FONT_SMALL);
disp.rdis->drawString(0, 0, "FTP ngs.noaa.gov");
// fetch rinex from server
File fh = SPIFFS.open("/brdc.gz","w");
if(!fh) {
Serial.println("cannot open file\n");
return;
}
char buf[252];
snprintf(buf, 128, "/cors/rinex/%04d/%03d/brdc%03d0.%02dn.gz", year, day, day, year-2000);
char host[252];
strcpy(host, sonde.config.ephftp);
char *buf = strchr(host, '/');
if(!buf) { Serial.println("Invalid FTP host config"); return; }
*buf = 0;
buf++;
uint8_t dispw, disph, dispxs, dispys;
disp.rdis->getDispSize(&disph, &dispw, &dispxs, &dispys);
disp.rdis->clear();
disp.rdis->setFont(FONT_SMALL);
disp.rdis->drawString(0, 0, host);
// fetch rinex from server
char *ptr = buf + strlen(buf);
snprintf(ptr, 128, "%04d/%03d/brdc%03d0.%02dn.gz", year, day, day, year-2000);
Serial.println("running geteph\n");
disp.rdis->drawString(0, 1, buf+21);
disp.rdis->drawString(0, 1*dispys, ptr+9);
if(!client.connect(ftpserver, 21)) {
Serial.println("FTP connection to www.ngs.noaa.gov failed");
if(!client.connect(host, 21)) {
Serial.printf("FTP connection to %s failed\n", host);
return;
}
#if 0
while(!client.available()) delay(1);
while(client.available()) {
String s = client.readStringUntil('\n');
Serial.println(s);
}
#endif
if(getreply()>='4') { Serial.println("connected failed"); return; }
client.print("USER anonymous\r\n");
if(getreply()>='4') { Serial.println("USER failed"); return; }
@ -121,8 +128,8 @@ void geteph() {
}
uint16_t port = (array_pasv[4]<<8) | (array_pasv[5]&0xff);
WiFiClient dclient;
Serial.printf("connecting to %s:%d\n", ftpserver,port);
dclient.connect(ftpserver, port);
Serial.printf("connecting to %s:%d\n", host, port);
dclient.connect(host, port);
if(!dclient) {
Serial.println("data connection failed");
return;
@ -136,18 +143,22 @@ void geteph() {
int len=0;
while(dclient.connected()) {
while(dclient.available()) {
char c = dclient.read();
fh.write(c);
len++;
int c = dclient.read();
if(c==-1) {
Serial.println("dclient.read() returned -1 inspite of available() being true?!");
} else {
fh.write(c);
len++;
}
}
}
Serial.printf("fetched %d bytes\n", len);
fh.close();
snprintf(buf, 16, "Fetched %d B ",len);
buf[16]=0;
disp.rdis->drawString(0,2,buf);
disp.rdis->drawString(0,2*dispys,buf);
disp.rdis->drawString(0,4,"Decompressing...");
disp.rdis->drawString(0,4*dispys,"Decompressing...");
// decompression
tinfl_decompressor *decomp = (tinfl_decompressor *)malloc(sizeof(tinfl_decompressor));
tinfl_init(decomp);
@ -215,7 +226,8 @@ void geteph() {
status.close();
snprintf(buf, 16, "Done: %d B ",total);
buf[16]=0;
disp.rdis->drawString(0,5,buf);
disp.rdis->drawString(0,5*dispys,buf);
ephstate = EPH_GOOD;
delay(1000);
free(obuf);

11
RX_FSK/src/geteph.h Normal file
View File

@ -0,0 +1,11 @@
#include <inttypes.h>
#ifndef GETEPH_H
#define GETEPH_H
void geteph();
enum EPHSTATE { EPH_NOTUSED, EPH_PENDING, EPH_TIMEERR, EPH_ERROR, EPH_EPHERROR, EPH_GOOD };
extern uint8_t ephstate;
extern const char *ephtxt[];
#endif

0
libraries/SondeLib/gfxfont.h → RX_FSK/src/gfxfont.h Executable file → Normal file
View File

106
RX_FSK/src/json.cpp Normal file
View File

@ -0,0 +1,106 @@
#include "json.h"
#include "RS41.h"
extern const char *sondeTypeStrSH[];
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;
}
return sondeTypeStrSH[sonde.realType(si)];
}
int float2json(char **buf, int *maxlen, const char *fmt, float value) {
if(isnan(value)) return 0;
int n = snprintf(*buf, *maxlen, fmt, value);
if(n>*maxlen) return -1;
*buf += n; *maxlen -= n;
return n;
}
// To be used by
// - MQTT
// - rdzJSON (for Android app)
// - Web map
int sonde2json(char *buf, int maxlen, SondeInfo *si)
{
SondeData *s = &(si->d);
int n;
n = float2json(&buf, &maxlen, "\"lat\": %.5f,", s->lat);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"lon\": %.5f,", s->lon);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"alt\": %.1f,", s->alt);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"vs\": %.1f,", s->vs);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"hs\": %.1f,", s->hs);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"climb\": %.1f,", s->vs); // used by HTML map, to be removed (-> vs)
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"speed\": %.1f,", s->hs); // used by HTML map, to be removed (-> hs)
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"dir\": %.1f,", s->dir);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"temp\": %.1f,", s->temperature );
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"humidity\": %.1f,", s->relativeHumidity);
if(n<0) return -1;
n = float2json(&buf, &maxlen, "\"pressure\": %.1f,", s->pressure);
if(n<0) return -1;
n = snprintf(buf, maxlen,
"\"type\":\"%s\","
"\"id\": \"%s\"," // TODO: maybe remove in the future, ser is enough, client can calculate APRS id if needed
"\"ser\": \"%s\","
"\"frame\": %u," // raw frame, from sonde, can be 0. (TODO: add virtual frame # as in sondehub?)
"\"vframe\": %d,"
"\"time\": %u,"
"\"sats\": %d,"
"\"freq\": %.2f,"
"\"rssi\": %d,"
"\"afc\": %d,"
"\"launchKT\": %d,"
"\"burstKT\": %d,"
"\"countKT\": %d,"
"\"crefKT\": %d,"
"\"launchsite\": \"%s\","
"\"res\": %d",
getType(si),
s->id,
s->ser,
s->frame,
s->vframe,
s->time,
s->sats,
si->freq,
si->rssi,
si->afc,
s->launchKT,
s->burstKT,
s->countKT,
s->crefKT,
si->launchsite,
(int)si->rxStat[0]
);
if(n>=maxlen) return -1;
buf += n; maxlen -= n;
// add only if available
if(s->batteryVoltage > 0) {
n = snprintf(buf, maxlen, ",\"batt\": %.1f", s->batteryVoltage);
if(n>=maxlen) return -1;
buf += n; maxlen -= n;
}
return 0;
}

8
RX_FSK/src/json.h Normal file
View File

@ -0,0 +1,8 @@
#ifndef _JSON_H
#define _JSON_H
#include "Sonde.h"
int sonde2json(char *buf, int maxlen, SondeInfo *si);
#endif

11
RX_FSK/src/library.json Normal file
View File

@ -0,0 +1,11 @@
{
"dependencies":
[
{
"owner": "olikraus",
"name": "U8g2",
"version": "^2.28.8"
}
]
}

165
RX_FSK/src/mqtt.cpp Normal file
View File

@ -0,0 +1,165 @@
#include <Arduino.h>
#include "mqtt.h"
#include <WiFi.h>
//#include <AsyncMqttClient.h>
#include <AsyncMqtt_Generic.h>
#include <ESPmDNS.h>
#include "RS41.h"
#include "json.h"
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);
Serial.print("] ");
for (int i=0;i<length;i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
static char buffer[21];
void MQTT::init(const char* host, uint16_t port, const char* id, const char *username, const char *password, const char *prefix)
{
WiFi.hostByName(host, this->ip);
this->port = port;
this->username = username;
this->password = password;
this->prefix = prefix;
Serial.println("[MQTT] pubsub client");
mqttClient.setServer(ip, port);
snprintf(buffer, 20, "%s%04d", id, (int)random(0, 1000));
buffer[20] = 0;
Serial.print(buffer);
mqttClient.setClientId(buffer);
if (strlen(password) > 0) {
mqttClient.setCredentials(username, password);
}
}
void MQTT::connectToMqtt() {
Serial.println("Connecting to MQTT...");
mqttClient.connect();
}
void MQTT::publishUptime()
{
mqttClient.connect(); // ensure we've got connection
Serial.println("[MQTT] writing");
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");
mqttClient.publish(topic, 1, 1, payload);
}
void MQTT::publishPacket(SondeInfo *si)
{
SondeData *s = &(si->d);
mqttClient.connect(); // ensure we've got connection
char payload[1024];
payload[0] = '{';
int n = sonde2json(payload+1, 1023, si);
if(n<0) {
// ERROR
Serial.println("publishPacket: sonde2json failed, string too long");
}
#if 0
snprintf(payload, 1024, "{"
"\"active\": %d,"
"\"freq\": %.2f,"
"\"id\": \"%s\","
"\"ser\": \"%s\","
"\"validId\": %d,"
"\"launchsite\": \"%s\","
"\"lat\": %.5f,"
"\"lon\": %.5f,"
"\"alt\": %.1f,"
"\"vs\": %.1f,"
"\"hs\": %.1f,"
"\"dir\": %.1f,"
"\"sats\": %d,"
"\"validPos\": %d,"
"\"time\": %u,"
"\"frame\": %u,"
"\"validTime\": %d,"
"\"rssi\": %d,"
"\"afc\": %d,"
"\"rxStat\": \"%s\","
"\"rxStart\": %u,"
"\"norxStart\": %u,"
"\"viewStart\": %u,"
"\"lastState\": %d,"
"\"launchKT\": %d,"
"\"burstKT\": %d,"
"\"countKT\": %d,"
"\"crefKT\": %d",
(int)si->active,
si->freq,
s->id,
s->ser,
(int)s->validID,
si->launchsite,
s->lat,
s->lon,
s->alt,
s->vs,
s->hs,
s->dir,
s->sats,
s->validPos,
s->time,
s->frame,
(int)s->validTime,
si->rssi,
si->afc,
si->rxStat,
si->rxStart,
si->norxStart,
si->viewStart,
si->lastState,
s->launchKT,
s->burstKT,
s->countKT,
s->crefKT
);
if ( !isnan( s->temperature ) ) {
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"temp\": ", s->temperature );
}
if ( !isnan( s->relativeHumidity ) ) {
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"humidity\": ", s->relativeHumidity );
}
if ( !isnan( s->pressure ) ) {
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"pressure\": ", s->pressure );
}
if ( !isnan( s->batteryVoltage && s->batteryVoltage > 0 ) ) {
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"batt\": ", s->batteryVoltage );
}
char subtype[11];
if ( RS41::getSubtype( subtype, 11, si) == 0 ) {
snprintf(payload, 1024, "%s%s%s%s", payload, ",\"subtype\": \"", subtype, "\"" );
}
snprintf(payload, 1024, "%s%s", payload, "}" ); // terminate payload string
#endif
strcat(payload, "}"); // terminate payload string
char topic[128];
snprintf(topic, 128, "%s%s", this->prefix, "packet");
Serial.print(payload);
mqttClient.publish(topic, 1, 1, payload);
}

29
RX_FSK/src/mqtt.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef MQTT_h
#define MQTT_h
#include <WiFi.h>
//#include <AsyncMqttClient.h>
#include <AsyncMqtt_Generic.h>
#include "Sonde.h"
#include "RS41.h"
class MQTT
{
public:
WiFiClient mqttWifiClient;
AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer;
IPAddress ip;
uint16_t port;
const char *username;
const char *password;
const char *prefix;
void init(const char *host, uint16_t port, const char *id, const char *username, const char *password, const char *prefix);
void publishPacket(SondeInfo *s);
void publishUptime();
private:
void connectToMqtt();
};
#endif

View File

@ -78,174 +78,6 @@ void rotZ(double x1, double y1, double z1, double angle, double *x2, double *y2,
/* ---------------------------------------------------------------------------------------------------- */
#if 0
int read_SEMalmanac(FILE *fp, EPHEM_t *alm) {
int l, j;
char buf[64];
unsigned n, week, toa, ui;
double dbl;
l = fscanf(fp, "%u", &n); if (l != 1) return -1;
l = fscanf(fp, "%s", buf); if (l != 1) return -1;
l = fscanf(fp, "%u", &week); if (l != 1) return -1;
l = fscanf(fp, "%u", &toa); if (l != 1) return -1;
for (j = 1; j <= n; j++) {
//memset(&ephem, 0, sizeof(ephem));
alm[j].week = (uint16_t)week;
alm[j].toa = (uint32_t)toa;
alm[j].toe = (double)toa;
alm[j].toc = alm[j].toe;
l = fscanf(fp, "%u", &ui); if (l != 1) return -1; alm[j].prn = (uint16_t)ui;
l = fscanf(fp, "%u", &ui); if (l != 1) return -2; alm[j].svn = (uint16_t)ui;
l = fscanf(fp, "%u", &ui); if (l != 1) return -3; alm[j].ura = (uint8_t)ui;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -4; alm[j].e = dbl;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -5; alm[j].delta_i = dbl;
alm[j].i0 = (0.30 + alm[j].delta_i) * PI;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -6; alm[j].OmegaDot = dbl * PI;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -7; alm[j].sqrta = dbl;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -6; alm[j].Omega0 = dbl * PI;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -8; alm[j].w = dbl * PI;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -9; alm[j].M0 = dbl * PI;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -10; alm[j].af0 = dbl;
l = fscanf(fp, "%lf", &dbl); if (l != 1) return -11; alm[j].af1 = dbl;
alm[j].af2 = 0;
alm[j].crc = 0;
alm[j].crs = 0;
alm[j].cuc = 0;
alm[j].cus = 0;
alm[j].cic = 0;
alm[j].cis = 0;
alm[j].tgd = 0;
alm[j].idot = 0;
alm[j].delta_n = 0;
l = fscanf(fp, "%u", &ui); if (l != 1) return -12; alm[j].health = (uint8_t)ui;
l = fscanf(fp, "%u", &ui); if (l != 1) return -13; alm[j].conf = (uint8_t)ui;
}
return 0;
}
int read_RNXephemeris(FILE *fp, EPHEM_t eph[][24]) {
int l, i;
char buf[64], str[20];
char buf_header[83];
//buf_data[80]; // 3 + 4*19 = 79
char *pbuf;
unsigned ui;
double dbl;
int c;
EPHEM_t ephem = {};
int hr = 0;
do {
//l = fread(buf_header, 81, 1, fp); // Zeilen in Header sind nicht immer mit Leerzeichen aufgefuellt
pbuf = fgets(buf_header, 82, fp); // max 82-1 Zeichen + '\0'
buf_header[82] = '\0'; // doppelt haelt besser
//l = strlen(buf_header);
} while ( pbuf && !strstr(buf_header, "END OF HEADER") );
//l = fread(buf_data, 80, 1, fp);
//buf_data[79] = '\0';
while (hr < 24) { // brdc/hour-rinex sollte nur Daten von einem Tag enthalten
//memset(&ephem, 0, sizeof(ephem));
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0; sscanf(buf, "%d", &ui);
ephem.prn = ui;
for (i = 0; i < 16; i++) ephem.epoch[i] = '0';
ephem.epoch[16] = '\0';
l = fread(buf, 19, 1, fp); if (l != 1) break; buf[19] = 0;
for (i = 0; i < 6; i++) {
c = buf[3*i ]; if (c == ' ') c = '0'; str[2*i ] = c;
c = buf[3*i+1]; if (c == ' ') c = '0'; str[2*i+1] = c;
}
str[12] = buf[17];
str[13] = buf[18];
str[14] = '\0';
strncpy(ephem.epoch , "20", 2); // vorausgesetzt 21.Jhd; Datum steht auch im Header
strncpy(ephem.epoch+2, str, 15);
ephem.epoch[16] = '\0';
strncpy(str, buf+9, 2); str[2] = '\0';
hr = atoi(str);
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af0 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af1 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af2 = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iode = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.crs = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.delta_n = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.M0 = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cuc = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.e = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cus = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.sqrta = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.toe = dbl;
ephem.toc = ephem.toe;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cic = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.Omega0 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cis = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.i0 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.crc = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.w = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.OmegaDot = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.idot = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.codeL2 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.gpsweek = (int)dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iodc = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.sva = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.health = (uint8_t)(dbl+0.1);
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.tgd = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iodc = dbl;
if ((c=fgetc(fp)) == EOF) break;
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.ttom = dbl;
pbuf = fgets(buf_header, 82, fp);
/* // die letzten beiden Felder (spare) sind manchmal leer (statt 0.00); manchmal fehlt sogar das drittletzte Feld
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.fit = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.spare1 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.spare2 = dbl;
if ((c=fgetc(fp)) == EOF) break; */
ephem.week = 1; // ephem.gpsweek
eph[ephem.prn][hr] = ephem;
if (pbuf == NULL) break;
}
return 0;
}
#endif
static EPHEM_t *te;
@ -253,18 +85,23 @@ static EPHEM_t *te;
#define fread(buffer, siz, els, file) (file.read((uint8_t *)buffer, (siz)*(els))/siz)
#define fgetc(file) (char)file.read()
int readDbl(File *fp, double *dbl) {
uint8_t buf[20];
int l = fp->read(buf, 19);
if(l!=19) return -1;
if (buf[15] == 'D') buf[15] = 'E';
buf[19] = 0;
sscanf((char *)buf, "%lf", dbl);
return 0;
}
//EPHEM_t *read_RNXpephs(FILE *fp) {
EPHEM_t *read_RNXpephs(const char *file) {
int l, i;
//char buffer[86];
char buf[64], str[20];
unsigned ui;
double dbl;
int c;
EPHEM_t ephem = {};
// int count = 0;
//long fpos;
File fp = SPIFFS.open(file, "r");
if(!fp) { Serial.printf("Error opening %s\n", file); }
@ -277,23 +114,7 @@ EPHEM_t *read_RNXpephs(const char *file) {
Serial.printf("Skipping header: %s\n", line.c_str());
} while ( fp.available() && !strstr((const char *)line.c_str(), "END OF HEADER") );
if (!fp.available()) return NULL;
/*
fpos = ftell(fp);
count = 0;
while (count >= 0) { // data-Zeilen: 79 Zeichen
pbuf = fgets(buffer, 84, fp); if (pbuf == 0) break;
strncpy(str, buffer, 3);
str[3] = '\0';
sscanf(str, "%d", &ui);
if (ui < 33) count++;
for (i = 1; i < 8; i++) {
pbuf = fgets(buffer, 84, fp); if (pbuf == 0) break;
}
}
printf("Ephemerides: %d total=%d\n", count, count*sizeof(ephem));
fseek(fp, fpos, SEEK_SET);
*/
if(te) free(te);
te = (EPHEM_t *)calloc( 34, sizeof(ephem) ); // calloc( 1, sizeof(ephem) );
if (te == NULL) return NULL;
@ -325,56 +146,84 @@ EPHEM_t *read_RNXpephs(const char *file) {
strncpy(ephem.epoch+2, str, 15);
ephem.epoch[16] = '\0';
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af0 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af1 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af2 = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.af0 = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.af1 = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.af2 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af0 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af1 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.af2 = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iode = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.crs = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.delta_n = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.M0 = dbl;
if(readDbl(&fp, &dbl)<0) break; //ephem.iode = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.crs = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.delta_n = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.M0 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iode = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.crs = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.delta_n = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.M0 = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cuc = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.e = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cus = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.sqrta = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.cuc = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.e = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.cus = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.sqrta = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cuc = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.e = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cus = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.sqrta = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.toe = dbl;
ephem.toc = ephem.toe;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cic = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.Omega0 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cis = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.toe = dbl; ephem.toc = ephem.toe;
if(readDbl(&fp, &dbl)<0) break; ephem.cic = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.Omega0 = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.cis = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.toe = dbl;
// ephem.toc = ephem.toe;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cic = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.Omega0 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.cis = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.i0 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.crc = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.w = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.OmegaDot = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.i0 = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.crc = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.w = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.OmegaDot = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.i0 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.crc = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.w = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.OmegaDot = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.idot = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.codeL2 = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.gpsweek = (int)dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iodc = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.idot = dbl;
if(readDbl(&fp, &dbl)<0) break; //ephem.codeL2 = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.gpsweek = (int)dbl;
if(readDbl(&fp, &dbl)<0) break; //ephem.iodc = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.idot = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.codeL2 = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.gpsweek = (int)dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iodc = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.sva = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.health = (uint8_t)(dbl+0.1);
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.tgd = dbl;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iodc = dbl;
if(readDbl(&fp, &dbl)<0) break; //ephem.sva = dbl;
if(readDbl(&fp, &dbl)<0) break; ephem.health = (uint8_t)(dbl+0.1);
if(readDbl(&fp, &dbl)<0) break; ephem.tgd = dbl;
if(readDbl(&fp, &dbl)<0) break; //ephem.iodc = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.sva = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.health = (uint8_t)(dbl+0.1);
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); ephem.tgd = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.iodc = dbl;
while ((c=fgetc(fp)) != '\n') { if (c == EOF) break; }
l = fread(buf, 3, 1, fp); if (l != 1) break; buf[ 3] = 0;
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.ttom = dbl;
if(readDbl(&fp, &dbl)<0) break; //ephem.ttom = dbl;
//l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.ttom = dbl;
String l = fp.readStringUntil('\n');
/* // die letzten beiden Felder (spare) sind manchmal leer (statt 0.00); manchmal fehlt sogar das drittletzte Feld
l = fread(buf, 19, 1, fp); if (l != 1) break; if (buf[15] == 'D') buf[15] = 'E'; buf[19] = 0; sscanf(buf, "%lf", &dbl); //ephem.fit = dbl;

View File

571
libraries/SondeLib/rs92gps.cpp → RX_FSK/src/rs92gps.cpp Executable file → Normal file
View File

@ -41,24 +41,23 @@
*/
#if 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#endif
#include <SPIFFS.h>
#include "nav_gps_vel.h"
#include "rs92gps.h"
#include "geteph.h"
#include "Sonde.h"
gpx_t gpx;
int option_verbose = 0, // ausfuehrliche Anzeige
const int option_verbose = 0, // ausfuehrliche Anzeige
option_raw = 1, // rohe Frames
option_inv = 0, // invertiert Signal
option_res = 0, // genauere Bitmessung
@ -83,25 +82,6 @@ int almanac = 0,
int exSat = -1;
#if 0
/* --- RS92-SGP: 8N1 manchester --- */
#define BITS (2*(1+8+1)) // 20
#define HEADOFS 40 // HEADOFS+HEADLEN = 120 (bis 0x10)
#define HEADLEN 80 // (HEADOFS+HEADLEN) mod BITS = 0
/*
#define HEADOFS 0 // HEADOFS muss 0 wegen Wiederholung
#define HEADLEN 60 // HEADLEN < 100, (HEADOFS+HEADLEN) mod BITS = 0
*/
#define FRAMESTART ((HEADOFS+HEADLEN)/BITS)
/* 2A 10*/
char header[] = "10100110011001101001"
"10100110011001101001"
"10100110011001101001"
"10100110011001101001"
"1010011001100110100110101010100110101001";
char buf[HEADLEN+1] = "x";
#endif
int bufpos = -1;
@ -143,225 +123,7 @@ int findstr(char *buff, char *str, int pos) {
return i;
}
#if 0
int read_wav_header(FILE *fp) {
char txt[4+1] = "\0\0\0\0";
unsigned char dat[4];
int byte, p=0;
if (fread(txt, 1, 4, fp) < 4) return -1;
if (strncmp(txt, "RIFF", 4)) return -1;
if (fread(txt, 1, 4, fp) < 4) return -1;
// pos_WAVE = 8L
if (fread(txt, 1, 4, fp) < 4) return -1;
if (strncmp(txt, "WAVE", 4)) return -1;
// pos_fmt = 12L
for ( ; ; ) {
if ( (byte=fgetc(fp)) == EOF ) return -1;
txt[p % 4] = byte;
p++; if (p==4) p=0;
if (findstr(txt, "fmt ", p) == 4) break;
}
if (fread(dat, 1, 4, fp) < 4) return -1;
if (fread(dat, 1, 2, fp) < 2) return -1;
if (fread(dat, 1, 2, fp) < 2) return -1;
channels = dat[0] + (dat[1] << 8);
if (fread(dat, 1, 4, fp) < 4) return -1;
memcpy(&sample_rate, dat, 4); //sample_rate = dat[0]|(dat[1]<<8)|(dat[2]<<16)|(dat[3]<<24);
if (fread(dat, 1, 4, fp) < 4) return -1;
if (fread(dat, 1, 2, fp) < 2) return -1;
//byte = dat[0] + (dat[1] << 8);
if (fread(dat, 1, 2, fp) < 2) return -1;
bits_sample = dat[0] + (dat[1] << 8);
// pos_dat = 36L + info
for ( ; ; ) {
if ( (byte=fgetc(fp)) == EOF ) return -1;
txt[p % 4] = byte;
p++; if (p==4) p=0;
if (findstr(txt, "data", p) == 4) break;
}
if (fread(dat, 1, 4, fp) < 4) return -1;
Serial.printf("sample_rate: %d\n", sample_rate);
Serial.printf("bits : %d\n", bits_sample);
Serial.printf("channels : %d\n", channels);
if ((bits_sample != 8) && (bits_sample != 16)) return -1;
samples_per_bit = sample_rate/(float)BAUD_RATE;
Serial.printf("samples/bit: %.2f\n", samples_per_bit);
return 0;
}
#endif
#if 0
#define EOF_INT 0x1000000
#define LEN_movAvg 3
int movAvg[LEN_movAvg];
unsigned long sample_count = 0;
double bitgrenze = 0;
int read_signed_sample(FILE *fp) { // int = i32_t
int byte, i, sample, s=0; // EOF -> 0x1000000
for (i = 0; i < channels; i++) {
// i = 0: links bzw. mono
byte = fgetc(fp);
if (byte == EOF) return EOF_INT;
if (i == 0) sample = byte;
if (bits_sample == 16) {
byte = fgetc(fp);
if (byte == EOF) return EOF_INT;
if (i == 0) sample += byte << 8;
}
}
if (bits_sample == 8) s = sample-128; // 8bit: 00..FF, centerpoint 0x80=128
if (bits_sample == 16) s = (short)sample;
if (option_avg) {
movAvg[sample_count % LEN_movAvg] = s;
s = 0;
for (i = 0; i < LEN_movAvg; i++) s += movAvg[i];
s = (s+0.5) / LEN_movAvg;
}
sample_count++;
return s;
}
int par=1, par_alt=1;
int read_bits_fsk(FILE *fp, int *bit, int *len) {
static int sample;
int n, y0;
float l, x1;
static float x0;
n = 0;
do{
y0 = sample;
sample = read_signed_sample(fp);
if (sample == EOF_INT) return EOF;
//sample_count++; // in read_signed_sample()
par_alt = par;
par = (sample >= 0) ? 1 : -1; // 8bit: 0..127,128..255 (-128..-1,0..127)
n++;
} while (par*par_alt > 0);
if (!option_res) l = (float)n / samples_per_bit;
else { // genauere Bitlaengen-Messung
x1 = sample/(float)(sample-y0); // hilft bei niedriger sample rate
l = (n+x0-x1) / samples_per_bit; // meist mehr frames (nicht immer)
x0 = x1;
}
*len = (int)(l+0.5);
if (!option_inv) *bit = (1+par_alt)/2; // oben 1, unten -1
else *bit = (1-par_alt)/2; // sdr#<rev1381?, invers: unten 1, oben -1
/* Y-offset ? */
return 0;
}
int bitstart = 0;
int read_rawbit(FILE *fp, int *bit) {
int sample;
int n, sum;
sum = 0;
n = 0;
if (bitstart) {
n = 1; // d.h. bitgrenze = sample_count-1 (?)
bitgrenze = sample_count-1;
bitstart = 0;
}
bitgrenze += samples_per_bit;
do {
sample = read_signed_sample(fp);
if (sample == EOF_INT) return EOF;
//sample_count++; // in read_signed_sample()
//par = (sample >= 0) ? 1 : -1; // 8bit: 0..127,128..255 (-128..-1,0..127)
sum += sample;
n++;
} while (sample_count < bitgrenze); // n < samples_per_bit
if (sum >= 0) *bit = 1;
else *bit = 0;
if (option_inv) *bit ^= 1;
return 0;
}
/* ------------------------------------------------------------------------------------ */
// manchester1 1->10,0->01: 1.bit
// manchester2 0->10,1->01: 2.bit
// RS92-SGP: 8N1 manchester2
char manch(char *mbits) {
if ((mbits[0] == 1) && (mbits[1] == 0)) return 0;
else if ((mbits[0] == 0) && (mbits[1] == 1)) return 1;
else return -1;
}
int bits2byte(char bits[]) {
int i, byteval=0, d=1;
int bit8[8];
if (manch(bits+0) != 0) return 0x100;
for (i = 0; i < 8; i++) {
bit8[i] = manch(bits+2*(i+1));
}
if (manch(bits+(2*(8+1))) != 1) return 0x100;
for (i = 0; i < 8; i++) { // little endian
if (bit8[i] == 1) byteval += d;
else if (bit8[i] == 0) byteval += 0;
else return 0x100;
d <<= 1;
}
return byteval;
}
void inc_bufpos() {
bufpos = (bufpos+1) % HEADLEN;
}
int compare() {
int i=0, j = bufpos;
while (i < HEADLEN) {
if (j < 0) j = HEADLEN-1;
if (buf[j] != header[HEADOFS+HEADLEN-1-i]) break;
j--;
i++;
}
return i;
}
#endif
/*
uint8_t xorbyte(int pos) {
return xframe[pos] ^ mask[pos % MASK_LEN];
}
*/
uint8_t framebyte(int pos) {
return frame[pos];
}
@ -698,53 +460,6 @@ void prn12(uint8_t *prn_le, uint8_t prns[12]) {
}
int calc_satpos_alm(EPHEM_t alm[], double t, SAT_t *satp) {
return -1;
#if 0
double X, Y, Z, vX, vY, vZ;
int j;
int week;
double cl_corr, cl_drift;
for (j = 1; j < 33; j++) {
if (alm[j].prn > 0) { // prn==j
// Woche hat 604800 sec
if (t-alm[j].toa > WEEKSEC/2) rollover = +1;
else if (t-alm[j].toa < -WEEKSEC/2) rollover = -1;
else rollover = 0;
week = alm[j].week - rollover;
/*if (j == 1)*/ gpx.week = week + GPS_WEEK1024*1024;
if (option_vel >= 2) {
GPS_SatellitePositionVelocity_Ephem(
week, t, alm[j],
&cl_corr, &cl_drift, &X, &Y, &Z, &vX, &vY, &vZ
);
satp[alm[j].prn].clock_drift = cl_drift;
satp[alm[j].prn].vX = vX;
satp[alm[j].prn].vY = vY;
satp[alm[j].prn].vZ = vZ;
}
else {
GPS_SatellitePosition_Ephem(
week, t, alm[j],
&cl_corr, &X, &Y, &Z
);
}
satp[alm[j].prn].X = X;
satp[alm[j].prn].Y = Y;
satp[alm[j].prn].Z = Z;
satp[alm[j].prn].clock_corr = cl_corr;
}
}
return 0;
#endif
}
int calc_satpos_rnx(EPHEM_t eph[][24], double t, SAT_t *satp) {
double X, Y, Z, vX, vY, vZ;
int j, i, ti;
@ -924,12 +639,10 @@ int get_pseudorange() {
// GPS Sat Pos (& Vel)
//if (almanac) calc_satpos_alm( alm, gpstime/1000.0, sat);
if (ephem) calc_satpos_rnx2(ephs, gpstime/1000.0, sat);
// GPS Sat Pos t -= 1s
if (option_vel == 1) {
//if (almanac) calc_satpos_alm( alm, gpstime/1000.0-1, sat1s);
if (ephem) calc_satpos_rnx2(ephs, gpstime/1000.0-1, sat1s);
}
@ -1115,27 +828,33 @@ int naiv_2Dfix(int N, SAT_t sats[], double alt) {
int get_GPSkoord(int N) {
double lat, lon, alt, rx_cl_bias;
double vH, vD, vU;
double lat1s, lon1s, alt1s,
lat0 , lon0 , alt0 , pos0_ecef[3];
double pos_ecef[3], pos1s_ecef[3], dpos_ecef[3],
double pos_ecef[3], dpos_ecef[3],
vel_ecef[3], dvel_ecef[3];
double gdop, gdop0 = 1000.0;
//double hdop, vdop, pdop;
int i0, i1, i2, i3, j, k, n;
int i0, i1, i2, i3, j;
int nav_ret = 0;
int num = 0;
SAT_t Sat_A[4];
#if 0
int k, n;
double lat1s, lon1s, alt1s,
lat0 , lon0 , alt0 , pos0_ecef[3];
double pos1s_ecef[3];
SAT_t Sat_B[12]; // N <= 12
SAT_t Sat_B1s[12];
SAT_t Sat_C[12]; // 11
double diter = 0;
int exN = -1;
#endif
double diter = 0;
#if 0
if (option_vergps == 8) {
fprintf(stdout, " sats: ");
for (j = 0; j < N; j++) fprintf(stdout, "%02d ", prn[j]);
fprintf(stdout, "\n");
}
#endif
gpx.lat = gpx.lon = gpx.alt = 0;
@ -1166,6 +885,7 @@ int get_GPSkoord(int N) {
for (j=0; j<3; j++) vel_ecef[j] += dvel_ecef[j];
get_GPSvel(lat, lon, vel_ecef, &vH, &vD, &vU);
}
#if 0
if (option_vergps == 8) {
// gdop = sqrt(DOP[0]+DOP[1]+DOP[2]+DOP[3]); // s.o.
//hdop = sqrt(DOP[0]+DOP[1]);
@ -1186,6 +906,7 @@ int get_GPSkoord(int N) {
fprintf(stdout, "\n");
}
}
#endif
}
else gdop = -1;
@ -1209,6 +930,7 @@ int get_GPSkoord(int N) {
}}}}
}
#if 0
if (option_vergps == 8 || option_vergps == 2) {
for (j = 0; j < N; j++) Sat_B[j] = sat[prn[j]];
@ -1335,6 +1057,7 @@ int get_GPSkoord(int N) {
}
}
#endif
return num;
}
@ -1472,264 +1195,10 @@ void get_eph(const char *file) {
if (ephs) {
ephem = 1;
almanac = 0;
}
} else {
ephstate = EPH_EPHERROR;
}
Serial.printf("reading RNX done, result is %d, ephs=%p\n", ephem, ephs);
if (!option_der) d_err = 1000;
}
#if 0
int main(int argc, char *argv[]) {
FILE *fp, *fp_alm = NULL, *fp_eph = NULL;
char *fpname;
char bitbuf[BITS];
int bit_count = 0,
byte_count = FRAMESTART,
header_found = 0,
byte, i;
int bit, len;
char *pbuf = NULL;
#ifdef CYGWIN
_setmode(_fileno(stdin), _O_BINARY);
#endif
setbuf(stdout, NULL);
fpname = argv[0];
++argv;
while ((*argv) && (!fileloaded)) {
if ( (strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--help") == 0) ) {
Serial.printf("%s [options] <file>\n", fpname);
Serial.printf(" file: audio.wav or raw_data\n");
Serial.printf(" options:\n");
Serial.printf(" --vel; --vel1, --vel2 (-g2)\n");
Serial.printf(" -v, -vx, -vv\n");
Serial.printf(" -r, --raw\n");
Serial.printf(" -i, --invert\n");
Serial.printf(" -a, --almanac <almanacSEM>\n");
Serial.printf(" -e, --ephem <ephemperisRinex>\n");
Serial.printf(" -g1 (verbose GPS: 4 sats)\n");
Serial.printf(" -g2 (verbose GPS: all sats)\n");
Serial.printf(" -gg (vverbose GPS)\n");
Serial.printf(" --crc (CRC check GPS)\n");
Serial.printf(" --rawin1,2 (raw_data file)\n");
return 0;
}
else if ( (strcmp(*argv, "--vel") == 0) ) {
option_vel = 4;
}
else if ( (strcmp(*argv, "--vel1") == 0) ) {
option_vel = 1;
if (option_vergps < 1) option_vergps = 2;
}
else if ( (strcmp(*argv, "--vel2") == 0) ) {
option_vel = 2;
if (option_vergps < 1) option_vergps = 2;
}
else if ( (strcmp(*argv, "--iter") == 0) ) {
option_iter = 1;
}
else if ( (strcmp(*argv, "-v") == 0) ) {
option_verbose = 1;
}
else if ( (strcmp(*argv, "-vv") == 0) ) {
option_verbose = 4;
}
else if ( (strcmp(*argv, "-vx") == 0) ) {
option_aux = 1;
}
else if (strcmp(*argv, "--crc") == 0) { option_crc = 1; }
else if ( (strcmp(*argv, "-r") == 0) || (strcmp(*argv, "--raw") == 0) ) {
option_raw = 1;
}
else if ( (strcmp(*argv, "-i") == 0) || (strcmp(*argv, "--invert") == 0) ) {
option_inv = 1;
}
else if ( (strcmp(*argv, "-a") == 0) || (strcmp(*argv, "--almanac") == 0) ) {
++argv;
if (*argv) fp_alm = fopen(*argv, "r"); // txt-mode
else return -1;
if (fp_alm == NULL) Serial.printf("[almanac] %s konnte nicht geoeffnet werden\n", *argv);
}
else if ( (strcmp(*argv, "-e") == 0) || (strncmp(*argv, "--ephem", 7) == 0) ) {
++argv;
if (*argv) fp_eph = fopen(*argv, "rb"); // bin-mode
else return -1;
if (fp_eph == NULL) Serial.printf("[rinex] %s konnte nicht geoeffnet werden\n", *argv);
}
else if ( (strcmp(*argv, "--dop") == 0) ) {
++argv;
if (*argv) {
dop_limit = atof(*argv);
if (dop_limit <= 0 || dop_limit >= 100) dop_limit = 9.9;
}
else return -1;
}
else if ( (strcmp(*argv, "--der") == 0) ) {
++argv;
if (*argv) {
d_err = atof(*argv);
if (d_err <= 0 || d_err >= 100000) d_err = 10000;
else option_der = 1;
}
else return -1;
}
else if ( (strcmp(*argv, "--exsat") == 0) ) {
++argv;
if (*argv) {
exSat = atoi(*argv);
if (exSat < 1 || exSat > 32) exSat = -1;
}
else return -1;
}
else if (strcmp(*argv, "-g1") == 0) { option_vergps = 1; } // verbose1 GPS
else if (strcmp(*argv, "-g2") == 0) { option_vergps = 2; } // verbose2 GPS (bancroft)
else if (strcmp(*argv, "-gg") == 0) { option_vergps = 8; } // vverbose GPS
else if (strcmp(*argv, "--rawin1") == 0) { rawin = 2; } // raw_txt input1
else if (strcmp(*argv, "--rawin2") == 0) { rawin = 3; } // raw_txt input2 (SM)
else if ( (strcmp(*argv, "--avg") == 0) ) {
option_avg = 1;
}
else if (strcmp(*argv, "-b") == 0) { option_b = 1; }
else {
if (!rawin) fp = fopen(*argv, "rb");
else fp = fopen(*argv, "r");
if (fp == NULL) {
Serial.printf("%s konnte nicht geoeffnet werden\n", *argv);
return -1;
}
fileloaded = 1;
}
++argv;
}
if (!fileloaded) fp = stdin;
if (fp_alm) {
i = read_SEMalmanac(fp_alm, alm);
if (i == 0) {
almanac = 1;
}
fclose(fp_alm);
if (!option_der) d_err = 4000;
}
if (fp_eph) {
/* i = read_RNXephemeris(fp_eph, eph);
if (i == 0) {
ephem = 1;
almanac = 0;
}
fclose(fp_eph); */
ephs = read_RNXpephs(fp_eph);
if (ephs) {
ephem = 1;
almanac = 0;
}
fclose(fp_eph);
if (!option_der) d_err = 1000;
}
if (!rawin) {
i = read_wav_header(fp);
if (i) {
fclose(fp);
return -1;
}
while (!read_bits_fsk(fp, &bit, &len)) {
if (len == 0) { // reset_frame();
if (byte_count > pos_SondeID+8) {
if (byte_count < FRAME_LEN-20) err_gps = 1;
print_frame(byte_count);
err_gps = 0;
}
bit_count = 0;
byte_count = FRAMESTART;
header_found = 0;
inc_bufpos();
buf[bufpos] = 'x';
continue; // ...
}
for (i = 0; i < len; i++) {
inc_bufpos();
buf[bufpos] = 0x30 + bit; // Ascii
if (!header_found) {
if (compare() >= HEADLEN) header_found = 1;
}
else {
bitbuf[bit_count] = bit;
bit_count++;
if (bit_count == BITS) {
bit_count = 0;
byte = bits2byte(bitbuf);
frame[byte_count] = byte;
byte_count++;
if (byte_count == FRAME_LEN) {
byte_count = FRAMESTART;
header_found = 0;
//inc_bufpos();
//buf[bufpos] = 'x';
print_frame(FRAME_LEN);
}
}
}
}
if (header_found && option_b) {
bitstart = 1;
while ( byte_count < FRAME_LEN ) {
if (read_rawbit(fp, &bit) == EOF) break;
bitbuf[bit_count] = bit;
bit_count++;
if (bit_count == BITS) {
bit_count = 0;
byte = bits2byte(bitbuf);
frame[byte_count] = byte;
byte_count++;
}
}
header_found = 0;
print_frame(byte_count);
byte_count = FRAMESTART;
}
}
}
else //if (rawin)
{
if (rawin == 3) frameofs = 5;
while (1 > 0) {
pbuf = fgets(buffer_rawin, rawin*FRAME_LEN+4, fp);
if (pbuf == NULL) break;
buffer_rawin[rawin*FRAME_LEN+1] = '\0';
len = strlen(buffer_rawin) / rawin;
if (len > pos_SondeID+8) {
for (i = 0; i < len-frameofs; i++) { //%2x SCNx8=%hhx(inttypes.h)
sscanf(buffer_rawin+rawin*i, "%2hhx", frame+frameofs+i);
// wenn ohne %hhx: sscanf(buffer_rawin+rawin*i, "%2x", &byte); frame[frameofs+i] = (uint8_t)byte;
}
if (len < FRAME_LEN-20) err_gps = 1;
print_frame(len);
err_gps = 0;
}
}
}
free(ephs);
fclose(fp);
return 0;
}
#endif

0
libraries/SondeLib/rs92gps.h → RX_FSK/src/rs92gps.h Executable file → Normal file
View File

0
libraries/SondeLib/rsc.cpp → RX_FSK/src/rsc.cpp Executable file → Normal file
View File

0
libraries/SondeLib/rsc.h → RX_FSK/src/rsc.h Executable file → Normal file
View File

View File

BIN
RX_FSK/update.ino.bin Normal file

Binary file not shown.

4
RX_FSK/version.h Normal file
View File

@ -0,0 +1,4 @@
const char *version_name = "RadioSonde";
const char *version_id = "1.0.0";
const int SPIFFS_MAJOR=1;
const int SPIFFS_MINOR=0;

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +0,0 @@
Radiosonde
Radiosonde
Terreandro
terrexavier
TERREWIFI
terrexavier

View File

@ -1,24 +0,0 @@
# Frequency in Mhz (format nnn.nnn)
# Type (4=RS41, R=RS92, 6=DFM normal, DFM-06, 9=DFM inverted, DFM-09, M=M10)
#
400.000 9 + Test(FR)
401.500 4 - Santander(ES)
402.000 M + Nimes(FR)
402.800 4 + Cuneo(IT)
403.000 6 + Ajactio(FR)
403.010 6 + Canjuers(FR)
404.200 4 + Rome(IT)
404.500 4 - Payern(CH)
404.500 M - Bourges(FR)
404.789 9 + Frejus1(FR)
404.800 4 + Milan(IT)
405.000 R + Frejus2(FR)
401.199 M - Trappes(FR)
405.789 9 + Pegomas(FR)
405.100 4 - Lindenberg
405.700 4 - Bergen
405.900 4 - Bergen_2
405.100 4 - Meppen_2
405.300 4 - Essen
405.500 4 - Essen_2
# end

View File

@ -1,10 +0,0 @@
<html>
<head>
<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">
</head>
<body>
<button class="tablinks" onclick="javascript:window.open('http://wx.dl2mf.de/','_blank')"> Sonde Map</button><br><br>
<button class="tablinks" onclick="javascript:window.open('http://radiosondy.info/','_self')"> RadioSondy</button><br><br>
<button class="tablinks" onclick="javascript:window.open('http://tracker.sondehub.org/','_blank')"> SondeHub</button><br><br>
</body>
</html>

View File

@ -1,4 +0,0 @@
const char *version_name = "RadioSonde";
const char *version_id = "0.8.8";
//const int SPIFFS_MAJOR=3;
//const int SPIFFS_MINOR=5;

View File

@ -38,10 +38,8 @@ Installer "U8g2"
Installer "MicroNMEA"
Installer "TFT_22_ILI9225" nécessaire pas pour l'écran car j'ai tout supprimé
mais pour les fonts, car le TTGO fonctionne avec OLED SSD1306 par défaut!
## Ajouter les bibliothèques, parties 2
Depuis https://github.com/me-no-dev/ESPAsyncWebServer télécharger le ZIP , l'extraire dans "libraries"
@ -52,6 +50,7 @@ Depuis https://github.com/me-no-dev/AsyncTCP télécharger le ZIP, l'extraire da
de même pour https://github.com/lewisxhe/AXP202X_Library télécharger le ZIP, l'extraire dans "libraries", et renommer le répertoire en AXP202X_Library
aussi https://www.arduino.cc/reference/en/libraries/gfx-library-for-arduino/
## Ajouter les bibliothèques, parties 3
Copier libraries/SX1278FSK
@ -69,12 +68,6 @@ ln -s <whereyouclonedthegit>/radiosonde/libraries/SX1278FSK/ .
```
Redémarrer Arduino IDE
## Ajout carte esp32
Allez dans Outils-> type de cartes -> gestionnaire de cartes
puis dans la case taper esp32 et Installer.
## Dernière parties
Dans Outils -> Esp32 arduino: ->

View File

@ -1,347 +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
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();

View File

@ -1,529 +0,0 @@
/* M10 decoder functions */
#include "M10.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#include <SPIFFS.h>
// well...
//#include "rs92gps.h"
#define M10_DEBUG 1
#if M10_DEBUG
#define M10_DBG(x) x
#else
#define M10_DBG(x)
#endif
static byte data1[512];
static byte *dataptr=data1;
static uint8_t rxbitc;
static uint16_t rxbyte;
static int rxp=0;
static int haveNewFrame = 0;
static int lastFrame = 0;
static int headerDetected = 0;
int M10::setup(float frequency)
{
#if M10_DEBUG
Serial.println("Setup sx1278 for M10 sonde");
#endif
//if(!initialized) {
//Gencrctab();
//initrsc();
// not here for now.... get_eph("/brdc.19n");
// initialized = true;
//}
if(sx1278.ON()!=0) {
M10_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
M10_DBG(Serial.println("Setting FSJ mode FAILED"));
return 1;
}
if(sx1278.setBitrate(9600)!=0) {
M10_DBG(Serial.println("Setting bitrate 9600bit/s FAILED"));
return 1;
}
#if M10_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs92.rxbw)!=0) {
M10_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs92.rxbw)!=0) {
M10_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs92.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
M10_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="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
//const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
// was 0x57
//const char *SYNC="\x99\x9A";
#if 1
// version 1, working with continuous RX
const char *SYNC="\x66\x65";
if(sx1278.setSyncConf(0x70, 2, (const uint8_t *)SYNC)!=0) {
M10_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
//if(sx1278.setPreambleDetect(0xA8)!=0) {
if((sx1278.setPreambleDetect(0x9F)!=0)&&((sx1278.setPreambleDetect(0xAF)!=0))) {
M10_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
#endif
#if 0
// version 2, with per-packet rx start, untested
// header is 2a 10 65, i.e. with lsb first
// 0 0101 0100 1 0 0000 1000 1 0 1010 0110 1
// 10 10011001 10011010 01 10 10101010 01101010 01 10 01100110 10010110 01
// preamble 0x6A 0x66 0x6A
// i.e. preamble detector on (0x80), preamble detector size 1 (0x00), preample chip errors??? (0x0A)
// after 2a2a2a2a2a1065
if(sx1278.setPreambleDetect(0xA8)!=0) {
M10_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// sync config: ato restart (01), preamble polarity AA (0), sync on (1), resevered (0), syncsize 2+1 (010) => 0x52
const char *SYNC="\x6A\x66\x69";
if(sx1278.setSyncConf(0x52, 3, (const uint8_t *)SYNC)!=0) {
M10_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
// payload length is ((240 - 7)*10 +6)/8 = 292
#endif
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
M10_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("M10: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
//sx1278.setPayloadLength(292);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
#if M10_DEBUG
M10_DBG(Serial.println("Setting SX1278 config for M10 finished\n"); Serial.println());
#endif
return res;
}
#if 0
int M10::setFrequency(float frequency) {
Serial.print("M10: setting RX frequency to ");
Serial.println(frequency);
int res = sx1278.setFrequency(frequency);
// enable RX
sx1278.setPayloadLength(0); // infinite for now...
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return res;
}
#endif
#if 0
uint32_t M10::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;
}
#endif
M10::M10() {
}
#define M10_FRAMELEN 101
#define M10_CRCPOS 99
void M10::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X ", data[i]);
Serial.print(buf);
}
Serial.println();
}
static int update_checkM10(int c, uint8_t b) {
int c0, c1, t, t6, t7, s;
c1 = c & 0xFF;
// B
b = (b >> 1) | ((b & 1) << 7);
b ^= (b >> 2) & 0xFF;
// A1
t6 = ( c & 1) ^ ((c >> 2) & 1) ^ ((c >> 4) & 1);
t7 = ((c >> 1) & 1) ^ ((c >> 3) & 1) ^ ((c >> 5) & 1);
t = (c & 0x3F) | (t6 << 6) | (t7 << 7);
// A2
s = (c >> 7) & 0xFF;
s ^= (s >> 2) & 0xFF;
c0 = b ^ t ^ s;
return ((c1 << 8) | c0) & 0xFFFF;
}
static bool checkM10crc(uint8_t *msg) {
int i, cs, cs1;
cs = 0;
for (i = 0; i < M10_CRCPOS; i++) {
cs = update_checkM10(cs, msg[i]);
}
cs = cs & 0xFFFF;
cs1 = (msg[M10_CRCPOS] << 8) | msg[M10_CRCPOS+1];
return (cs1 == cs);
}
typedef uint32_t SET256[8];
static SET256 sondeudp_VARSET = {0x03BBBBF0UL,0x80600000UL,0x06A001A0UL,
0x0000001CUL,0x00000000UL,0x00000000UL,0x00000000UL,
0x00000000UL};
// VARSET=SET256{4..9,11..13,15..17,19..21,23..25,53..54,63,69,71,72,85,87,89,90,98..100};
static uint8_t fixcnt[M10_FRAMELEN];
static uint8_t fixbytes[M10_FRAMELEN];
static int32_t getint32(uint8_t *data) {
return (int32_t)( data[3]|(data[2]<<8)|(data[1]<<16)|(data[0]<<24) );
}
static int16_t getint16(uint8_t *data) {
return (int16_t)(data[1]|((uint16_t)data[0]<<8));
}
static char dez(uint8_t nr) {
nr = nr%10;
return '0'+nr;
}
static char hex(uint8_t nr) {
nr = nr&0x0f;
if(nr<10) return '0'+nr;
else return 'A'+nr-10;
}
const static float DEGMUL = 1.0/0xB60B60;
#define VMUL 0.005
#ifndef PI
#define PI (3.1415926535897932384626433832795)
#endif
#define RAD (PI/180)
// ret: 1=frame ok; 2=frame with errors; 0=ignored frame (m10dop-alternativ)
int M10::decodeframeM10(uint8_t *data) {
int repairstep = 16;
int repl = 0;
bool crcok;
// error correction, inspired by oe5dxl's sondeudp
do {
crcok = checkM10crc(data);
if(crcok) break;
repl = 0;
for(int i=0; i<M10_CRCPOS; i++) {
if( ((sondeudp_VARSET[i/32]&(1<<(i%32))) != 1) && (fixcnt[i]>=repairstep) ) {
repl++;
data[i] = fixbytes[i];
}
}
repairstep >>= 1;
} while(repairstep>0);
if(crcok) {
for(int i=0; i<M10_CRCPOS; i++) {
if(fixbytes[i]==data[i] &&fixcnt[i]<255) fixcnt[i]++;
else { fixcnt[i]=0; fixbytes[i]=data[i]; }
}
}
Serial.println(crcok?"CRC OK":"CRC NOT OK");
//M10 0x9F M10Plus 0xAF M20 0x20
if((data[1]==0x9F && data[2]==0x20) || (data[1]==0xAF && data[2]==0x20)) {
Serial.println("Decoding...");
// Its a M10
// getid...
char ids[11];
ids[0] = 'M';
ids[1] = 'E';
ids[2] = hex(data[95]/16);
ids[3] = hex(data[95]);
ids[4] = hex(data[93]);
uint32_t id = data[96] + data[97]*256;
ids[5] = hex(id/4096);
ids[6] = hex(id/256);
ids[7] = hex(id/16);
ids[8] = hex(id);
ids[9] = 0;
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);
strncpy(sonde.si()->ser, ids, 10);
sonde.si()->validID = true;
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
// position data
sonde.si()->lat = getint32(data+14) * DEGMUL;
sonde.si()->lon = getint32(data+18) * DEGMUL;
sonde.si()->alt = getint32(data+22) * 0.001;
float ve = getint16(data+4)*VMUL;
float vn = getint16(data+6)*VMUL;
sonde.si()->vs = getint16(data+8) * VMUL;
sonde.si()->hs = sqrt(ve*ve+vn*vn);
float dir = atan2(vn, ve)*(1.0/RAD);
if(dir<0) dir+=360;
sonde.si()->dir = dir;
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;
sonde.si()->validTime = true;
} else {
Serial.printf("data is %02x %02x %02x\n", data[0], data[1], data[2]);
return 0;
}
return crcok?1:2;
}
static uint32_t rxdata;
static bool rxsearching=true;
// search for
// //101001100110011010011010011001100110100110101010100110101001
// //1010011001100110100110100110 0110.0110 1001.1010 1010.1001 1010.1001 => 0x669AA9A9
void M10::processM10data(uint8_t dt)
{
for(int i=0; i<8; i++) {
uint8_t d = (dt&0x80)?1:0;
dt <<= 1;
rxdata = (rxdata<<1) | d;
//uint8_t value = ((rxdata>>1)^rxdata)&0x01;
//if((rxbitc&1)==1) { rxbyte = (rxbyte>>1) + ((value)<<8); } // mancester decoded data
//rxbyte = (rxbyte>>1) | (d<<8);
if( (rxbitc&1)==0 ) {
// "bit1"
rxbyte = (rxbyte<<1) | d;
} else {
// "bit2" ==> 01 or 10 => 1, otherweise => 0
rxbyte = rxbyte ^ d;
}
//
if(rxsearching) {
if( rxdata == 0xcccca64c || rxdata == 0x333359b3 ) {
rxsearching = false;
rxbitc = 0;
rxp = 0;
#if 1
int rssi=sx1278.getRSSI();
int fei=sx1278.getFEI();
int afc=sx1278.getAFC();
Serial.print("Test: RSSI="); Serial.print(rssi);
Serial.print(" FEI="); Serial.print(fei);
Serial.print(" AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
#endif
}
} else {
rxbitc = (rxbitc+1)%16; // 16;
if(rxbitc == 0) { // got 8 data bit
//Serial.printf("%03x ",rxbyte);
dataptr[rxp++] = rxbyte&0xff; // (rxbyte>>1)&0xff;
#if 0
if(rxp==7 && dataptr[6] != 0x65) {
Serial.printf("wrong start: %02x\n",dataptr[6]);
rxsearching = true;
}
#endif
if(rxp>=M10_FRAMELEN) {
rxsearching = true;
haveNewFrame = decodeframeM10(dataptr);
}
}
}
}
}
int M10::receive() {
unsigned long t0 = millis();
Serial.printf("M10::receive() start at %ld\n",t0);
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");
}
if ( bitRead(value, 2) == 1 ) {
Serial.println("FIFO: ready()");
sx1278.clearIRQFlags();
}
if(bitRead(value, 6) == 0) { // while FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
//Serial.printf("%02x",data);
processM10data(data);
value = sx1278.readRegister(REG_IRQ_FLAGS2);
} else {
if(headerDetected) {
t0 = millis(); // restart timer... don't time out if header detected...
headerDetected = 0;
}
if(haveNewFrame) {
Serial.printf("M10::receive(): new frame complete after %ldms\n", millis()-t0);
printRaw(dataptr, M10_FRAMELEN);
int retval = haveNewFrame==1 ? RX_OK: RX_ERROR;
haveNewFrame = 0;
return retval;
}
delay(2);
}
}
Serial.printf("M10::receive() timed out\n");
return RX_TIMEOUT; // TODO RX_OK;
}
#define M10MAXLEN (240)
int M10::waitRXcomplete() {
// called after complete...
#if 0
Serial.printf("decoding frame %d\n", lastFrame);
print_frame(lastFrame==1?data1:data2, 240);
SondeInfo *si = sonde.sondeList+rxtask.receiveSonde;
si->lat = gpx.lat;
si->lon = gpx.lon;
si->alt = gpx.alt;
si->vs = gpx.vU;
si->hs = gpx.vH;
si->dir = gpx.vD;
si->validPos = 0x3f;
memcpy(si->id, gpx.id, 9);
si->validID = true;
int res=0;
uint32_t t0 = millis();
while( rxtask.receiveResult == 0xFFFF && millis()-t0 < 2000) { delay(20); }
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 = 0xFFFF;
Serial.printf("M10::waitRXcomplete returning %d (%s)\n", res, RXstr[res]);
return res;
#endif
return 0;
}
#if 0
int oldwaitRXcomplete() {
Serial.println("M10: receive frame...\n");
sx1278receiveData = true;
delay(6000); // done in other task....
//sx1278receiveData = false;
#if 0
//sx1278.setPayloadLength(518-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
//sx1278.setPayloadLength(0); // infinite for now...
////// test code for continuous reception
// sx1278.receive(); /// active FSK RX mode -- already done above...
uint8_t value = sx1278.readRegister(REG_IRQ_FLAGS2);
unsigned long previous = millis();
byte ready=0;
uint32_t wait = 8000;
// while not yet done or FIFO not yet empty
// bit 6: FIFO Empty
// bit 2 payload ready
int by=0;
while( (!ready || bitRead(value,6)==0) && (millis() - previous < wait) )
{
if( bitRead(value, 7) ) { Serial.println("FIFO full"); }
if( bitRead(value, 4) ) { Serial.println("FIFO overflow"); }
if( bitRead(value,2)==1 ) ready=1;
if( bitRead(value, 6) == 0 ) { // FIFO not empty
byte data = sx1278.readRegister(REG_FIFO);
process8N1data(data);
by++;
#if 0
if(di==1) {
int rssi=getRSSI();
int fei=getFEI();
int afc=getAFC();
Serial.print("Test: RSSI="); Serial.println(rssi);
Serial.print("Test: FEI="); Serial.println(fei);
Serial.print("Test: AFC="); Serial.println(afc);
sonde.si()->rssi = rssi;
sonde.si()->afc = afc;
}
if(di>520) {
// TODO
Serial.println("TOO MUCH DATA");
break;
}
previous = millis(); // reset timeout after receiving data
#endif
}
value = sx1278.readRegister(REG_IRQ_FLAGS2);
}
Serial.printf("processed %d bytes before end/timeout\n", by);
#endif
/////
#if 0
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; } //if timeout... return 1
printRaw(data, M10MAXLEN);
//for(int i=0; i<M10MAXLEN; i++) { data[i] = reverse(data[i]); }
//printRaw(data, MAXLEN);
//for(int i=0; i<M10MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
//printRaw(data, MAXLEN);
//int res = decode41(data, M10MAXLEN);
#endif
int res=0;
return res==0 ? RX_OK : RX_ERROR;
}
#endif
M10 m10 = M10();

View File

@ -1,556 +0,0 @@
/* RS41 decoder functions */
#include "RS41.h"
#include "SX1278FSK.h"
#include "rsc.h"
#include "Sonde.h"
#define RS41_DEBUG 0
#if RS41_DEBUG
#define RS41_DBG(x) x
#else
#define RS41_DBG(x)
#endif
#define RS41MAXLEN (320)
static byte data[800];
static int dpos = 0;
static uint16_t CRCTAB[256];
#define X2C_DIVR(a, b) ((b) != 0.0f ? (a)/(b) : (a))
#define X2C_DIVL(a, b) ((a)/(b))
static uint32_t X2C_LSH(uint32_t a, int32_t length, int32_t n)
{
uint32_t m;
m = 0;
m = (length == 32) ? 0xFFFFFFFFl : (1 << length) - 1;
if (n > 0) {
if (n >= (int32_t)length)
return 0;
return (a << n) & m;
}
if (n <= (int32_t)-length)
return 0;
return (a >> -n) & m;
}
static double atang2(double x, double y)
{
double w;
if (fabs(x)>fabs(y)) {
w = (double)atan((float)(X2C_DIVL(y,x)));
if (x<0.0) {
if (y>0.0) w = 3.1415926535898+w;
else w = w-3.1415926535898;
}
}
else if (y!=0.0) {
w = (double)(1.5707963267949f-atan((float)(X2C_DIVL(x,
y))));
if (y<0.0) w = w-3.1415926535898;
}
else w = 0.0;
return w;
} /* end atang2() */
static void Gencrctab(void)
{
uint16_t j;
uint16_t i;
uint16_t crc;
for (i = 0U; i<=255U; i++) {
crc = (uint16_t)(i*256U);
for (j = 0U; j<=7U; j++) {
if ((0x8000U & crc)) crc = X2C_LSH(crc,16,1)^0x1021U;
else crc = X2C_LSH(crc,16,1);
} /* end for */
CRCTAB[i] = X2C_LSH(crc,16,-8)|X2C_LSH(crc,16,8);
} /* end for */
} /* end Gencrctab() */
int RS41::setup(float frequency)
{
#if RS41_DEBUG
Serial.println("Setup sx1278 for RS41 sonde");
#endif
if(!initialized) {
Gencrctab();
initrsc();
initialized = true;
}
if(sx1278.ON()!=0) {
RS41_DBG(Serial.println("Setting SX1278 power on FAILED"));
return 1;
}
if(sx1278.setFSK()!=0) {
RS41_DBG(Serial.println("Setting FSK mode FAILED"));
return 1;
}
if(sx1278.setBitrate(4800)!=0) {
RS41_DBG(Serial.println("Setting bitrate 4800bit/s FAILED"));
return 1;
}
#if RS41_DEBUG
float br = sx1278.getBitrate();
Serial.print("Exact bitrate is ");
Serial.println(br);
#endif
if(sx1278.setAFCBandwidth(sonde.config.rs41.agcbw)!=0) {
RS41_DBG(Serial.printf("Setting AFC bandwidth %d Hz FAILED", sonde.config.rs41.agcbw));
return 1;
}
if(sx1278.setRxBandwidth(sonde.config.rs41.rxbw)!=0) {
RS41_DBG(Serial.printf("Setting RX bandwidth to %d Hz FAILED", sonde.config.rs41.rxbw));
return 1;
}
// Enable auto-AFC, auto-AGC, RX Trigger by preamble
if(sx1278.setRxConf(0x1E)!=0) {
RS41_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="\x10\xB6\xCA\x11\x22\x96\x12\xF8";
const char *SYNC="\x08\x6D\x53\x88\x44\x69\x48\x1F";
if(sx1278.setSyncConf(0x57, 8, (const uint8_t *)SYNC)!=0) {
RS41_DBG(Serial.println("Setting SYNC Config FAILED"));
return 1;
}
if(sx1278.setPreambleDetect(0xA8)!=0) {
RS41_DBG(Serial.println("Setting PreambleDetect FAILED"));
return 1;
}
// Packet config 1: fixed len, no mancecer, no crc, no address filter
// Packet config 2: packet mode, no home ctrl, no beackn, msb(packetlen)=0)
if(sx1278.setPacketConfig(0x08, 0x40)!=0) {
RS41_DBG(Serial.println("Setting Packet config FAILED"));
return 1;
}
Serial.print("RS41: setting RX frequency to ");
Serial.println(frequency);
int retval = sx1278.setFrequency(frequency);
dpos = 0;
#if RS41_DEBUG
RS41_DBG(Serial.println("Setting SX1278 config for RS41 finished\n"); Serial.println());
#endif
sx1278.clearIRQFlags();
// the following is already done in receivePacketTimeout()
// sx1278.setPayloadLength(RS41MAXLEN-8); // Expect 320-8 bytes or 518-8 bytes (8 byte header)
// sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
return retval;
}
uint32_t RS41::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;
}
RS41::RS41() {
}
/* RS41 reed solomon decoder, from dxlAPRS
*/
static int32_t reedsolomon41(byte buf[], uint32_t buf_len, uint32_t len2)
{
uint32_t i;
int32_t res1;
/*tb1, */
int32_t res;
char b1[256];
char b[256];
uint32_t eraspos[24];
uint32_t tmp;
for (i = 0UL; i<=255UL; i++) {
b[i] = 0;
b1[i] = 0;
} /* end for */
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
b[230UL-i] = buf[i*2UL+56UL];
b1[230UL-i] = buf[i*2UL+57UL];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
b[254UL-i] = buf[i+8UL];
b1[254UL-i] = buf[i+32UL];
} /* end for */
res = decodersc(b, eraspos, 0);
res1 = decodersc(b1, eraspos, 0);
if (res>0L && res<=12L) {
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
buf[i*2UL+56UL] = b[230UL-i];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+8UL] = b[254UL-i];
} /* end for */
}
if (res1>0L && res1<=12L) {
tmp = len2;
i = 0UL;
if (i<=tmp) for (;; i++) {
buf[i*2UL+57UL] = b1[230UL-i];
if (i==tmp) break;
} /* end for */
for (i = 0UL; i<=23UL; i++) {
buf[i+32UL] = b1[254UL-i];
} /* end for */
}
if (res<0L || res1<0L) return -1L;
else return res+res1;
return 0;
} /* end reedsolomon41() */
static char crcrs(const byte frame[], uint32_t frame_len,
int32_t from, int32_t to)
{
uint16_t crc;
int32_t i;
int32_t tmp;
crc = 0xFFFFU;
tmp = to-3L;
i = from;
if (i<=tmp) for (;; i++) {
crc = X2C_LSH(crc,16,-8)^CRCTAB[(uint32_t)((crc^(uint16_t)(uint8_t)frame[i])&0xFFU)];
if (i==tmp) break;
} /* end for */
return frame[to-1L]==(char)crc && frame[to-2L]==(char)X2C_LSH(crc,
16,-8);
} /* end crcrs() */
static int32_t getint32(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
uint32_t i;
n = 0UL;
for (i = 3UL;; i--) {
n = n*256UL+(uint32_t)(uint8_t)frame[p+i];
if (i==0UL) break;
} /* end for */
return (int32_t)n;
} /* end getint32() */
static uint32_t getcard16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
return (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
} /* end getcard16() */
static int32_t getint16(const byte frame[], uint32_t frame_len,
uint32_t p)
{
uint32_t n;
n = (uint32_t)(uint8_t)frame[p]+256UL*(uint32_t)(uint8_t)
frame[p+1UL];
if (n>=32768UL) return (int32_t)(n-65536UL);
return (int32_t)n;
} /* end getint16() */
static void wgs84r(double x, double y, double z,
double * lat, double * long0,
double * heig)
{
double sl;
double ct;
double st;
double t;
double rh;
double xh;
double h;
h = x*x+y*y;
if (h>0.0) {
rh = (double)sqrt((float)h);
xh = x+rh;
*long0 = atang2(xh, y)*2.0;
if (*long0>3.1415926535898) *long0 = *long0-6.2831853071796;
t = (double)atan((float)(X2C_DIVL(z*1.003364089821,
rh)));
st = (double)sin((float)t);
ct = (double)cos((float)t);
*lat = (double)atan((float)
(X2C_DIVL(z+4.2841311513312E+4*st*st*st,
rh-4.269767270718E+4*ct*ct*ct)));
sl = (double)sin((float)*lat);
*heig = X2C_DIVL(rh,(double)cos((float)*lat))-(double)(X2C_DIVR(6.378137E+6f,
sqrt((float)(1.0-6.6943799901413E-3*sl*sl))));
}
else {
*lat = 0.0;
*long0 = 0.0;
*heig = 0.0;
}
/* lat:=atan(z/(rh*(1.0 - E2))); */
/* heig:=sqrt(h + z*z) - EARTHA; */
} /* end wgs84r() */
// returns: 0=ok, -1=error
static void posrs41(const byte b[], uint32_t b_len, uint32_t p)
{
double dir;
double vu;
double ve;
double vn;
double vz;
double vy;
double vx;
double heig;
double long0;
double lat;
double z;
double y;
double x;
x = (double)getint32(b, b_len, p)*0.01;
y = (double)getint32(b, b_len, p+4UL)*0.01;
z = (double)getint32(b, b_len, p+8UL)*0.01;
wgs84r(x, y, z, &lat, &long0, &heig);
Serial.print(" ");
sonde.si()->lat = (float)(X2C_DIVL(lat,1.7453292519943E-2));
Serial.print(sonde.si()->lat);
Serial.print(" ");
sonde.si()->lon = (float)(X2C_DIVL(long0,1.7453292519943E-2));
Serial.print(sonde.si()->lon);
if (heig<1.E+5 && heig>(-1.E+5)) {
Serial.print(" ");
Serial.print((uint32_t)heig);
Serial.print("m");
}
/*speed */
vx = (double)getint16(b, b_len, p+12UL)*0.01;
vy = (double)getint16(b, b_len, p+14UL)*0.01;
vz = (double)getint16(b, b_len, p+16UL)*0.01;
vn = (-(vx*(double)sin((float)lat)*(double)
cos((float)long0))-vy*(double)
sin((float)lat)*(double)sin((float)
long0))+vz*(double)cos((float)lat);
ve = -(vx*(double)sin((float)long0))+vy*(double)
cos((float)long0);
vu = vx*(double)cos((float)lat)*(double)
cos((float)long0)+vy*(double)
cos((float)lat)*(double)sin((float)
long0)+vz*(double)sin((float)lat);
dir = X2C_DIVL(atang2(vn, ve),1.7453292519943E-2);
if (dir<0.0) dir = 360.0+dir;
sonde.si()->dir = dir;
Serial.print(" ");
sonde.si()->hs = sqrt((float)(vn*vn+ve*ve))*3.6f;
Serial.print(sonde.si()->hs);
Serial.print("km/h ");
Serial.print(dir);
Serial.print("deg ");
Serial.print((float)vu);
sonde.si()->vs = vu;
Serial.print("m/s ");
uint8_t sats = getcard16(b, b_len, p+18UL)&255UL;
Serial.print(sats);
Serial.print("Sats");
sonde.si()->sats = sats;
sonde.si()->alt = heig;
if( 0==(int)(lat*10000) && 0==(int)(long0*10000) ) {
if(sonde.si()->validPos) {
// we have an old position, so keep previous position and mark it as old
sonde.si()->validPos |= 0x80;
}
}
else
sonde.si()->validPos = 0x7f;
} /* end posrs41() */
// returns: 0: ok, -1: rs or crc error
int RS41::decode41(byte *data, int maxlen)
{
char buf[128];
int crcok = 0;
int32_t corr = reedsolomon41(data, 560, 131); // try short frame first
if(corr<0) {
corr = reedsolomon41(data, 560, 230); // try long frame
}
#if 0
Serial.print("RS result:");
Serial.print(corr);
Serial.println();
#endif
int p = 57; // 8 byte header, 48 byte RS
while(p<maxlen) { /* why 555? */
uint8_t typ = data[p++];
uint32_t len = data[p++]+2UL;
if(p+len>maxlen) break;
#if 0
// DEBUG OUTPUT
Serial.print("@");
Serial.print(p-2);
Serial.print(": ID:");
Serial.print(typ, HEX);
Serial.print(", len=");
Serial.print(len);
Serial.print(": ");
for(int i=0; i<len-1; i++) {
char buf[3];
snprintf(buf, 4, "%02X|", data[p+i]);
Serial.print(buf);
}
#endif
// check CRC
if(!crcrs(data, 560, p, p+len)) {
Serial.println("###CRC ERROR###");
} else {
crcok = 1;
switch(typ) {
case 'y': // name
{
Serial.print("#");
uint16_t fnr = data[p]+(data[p+1]<<8);
Serial.print(fnr);
sonde.si()->frame = fnr;
Serial.print("; RS41 ID ");
snprintf(buf, 10, "%.8s ", data+p+2);
Serial.print(buf);
sonde.si()->type=STYPE_RS41;
strncpy(sonde.si()->id, (const char *)(data+p+2), 8);
sonde.si()->id[8]=0;
strncpy(sonde.si()->ser, (const char *)(data+p+2), 8);
sonde.si()->ser[8]=0;
sonde.si()->validID=true;
int calnr = data[p+23];
// not sure about this
if(calnr==0x31) {
uint16_t bt = data[p+30] + 256*data[p+31];
sonde.si()->burstKT = bt;
}
// this should be right...
if(calnr==0x02) {
uint16_t kt = data[p+31] + 256*data[p+32];
sonde.si()->launchKT = kt;
}
// and this seems fine as well...
if(calnr==0x32) {
uint16_t cntdown = data[p+24] + (data[p+25]<<8);
uint16_t min = cntdown - (cntdown/3600)*3600;
Serial.printf("Countdown value: %d\n [%2d:%02d:%02d]", cntdown, cntdown/3600, min/60, min-(min/60)*60);
sonde.si()->countKT = cntdown;
sonde.si()->crefKT = fnr;
}
}
// TODO: some more data
break;
case '|': // date
{
uint32_t gpstime = getint32(data, 560, p+2);
uint16_t gpsweek = getint16(data, 560, p);
// 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;
sonde.si()->validTime = true;
}
break;
case '{': // pos
posrs41(data+p, len, 0);
break;
default:
break;
}}
p += len;
Serial.println();
}
return crcok ? 0 : -1;
}
void RS41::printRaw(uint8_t *data, int len)
{
char buf[3];
int i;
for(i=0; i<len; i++) {
snprintf(buf, 3, "%02X", data[i]);
Serial.print(buf);
}
Serial.println();
}
void RS41::bitsToBytes(uint8_t *bits, uint8_t *bytes, int len)
{
int i;
for(i=0; i<len*4; i++) {
bytes[i/8] = (bytes[i/8]<<1) | (bits[i]?1:0);
}
bytes[(i-1)/8] &= 0x0F;
}
static unsigned char lookup[16] = {
0x0, 0x8, 0x4, 0xc, 0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd, 0x3, 0xb, 0x7, 0xf, };
static uint8_t reverse(uint8_t n) {
return (lookup[n&0x0f] << 4) | lookup[n>>4];
}
static uint8_t scramble[64] = {150U,131U,62U,81U,177U,73U,8U,152U,50U,5U,89U,
14U,249U,68U,198U,38U,33U,96U,194U,234U,121U,93U,109U,161U,
84U,105U,71U,12U,220U,232U,92U,241U,247U,118U,130U,127U,7U,
153U,162U,44U,147U,124U,48U,99U,245U,16U,46U,97U,208U,188U,
180U,182U,6U,170U,244U,35U,120U,110U,59U,174U,191U,123U,76U,
193U};
int RS41::receive() {
sx1278.setPayloadLength(RS41MAXLEN-8);
int e = sx1278.receivePacketTimeout(1000, data+8);
if(e) { Serial.println("TIMEOUT"); return RX_TIMEOUT; }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = reverse(data[i]); }
for(int i=0; i<RS41MAXLEN; i++) { data[i] = data[i] ^ scramble[i&0x3F]; }
return decode41(data, RS41MAXLEN);
}
int RS41::waitRXcomplete() {
// Currently not used. can be used for additinoal post-processing
// (required for RS92 to avoid FIFO overrun in rx task)
#if 0
int res;
uint32_t t0 = millis();
while(rxtask.receiveResult<0 && millis()-t0 < 3000) { 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;
}
RS41 rs41 = RS41();

View File

@ -1,113 +0,0 @@
#include "Scanner.h"
#include <SX1278FSK.h>
#include <U8x8lib.h>
#include "Sonde.h"
#include "Display.h"
#define CHANBW 10
#define PIXSAMPL (50/CHANBW)
#define SMOOTH 3
//#define STARTF 401000000
#define NCHAN ((int)(6000/CHANBW))
double STARTF = (sonde.config.startfreq * 1000000);
//int CHANBW = (sonde.config.channelbw);
//int NCHAN = ((int)(6000/CHANBW));
//int PIXSAMPL = (50/CHANBW);
int scanresult[NCHAN];
int scandisp[NCHAN/PIXSAMPL];
#define PLOT_N 128
#define TICK1 (128/6)
#define TICK2 (TICK1/4)
//#define PLOT_MIN -250
#define PLOT_MIN (sonde.config.noisefloor*2)
#define PLOT_SCALE(x) (x<PLOT_MIN?0:(x-PLOT_MIN)/2)
const byte tilepatterns[9]={0,0x80,0xC0,0xE0,0xF0,0xF8,0xFC,0xFE,0xFF};
void Scanner::fillTiles(uint8_t *row, int value) {
for(int y=0; y<8; y++) {
int nbits = value - 8*(7-y);
if(nbits<0) { row[8*y]=0; continue; }
if(nbits>=8) { row[8*y]=255; continue; }
row[8*y] = tilepatterns[nbits];
}
}
/*
* There are 16*8 columns to plot, NPLOT must be lower than that
* currently, we use 128 * 50kHz channels
* There are 8*8 values to plot; MIN is bottom end,
*/
uint8_t tiles[16] = { 0x0f,0x0f,0x0f,0x0f,0xf0,0xf0,0xf0,0xf0, 1, 3, 7, 15, 31, 63, 127, 255};
void Scanner::plotResult()
{
uint8_t row[8*8];
for(int i=0; i<PLOT_N; i+=8) {
for(int j=0; j<8; j++) {
fillTiles(row+j, PLOT_SCALE(scandisp[i+j]));
if( ((i+j)%TICK1)==0) { row[j] |= 0x07; }
if( ((i+j)%TICK2)==0) { row[j] |= 0x01; }
}
for(int y=0; y<8; y++) {
if(sonde.config.marker && y==1) {
// don't overwrite MHz marker text
if(i<3*8 || (i>=7*8&&i<10*8) || i>=13*8) continue;
}
disp.rdis->drawTile(i/8, y, 1, row+8*y);
}
}
}
void Scanner::scan()
{
// Configure
sx1278.writeRegister(REG_PLL_HOP, 0x80); // FastHopOn
sx1278.setRxBandwidth(CHANBW*1000);
sx1278.writeRegister(REG_RSSI_CONFIG, SMOOTH&0x07);
sx1278.setFrequency(STARTF);
sx1278.writeRegister(REG_OP_MODE, FSK_RX_MODE);
delay(20);
unsigned long start = millis();
uint32_t lastfrf=-1;
for(int iter=0; iter<2; iter++) { // two interations, to catch all RS41 transmissions
for(int i=0; i<NCHAN; i++) {
float freq = STARTF + 1000.0*i*CHANBW;
uint32_t frf = freq * 1.0 * (1<<19) / SX127X_CRYSTAL_FREQ;
if( (lastfrf>>16)!=(frf>>16) ) {
sx1278.writeRegister(REG_FRF_MSB, (frf&0xff0000)>>16);
}
if( ((lastfrf&0x00ff00)>>8) != ((frf&0x00ff00)>>8) ) {
sx1278.writeRegister(REG_FRF_MID, (frf&0x00ff00)>>8);
}
sx1278.writeRegister(REG_FRF_LSB, (frf&0x0000ff));
lastfrf = frf;
// Wait TS_HOP (20us) + TS_RSSI ( 2^(SMOOTH+1) / 4 / CHANBW us)
int wait = 20 + 1000*(1<<(SMOOTH+1))/4/CHANBW;
delayMicroseconds(wait+5);
int rssi = -(int)sx1278.readRegister(REG_RSSI_VALUE_FSK);
if(iter==0) { scanresult[i] = rssi; } else {
if(rssi>scanresult[i]) scanresult[i]=rssi;
}
}
}
unsigned long duration = millis()-start;
Serial.print("Scan time: ");
Serial.println(duration);
for(int i=0; i<NCHAN; i+=PIXSAMPL) {
scandisp[i/PIXSAMPL]=scanresult[i];
for(int j=1; j<PIXSAMPL; j++) { scandisp[i/PIXSAMPL]+=scanresult[i+j]; }
//for(int j=1; j<PIXSAMPL; j++) { if(scanresult[i+j]>scandisp[i/PIXSAMPL]) scandisp[i/PIXSAMPL] = scanresult[i+j]; }
Serial.print(scanresult[i]); Serial.print(", ");
}
Serial.println("\n");
for(int i=0; i<NCHAN/PIXSAMPL; i++) {
scandisp[i]/=PIXSAMPL;
Serial.print(scandisp[i]); Serial.print(", ");
}
Serial.println("\n");
}
Scanner scanner = Scanner();

File diff suppressed because it is too large Load Diff

View File

@ -1,468 +0,0 @@
// SPDX-License-Identifier: GPL-3.0
// original source: https://github.com/Nkawu/TFT22_ILI9225
#ifndef TFT22_ILI9225_h
#define TFT22_ILI9225_h
#ifdef __STM32F1__
#define ARDUINO_STM32_FEATHER
#define PROGMEM
// if 'SPI_CHANNEL' is not defined, 'SPI' is used, only valid for STM32F1
//#define SPI_CHANNEL SPI_2
#endif
#define USE_STRING_CLASS
#ifdef USE_STRING_CLASS
#define STRING String
#else
#define STRING const char *
#endif
#if ARDUINO >= 100
#include "Arduino.h"
#else
#include "WProgram.h"
#endif
#include <SPI.h>
#include "gfxfont.h"
#if defined(ARDUINO_STM32_FEATHER) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(STM32F1)
typedef volatile uint32 RwReg;
#endif
#if defined(ARDUINO_FEATHER52)
typedef volatile uint32_t RwReg;
#endif
/* ILI9225 screen size */
#define ILI9225_LCD_WIDTH 176
#define ILI9225_LCD_HEIGHT 220
/* ILI9225 LCD Registers */
#define ILI9225_DRIVER_OUTPUT_CTRL (0x01u) // Driver Output Control
#define ILI9225_LCD_AC_DRIVING_CTRL (0x02u) // LCD AC Driving Control
#define ILI9225_ENTRY_MODE (0x03u) // Entry Mode
#define ILI9225_DISP_CTRL1 (0x07u) // Display Control 1
#define ILI9225_BLANK_PERIOD_CTRL1 (0x08u) // Blank Period Control
#define ILI9225_FRAME_CYCLE_CTRL (0x0Bu) // Frame Cycle Control
#define ILI9225_INTERFACE_CTRL (0x0Cu) // Interface Control
#define ILI9225_OSC_CTRL (0x0Fu) // Osc Control
#define ILI9225_POWER_CTRL1 (0x10u) // Power Control 1
#define ILI9225_POWER_CTRL2 (0x11u) // Power Control 2
#define ILI9225_POWER_CTRL3 (0x12u) // Power Control 3
#define ILI9225_POWER_CTRL4 (0x13u) // Power Control 4
#define ILI9225_POWER_CTRL5 (0x14u) // Power Control 5
#define ILI9225_VCI_RECYCLING (0x15u) // VCI Recycling
#define ILI9225_RAM_ADDR_SET1 (0x20u) // Horizontal GRAM Address Set
#define ILI9225_RAM_ADDR_SET2 (0x21u) // Vertical GRAM Address Set
#define ILI9225_GRAM_DATA_REG (0x22u) // GRAM Data Register
#define ILI9225_GATE_SCAN_CTRL (0x30u) // Gate Scan Control Register
#define ILI9225_VERTICAL_SCROLL_CTRL1 (0x31u) // Vertical Scroll Control 1 Register
#define ILI9225_VERTICAL_SCROLL_CTRL2 (0x32u) // Vertical Scroll Control 2 Register
#define ILI9225_VERTICAL_SCROLL_CTRL3 (0x33u) // Vertical Scroll Control 3 Register
#define ILI9225_PARTIAL_DRIVING_POS1 (0x34u) // Partial Driving Position 1 Register
#define ILI9225_PARTIAL_DRIVING_POS2 (0x35u) // Partial Driving Position 2 Register
#define ILI9225_HORIZONTAL_WINDOW_ADDR1 (0x36u) // Horizontal Address Start Position
#define ILI9225_HORIZONTAL_WINDOW_ADDR2 (0x37u) // Horizontal Address End Position
#define ILI9225_VERTICAL_WINDOW_ADDR1 (0x38u) // Vertical Address Start Position
#define ILI9225_VERTICAL_WINDOW_ADDR2 (0x39u) // Vertical Address End Position
#define ILI9225_GAMMA_CTRL1 (0x50u) // Gamma Control 1
#define ILI9225_GAMMA_CTRL2 (0x51u) // Gamma Control 2
#define ILI9225_GAMMA_CTRL3 (0x52u) // Gamma Control 3
#define ILI9225_GAMMA_CTRL4 (0x53u) // Gamma Control 4
#define ILI9225_GAMMA_CTRL5 (0x54u) // Gamma Control 5
#define ILI9225_GAMMA_CTRL6 (0x55u) // Gamma Control 6
#define ILI9225_GAMMA_CTRL7 (0x56u) // Gamma Control 7
#define ILI9225_GAMMA_CTRL8 (0x57u) // Gamma Control 8
#define ILI9225_GAMMA_CTRL9 (0x58u) // Gamma Control 9
#define ILI9225_GAMMA_CTRL10 (0x59u) // Gamma Control 10
#define ILI9225C_INVOFF 0x20
#define ILI9225C_INVON 0x21
// autoincrement modes (register ILI9225_ENTRY_MODE, bit 5..3 )
enum autoIncMode_t { R2L_BottomUp, BottomUp_R2L, L2R_BottomUp, BottomUp_L2R, R2L_TopDown, TopDown_R2L, L2R_TopDown, TopDown_L2R };
/* RGB 16-bit color table definition (RG565) */
#define COLOR_BLACK 0x0000 /* 0, 0, 0 */
#define COLOR_WHITE 0xFFFF /* 255, 255, 255 */
#define COLOR_BLUE 0x001F /* 0, 0, 255 */
#define COLOR_GREEN 0x07E0 /* 0, 255, 0 */
#define COLOR_RED 0xF800 /* 255, 0, 0 */
#define COLOR_NAVY 0x000F /* 0, 0, 128 */
#define COLOR_DARKBLUE 0x0011 /* 0, 0, 139 */
#define COLOR_DARKGREEN 0x03E0 /* 0, 128, 0 */
#define COLOR_DARKCYAN 0x03EF /* 0, 128, 128 */
#define COLOR_CYAN 0x07FF /* 0, 255, 255 */
#define COLOR_TURQUOISE 0x471A /* 64, 224, 208 */
#define COLOR_INDIGO 0x4810 /* 75, 0, 130 */
#define COLOR_DARKRED 0x8000 /* 128, 0, 0 */
#define COLOR_OLIVE 0x7BE0 /* 128, 128, 0 */
#define COLOR_GRAY 0x8410 /* 128, 128, 128 */
#define COLOR_GREY 0x8410 /* 128, 128, 128 */
#define COLOR_SKYBLUE 0x867D /* 135, 206, 235 */
#define COLOR_BLUEVIOLET 0x895C /* 138, 43, 226 */
#define COLOR_LIGHTGREEN 0x9772 /* 144, 238, 144 */
#define COLOR_DARKVIOLET 0x901A /* 148, 0, 211 */
#define COLOR_YELLOWGREEN 0x9E66 /* 154, 205, 50 */
#define COLOR_BROWN 0xA145 /* 165, 42, 42 */
#define COLOR_DARKGRAY 0x7BEF /* 128, 128, 128 */
#define COLOR_DARKGREY 0x7BEF /* 128, 128, 128 */
#define COLOR_SIENNA 0xA285 /* 160, 82, 45 */
#define COLOR_LIGHTBLUE 0xAEDC /* 172, 216, 230 */
#define COLOR_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
#define COLOR_SILVER 0xC618 /* 192, 192, 192 */
#define COLOR_LIGHTGRAY 0xC618 /* 192, 192, 192 */
#define COLOR_LIGHTGREY 0xC618 /* 192, 192, 192 */
#define COLOR_LIGHTCYAN 0xE7FF /* 224, 255, 255 */
#define COLOR_VIOLET 0xEC1D /* 238, 130, 238 */
#define COLOR_AZUR 0xF7FF /* 240, 255, 255 */
#define COLOR_BEIGE 0xF7BB /* 245, 245, 220 */
#define COLOR_MAGENTA 0xF81F /* 255, 0, 255 */
#define COLOR_TOMATO 0xFB08 /* 255, 99, 71 */
#define COLOR_GOLD 0xFEA0 /* 255, 215, 0 */
#define COLOR_ORANGE 0xFD20 /* 255, 165, 0 */
#define COLOR_SNOW 0xFFDF /* 255, 250, 250 */
#define COLOR_YELLOW 0xFFE0 /* 255, 255, 0 */
/* Font defines */
#define FONT_HEADER_SIZE 4 // 1: pixel width of 1 font character, 2: pixel height,
#define readFontByte(x) pgm_read_byte(&cfont.font[x])
extern uint8_t Terminal6x8[];
extern uint8_t Terminal11x16[];
extern uint8_t Terminal12x16[];
extern uint8_t Trebuchet_MS16x21[];
struct _currentFont
{
uint8_t* font;
uint8_t width;
uint8_t height;
uint8_t offset;
uint8_t numchars;
uint8_t nbrows;
bool monoSp;
};
#define MONOSPACE 1
#if defined (ARDUINO_STM32_FEATHER)
#undef USE_FAST_PINIO
#elif defined (__AVR__) || defined(TEENSYDUINO) || defined(ESP8266) || defined(__arm__)
#define USE_FAST_PINIO
#endif
/// Main and core class
class TFT22_ILI9225 {
public:
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t SDI, int8_t CLK, int8_t LED);
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t LED);
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t SDI, int8_t CLK, int8_t LED, uint8_t brightness);
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t LED, uint8_t brightness);
/// Initialization
#ifndef ESP32
void begin(void);
#else
void begin(SPIClass &spi=SPI);
#endif
/// Clear the screen
void clear(void);
/// Invert screen
/// @param flag true to invert, false for normal screen
void invert(boolean flag);
/// Switch backlight on or off
/// @param flag true=on, false=off
void setBacklight(boolean flag);
/// Set backlight brightness
/// @param brightness sets backlight brightness 0-255
void setBacklightBrightness(uint8_t brightness);
/// Switch display on or off
/// @param flag true=on, false=off
void setDisplay(boolean flag);
/// Set orientation
/// @param orientation orientation, 0=portrait, 1=right rotated landscape, 2=reverse portrait, 3=left rotated landscape
void setOrientation(uint8_t orientation);
/// Get orientation
/// @return orientation orientation, 0=portrait, 1=right rotated landscape, 2=reverse portrait, 3=left rotated landscape
uint8_t getOrientation(void);
/// Font size, x-axis
/// @return horizontal size of current font, in pixels
// uint8_t fontX(void);
/// Font size, y-axis
/// @return vertical size of current font, in pixels
// uint8_t fontY(void);
/// Screen size, x-axis
/// @return horizontal size of the screen, in pixels
/// @note 240 means 240 pixels and thus 0..239 coordinates (decimal)
uint16_t maxX(void);
/// Screen size, y-axis
/// @return vertical size of the screen, in pixels
/// @note 220 means 220 pixels and thus 0..219 coordinates (decimal)
uint16_t maxY(void);
/// Draw circle
/// @param x0 center, point coordinate, x-axis
/// @param y0 center, point coordinate, y-axis
/// @param radius radius
/// @param color 16-bit color
void drawCircle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color);
/// Draw solid circle
/// @param x0 center, point coordinate, x-axis
/// @param y0 center, point coordinate, y-axis
/// @param radius radius
/// @param color 16-bit color
void fillCircle(uint8_t x0, uint8_t y0, uint8_t radius, uint16_t color);
/// Set background color
/// @param color background color, default=black
void setBackgroundColor(uint16_t color = COLOR_BLACK);
/// Draw line, rectangle coordinates
/// @param x1 start point coordinate, x-axis
/// @param y1 start point coordinate, y-axis
/// @param x2 end point coordinate, x-axis
/// @param y2 end point coordinate, y-axis
/// @param color 16-bit color
void drawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/// Draw rectangle, rectangle coordinates
/// @param x1 top left coordinate, x-axis
/// @param y1 top left coordinate, y-axis
/// @param x2 bottom right coordinate, x-axis
/// @param y2 bottom right coordinate, y-axis
/// @param color 16-bit color
void drawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/// Draw solid rectangle, rectangle coordinates
/// @param x1 top left coordinate, x-axis
/// @param y1 top left coordinate, y-axis
/// @param x2 bottom right coordinate, x-axis
/// @param y2 bottom right coordinate, y-axis
/// @param color 16-bit color
void fillRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
/// Draw pixel
/// @param x1 point coordinate, x-axis
/// @param y1 point coordinate, y-axis
/// @param color 16-bit color
void drawPixel(uint16_t x1, uint16_t y1, uint16_t color);
/// Draw ASCII Text (pixel coordinates)
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param s text string
/// @param color 16-bit color, default=white
/// @return x-position behind text
uint16_t drawText(uint16_t x, uint16_t y, STRING s, uint16_t color = COLOR_WHITE);
/// width of an ASCII Text (pixel )
/// @param s text string
uint16_t getTextWidth( STRING s ) ;
/// Calculate 16-bit color from 8-bit Red-Green-Blue components
/// @param red red component, 0x00..0xff
/// @param green green component, 0x00..0xff
/// @param blue blue component, 0x00..0xff
/// @return 16-bit color
uint16_t setColor(uint8_t red, uint8_t green, uint8_t blue);
/// Calculate 8-bit Red-Green-Blue components from 16-bit color
/// @param rgb 16-bit color
/// @param red red component, 0x00..0xff
/// @param green green component, 0x00..0xff
/// @param blue blue component, 0x00..0xff
void splitColor(uint16_t rgb, uint8_t &red, uint8_t &green, uint8_t &blue);
/// Draw triangle, triangle coordinates
/// @param x1 corner 1 coordinate, x-axis
/// @param y1 corner 1 coordinate, y-axis
/// @param x2 corner 2 coordinate, x-axis
/// @param y2 corner 2 coordinate, y-axis
/// @param x3 corner 3 coordinate, x-axis
/// @param y3 corner 3 coordinate, y-axis
/// @param color 16-bit color
void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color);
/// Draw solid triangle, triangle coordinates
/// @param x1 corner 1 coordinate, x-axis
/// @param y1 corner 1 coordinate, y-axis
/// @param x2 corner 2 coordinate, x-axis
/// @param y2 corner 2 coordinate, y-axis
/// @param x3 corner 3 coordinate, x-axis
/// @param y3 corner 3 coordinate, y-axis
/// @param color 16-bit color
void fillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color);
/// Set current font
/// @param font Font name
void setFont(uint8_t* font, bool monoSp=false ); // default = proportional
/// Get current font
_currentFont getFont();
/// Draw single character (pixel coordinates)
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param ch ASCII character
/// @param color 16-bit color, default=white
/// @return width of character in display pixels
uint16_t drawChar(uint16_t x, uint16_t y, uint16_t ch, uint16_t color = COLOR_WHITE);
/// width of an ASCII character (pixel )
/// @param ch ASCII character
uint16_t getCharWidth( uint16_t ch ) ;
/// Draw bitmap
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param bitmap
/// @param w width
/// @param h height
/// @param color 16-bit color, default=white
/// @param bg 16-bit color, background
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
void drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
void drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
/// Draw bitmap
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param bitmap, 2D 16bit color bitmap
/// @param w width
/// @param h height
void drawBitmap(uint16_t x, uint16_t y, const uint16_t** bitmap, int16_t w, int16_t h);
void drawBitmap(uint16_t x, uint16_t y, uint16_t** bitmap, int16_t w, int16_t h);
/// Draw bitmap
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param bitmap, 1D 16bit color bitmap
/// @param w width
/// @param h height
void drawBitmap(uint16_t x, uint16_t y, const uint16_t* bitmap, int16_t w, int16_t h);
void drawBitmap(uint16_t x, uint16_t y, uint16_t* bitmap, int16_t w, int16_t h);
/// Set current GFX font
/// @param f GFX font name defined in include file
void setGFXFont(const GFXfont *f = NULL);
/// Draw a string with the current GFX font
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param s string to print
/// @param color 16-bit color
void drawGFXText(int16_t x, int16_t y, STRING s, uint16_t color);
/// Get the width & height of a text string with the current GFX font
/// @param str string to analyze
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param w width in pixels of string
/// @param h height in pixels of string
void getGFXTextExtent(STRING str, int16_t x, int16_t y, int16_t *w, int16_t *h);
/// Draw a single character with the current GFX font
/// @param x point coordinate, x-axis
/// @param y point coordinate, y-axis
/// @param c character to draw
/// @param color 16-bit color
/// @return width of character in display pixels
uint16_t drawGFXChar(int16_t x, int16_t y, unsigned char c, uint16_t color);
uint16_t drawGFXcharBM(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t *bm, int bmwd);
void getGFXCharExtent(uint8_t c, int16_t *gw, int16_t *gh, int16_t *xa);
private:
void _spiWrite(uint8_t v);
void _spiWrite16(uint16_t v);
void _spiWriteCommand(uint8_t c);
void _spiWriteData(uint8_t d);
void _swap(uint16_t &a, uint16_t &b);
void _setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void _setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, autoIncMode_t mode);
void _resetWindow();
void _drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h,
uint16_t color, uint16_t bg, bool transparent, bool progmem, bool Xbit );
void _orientCoordinates(uint16_t &x1, uint16_t &y1);
void _writeRegister(uint16_t reg, uint16_t data);
void _writeData(uint8_t HI, uint8_t LO);
void _writeData16(uint16_t HILO);
void _writeCommand(uint8_t HI, uint8_t LO);
void _writeCommand16(uint16_t HILO);
uint16_t _maxX, _maxY, _bgColor;
#if defined (__AVR__) || defined(TEENSYDUINO)
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
#ifdef USE_FAST_PINIO
volatile uint8_t *mosiport, *clkport, *dcport, *rsport, *csport;
uint8_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
#elif defined (__arm__)
int32_t _rst, _rs, _cs, _sdi, _clk, _led;
#ifdef USE_FAST_PINIO
volatile RwReg *mosiport, *clkport, *dcport, *rsport, *csport;
uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
#elif defined (ESP8266) || defined (ESP32)
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
#ifdef USE_FAST_PINIO
volatile uint32_t *mosiport, *clkport, *dcport, *rsport, *csport;
uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
#endif
#else
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
#endif
uint8_t _orientation, _brightness;
// correspondig modes if orientation changed:
const autoIncMode_t modeTab [3][8] = {
// { R2L_BottomUp, BottomUp_R2L, L2R_BottomUp, BottomUp_L2R, R2L_TopDown, TopDown_R2L, L2R_TopDown, TopDown_L2R }//
/* 90° */ { BottomUp_L2R, L2R_BottomUp, TopDown_L2R, L2R_TopDown, BottomUp_R2L, R2L_BottomUp, TopDown_R2L, R2L_TopDown },
/*180° */ { L2R_TopDown , TopDown_L2R, R2L_TopDown, TopDown_R2L, L2R_BottomUp, BottomUp_L2R, R2L_BottomUp, BottomUp_R2L},
/*270° */ { TopDown_R2L , R2L_TopDown, BottomUp_R2L, R2L_BottomUp, TopDown_L2R, L2R_TopDown, BottomUp_L2R, L2R_BottomUp}
};
bool hwSPI, blState;
_currentFont cfont;
#ifdef ESP32
SPIClass _spi;
#endif
protected:
uint32_t writeFunctionLevel;
void startWrite(void);
void endWrite(void);
GFXfont *gfxFont;
};
#endif

View File

@ -1,124 +0,0 @@
#include "FS.h"
#include <SPIFFS.h>
#include <WiFi.h>
//Write data web
//Fonction
void writedataweb(){
String updateHost = "xavier.debert.free.fr";
String updateDataWeb = "/RS/dataweb/index.html.txt";
String *updateData = &updateDataWeb;
String dispHost = updateHost.substring(0, 14);
//disp.rdis->drawString(2, 0, dispHost.c_str());
Serial.println("Connecting to: " + updateHost);
// Connect to Update host
if (client.connect(updateHost.c_str(), updatePort)) {
// Connection succeeded, fecthing the bin
Serial.println("Fetching index.html: " + String(*updateData));
// Get the contents of the bin file
client.print(String("GET ") + *updateData + " HTTP/1.1\r\n" +
"Host: " + updateHost + "\r\n" +
"Cache-Control: no-cache\r\n" +
"Connection: close\r\n\r\n");
// Check what is being sent
// Serial.print(String("GET ") + bin + " HTTP/1.1\r\n" +
// "Host: " + host + "\r\n" +
// "Cache-Control: no-cache\r\n" +
// "Connection: close\r\n\r\n");
unsigned long timeout = millis();
while (client.available() == 0) {
if (millis() - timeout > 5000) {
Serial.println("Client Timeout !");
client.stop();
return;
}
}
// Once the response is available,
// check stuff
/*
Response Structure
HTTP/1.1 200 OK
x-amz-id-2: NVKxnU1aIQMmpGKhSwpCBh8y2JPbak18QLIfE+OiUDOos+7UftZKjtCFqrwsGOZRN5Zee0jpTd0=
x-amz-request-id: 2D56B47560B764EC
Date: Wed, 14 Jun 2017 03:33:59 GMT
Last-Modified: Fri, 02 Jun 2017 14:50:11 GMT
ETag: "d2afebbaaebc38cd669ce36727152af9"
Accept-Ranges: bytes
Content-Type: application/octet-stream
Content-Length: 357280
Server: AmazonS3
{{BIN FILE CONTENTS}}
*/
while (client.available()) {
// read line till /n
String line = client.readStringUntil('\n');
// remove space, to check if the line is end of headers
line.trim();
// if the the line is empty,
// this is end of headers
// break the while and feed the
// remaining `client` to the
// Update.writeStream();
if (!line.length()) {
//headers ended
break; // and get the OTA started
}
// Check if the HTTP Response is 200
// else break and Exit Update
if (line.startsWith("HTTP/1.1")) {
if (line.indexOf("200") < 0) {
Serial.println("Got a non 200 status code from server. error.");
break;
}
}
// extract headers here
// Start with content length
if (line.startsWith("Content-Length: ")) {
contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str());
Serial.println("Got " + String(contentLength) + " bytes from server");
}
// Next, the content type
if (line.startsWith("Content-Type: ")) {
String contentType = getHeaderValue(line, "Content-Type: ");
Serial.println("Got " + contentType + " payload.");
if (contentType == "application/text") {
isValidContentType = true;
}
}
}
} else {
// Connect to updateHost failed
// May be try?
// Probably a choppy network?
Serial.println("Connection to " + String(updateHost) + " failed. Please check your setup");
// retry??
// execOTA();
}
/*
Serial.printf("\nDataWeb On\n");
//open file for appending new blank line to EOF.
File f = SPIFFS.open("/data.html", "w");
f.println("<html><body>TEST de DATAWeb1</body></html>");
f.close();
*/
}

View File

@ -1,3 +0,0 @@
void geteph();

View File

@ -1,36 +0,0 @@
#include <FS.h>
#include <SPIFFS.h>
#include <SD_MMC.h> // (or SD_MMC.h)
void transfert_sd() {
if (!SD_MMC.begin()) {
Serial.println("Carte SD introuvable");
}
else{
Serial.println("Carte SD détectée");
sonde.clearDisplay();
disp.rdis->drawString(0, 2, "Carte SD On");
disp.rdis->drawString(0, 4, "Transfert Data");
File sourceFile = SPIFFS.open("/data.csv", "r");
File destFile = SD_MMC.open("/data.csv","w");
static uint8_t tanpon[512];
while( sourceFile.read( tanpon, 512) ) {
destFile.write( tanpon, 512 );
}
destFile.close();
sourceFile.close();
delay(1000);
//sonde.updateDisplay();
sonde.clearDisplay();
disp.rdis->drawString(0, 2, "Sortir SD");
disp.rdis->drawString(0, 4, "Reboot 5s");
delay(5000);
ESP.restart();
}
}