Compare commits

...
Author SHA1 Message Date
Ricardo Guzman 9a8bbc2a99 about info update 2026-10-05 14:56:05 -03:00
Ricardo Guzman c0d67acffd save always fixed value 2026-10-05 14:50:19 -03:00
Ricardo Guzman 075ed0e6b4 version update 2026-10-05 14:35:32 -03:00
Ricardo Guzman 8ded0e96df Lora SetPower 2026-10-05 14:32:59 -03:00
Ricardo Guzman d13227c519 libraries update 2026-10-05 14:05:48 -03:00
Ricardo Guzman 9556eabd80 aprspacketlib 1.0.10 test 2026-10-05 01:09:18 -03:00
Ricardo Guzman d75c88fc91 update Status Messages y Acks a APRSPacketLib 109 2026-10-05 00:30:59 -03:00
Ricardo Guzman 1257e5d9e0 date update 2026-10-04 13:31:46 -03:00
Ricardo Guzman bfeb0f13eb APRSPacketLib update again with cleaning updates 2026-10-04 13:30:31 -03:00
Ricardo Guzman 9bddbdf20a variable name change and payload id change for messages 2026-10-04 12:16:41 -03:00
Ricardo Guzman e6f8953da2 better position of rfCommentEveryXBeacons 2026-10-02 17:48:53 -03:00
Ricardo Guzman 1876016509 rfCommentEveryXBeacons 2026-10-02 17:43:04 -03:00
Ricardo Guzman 7717fb5d6a Map and Rx Packets fixes and security updates 2026-10-02 10:40:30 -03:00
Ricardo Guzman 5e133e9f11 Merge pull request #470 from potato-pancake-png/fix/packet-display
Fix stored XSS in received packet rendering
2026-10-02 09:50:23 -03:00
potato-pancake-png 206d831941 Fix stored XSS in received packet rendering 2026-10-02 13:32:57 +09:00
Ricardo Guzman b2ba3d992a commit update too 2026-10-01 14:50:57 -03:00
Ricardo Guzman 683bc7fe1a date update 2026-10-01 14:22:31 -03:00
Ricardo Guzman e692ce2729 Wifi warning with bad password 2026-10-01 14:18:35 -03:00
Ricardo Guzman 90b18ef088 date now on rx packets 2026-09-30 16:08:50 -03:00
Ricardo Guzman 477df390e1 mDNS updated with kiss-tnc for phone apps 2026-09-30 15:43:47 -03:00
Ricardo Guzman fd9df47bd3 update esptool to build 2026-09-30 13:50:33 -03:00
Ricardo Guzman a763200220 build versions update 2026-09-30 12:04:36 -03:00
Ricardo Guzman e47a97491e version update 2026-09-30 11:38:36 -03:00
Ricardo Guzman 5036c20ca9 readme update 2026-09-30 11:36:42 -03:00
Ricardo Guzman aa17b8ded1 date update 2026-09-30 11:27:09 -03:00
Ricardo Guzman 228a3dde25 update to 2026 2026-09-30 00:09:35 -03:00
Ricardo Guzman 7f195e1725 TCXO update 2026-09-29 22:02:59 -03:00
Ricardo Guzman 48538a6162 BAUD RATE GPS AUTO DETECT 2026-09-29 19:50:28 -03:00
Ricardo Guzman a472c307d3 cad timeout y oled update 2026-09-29 18:46:41 -03:00
Ricardo Guzman fc256de09c fix to crossfreq 2026-09-28 15:24:17 -03:00
Ricardo Guzman 9b5fa49e31 mejora digi moricef 2026-09-28 15:03:59 -03:00
Ricardo Guzman 102169c0aa gitignore claude update 2026-09-27 12:10:31 -03:00
Ricardo Guzman 295d1fa2ef picture update 2026-09-23 22:45:48 -03:00
Ricardo Guzman 033657836f QRP Labs Gateway update 2026-09-23 19:14:24 -03:00
Ricardo Guzman f739b90adc update Screen Display Eco fix 2026-09-23 15:29:51 -03:00
Ricardo Guzman 5e2341299a ESP32C3_SuperMini_DIY_LoRa_SX1278 added 2026-09-23 14:03:30 -03:00
Ricardo Guzman 8ba8981a32 NTP updated 2026-09-23 13:50:05 -03:00
Ricardo Guzman e52d4566ba WiFi loop protection 2026-09-23 12:39:07 -03:00
Ricardo Guzman c80eb9bd84 update of names 2026-09-23 12:08:57 -03:00
Ricardo Guzman 6b187da9f4 E32 400M30S 2026-09-23 11:28:17 -03:00
Ricardo Guzman ab27c11532 updated build again 2026-09-23 11:06:47 -03:00
Ricardo Guzman 43bfff5033 sin update build 2026-09-23 11:04:06 -03:00
Ricardo Guzman 8fa10a7813 RAK3312 and 3112 added 2026-09-23 11:02:34 -03:00
Ricardo Guzman 316c746936 WebUI fixes 2026-09-23 05:12:51 -03:00
Ricardo Guzman 3618558b21 update and clean code 2026-09-22 21:17:33 -03:00
Ricardo Guzman e1eda2e64a updated aprspacket lib y otros refactoring 2026-09-22 19:59:24 -03:00
Ricardo Guzman 759cddd0e5 date update 2026-09-22 14:28:29 -03:00
Ricardo Guzman 8186ff4fbd updated digi cleanPath and process 2026-09-22 14:27:48 -03:00
Ricardo Guzman 3720fe9881 added fix for mic encoded syslog 2026-09-16 17:12:27 -03:00
Ricardo Guzman 5d04c56fd2 tnc2 added 2026-09-15 11:52:09 -03:00
Ricardo Guzman ee65f953d6 date update 2026-09-07 10:54:24 -03:00
Ricardo Guzman c952ea0159 Merge pull request #456 from cwb/fix-oled-screen-height
display: fix SCREEN_HEIGHT definition and maxLines for SH1106 OLED
2026-09-07 10:52:20 -03:00
Ricardo Guzman 21a42ef157 update de iconos 2026-09-07 10:46:39 -03:00
Ricardo Guzman 53ee07a7c1 heard direct and repeated 2026-08-27 22:45:08 -04:00
Ricardo Guzman 12a0f5bed1 update a 24px symbol 2026-08-25 20:20:09 -04:00
Ricardo Guzman 27f34c5f67 48px 2026-08-25 20:04:12 -04:00
Craig 08bfb013ce display: fix SCREEN_HEIGHT definition and maxLines for SH1106 OLED 2026-08-25 17:03:01 -07:00
Ricardo Guzman d573c66fd7 casi 2026-08-25 19:50:37 -04:00
Ricardo Guzman e0dc2f81ee ready for testing 2026-08-24 23:18:25 -04:00
Ricardo Guzman 6e4dc3c301 update needed for t-decks 2026-08-16 12:28:40 -04:00
Ricardo Guzman d2e42c8571 readme update2 2026-08-16 12:01:31 -04:00
Ricardo Guzman 6b321bc1e0 images update 2026-08-16 11:51:27 -04:00
Ricardo Guzman f7c9201830 now LEAFET is local flash 2026-07-14 00:33:54 -04:00
Ricardo Guzman 1a042b7d0e blockeando leafet 1 2026-07-14 00:17:56 -04:00
Ricardo Guzman aaa44200b5 test query callsign fix 2026-07-06 13:09:19 -04:00
Ricardo Guzman c17ad32650 2026 update in copywrite info 2026-06-29 13:06:58 -04:00
Ricardo Guzman (Richonguzman) 26b5df1bd7 update delete of star in digipeated packet 2026-06-22 22:16:14 -04:00
Ricardo Guzman (Richonguzman) 0c3555bdbe new digimode added 2026-06-18 13:58:10 -04:00
Ricardo Guzman (Richonguzman) cf858ffad2 digi patch 2 2026-06-16 11:29:32 -04:00
Ricardo Guzman (Richonguzman) cbac8b0365 digi patch 1 2026-06-16 10:41:44 -04:00
Ricardo Guzman (Richonguzman) b76aed4d57 cambio digi modes de 2 y 3 a 1 y 2 para agregar patch 2026-06-16 10:25:22 -04:00
Ricardo Guzman (Richonguzman) 96d7be6ffc Map cargado y funcionando 2026-06-12 00:09:02 -04:00
Ricardo Guzman (Richonguzman) 94df6d2e8f map con estaciones 2026-06-11 23:59:24 -04:00
Ricardo Guzman (Richonguzman) 0a222edeb8 primer test solo igate 2026-06-11 22:15:37 -04:00
Ricardo Guzman (Richonguzman) e35d5f6a9e about update 2026-06-11 01:55:45 -04:00
Ricardo Guzman (Richonguzman) 96432b8624 NEW WEB 2026-06-11 01:39:34 -04:00
Ricardo Guzman (Richonguzman) 25ea82737a cad test 3 2026-06-10 10:46:07 -04:00
Ricardo Guzman (Richonguzman) df22b9dbc0 readme and date update 2026-06-10 00:27:15 -04:00
Ricardo Guzman (Richonguzman) 9898e123d2 CAD with backoff process 2026-06-10 00:25:27 -04:00
Ricardo Guzman (Richonguzman) a97757c0b2 test CAD 2026-06-09 22:46:41 -04:00
Ricardo Guzman (Richonguzman) 305c5b73f7 mayusculas a RPC 2026-04-21 11:16:02 -04:00
Ricardo Guzman (Richonguzman) dd3688f23b fake name 2026-04-21 11:12:00 -04:00
Ricardo Guzman (Richonguzman) 6f5846c53d test algo dos 2026-04-21 11:05:52 -04:00
Ricardo Guzman (Richonguzman) 5c30e72a88 test algo 2026-04-21 11:05:27 -04:00
Ricardo Guzman (Richonguzman) a024b939f5 Merge pull request #423 from richonguzman/richonguzman-patch-19
Add RPC_LORA_DIGIGATE_1W to build workflow
2026-04-21 10:42:19 -04:00
Ricardo Guzman (Richonguzman) 96c52c042e Add RPC_LORA_DIGIGATE_1W to build workflow 2026-04-21 10:40:33 -04:00
Ricardo Guzman (Richonguzman) 236ae65485 ElegantOta fix 2026-04-21 10:34:20 -04:00
Ricardo Guzman (Richonguzman) 4263d55839 screen height fix 2026-04-20 12:56:03 -04:00
Ricardo Guzman (Richonguzman) ba96bb135e test new screen 2026-04-20 12:37:52 -04:00
Ricardo Guzman (Richonguzman) 944d1fceaf cleaning code again 2026-04-13 15:24:55 -04:00
Ricardo Guzman (Richonguzman) 005abb3593 Merge pull request #412 from richonguzman/richonguzman-patch-18
Add 2 new target configurations for GPS variants
2026-03-25 12:22:03 -03:00
Ricardo Guzman (Richonguzman) fcf77f6d4b Add 2 new target configurations for GPS variants 2026-03-25 12:20:40 -03:00
Ricardo Guzman (Richonguzman) f576ac6c04 readme update 2026-03-25 12:09:43 -03:00
Ricardo Guzman (Richonguzman) f9f8a9b957 test new boards and gps read update 2026-03-25 12:03:00 -03:00
Ricardo Guzman (Richonguzman) 7f8c2af86b killing blank spaces 2026-03-20 16:47:05 -03:00
Ricardo Guzman (Richonguzman) 198245f600 minor blackspace fix 2026-03-10 23:43:50 -03:00
Ricardo Guzman (Richonguzman) 372bb532b8 Merge pull request #410 from richonguzman/richonguzman-patch-17
Add ttgo-t-beam-v1_SX1262 configuration
2026-03-10 23:38:02 -03:00
Ricardo Guzman (Richonguzman) 05f69fe450 Add ttgo-t-beam-v1_SX1262 configuration 2026-03-10 23:34:22 -03:00
Ricardo Guzman (Richonguzman) 0e8337292f added Tbeam AXP192 and SX1262 2026-03-10 23:25:04 -03:00
Ricardo Guzman (Richonguzman) f87a89213f ArduinoJson 7 update 2026-03-10 23:10:04 -03:00
Ricardo Guzman (Richonguzman) 3810821f45 Minor improvements for OTA and others 2026-03-10 22:44:02 -03:00
Ricardo Guzman (Richonguzman) 6a3b1c903d Merge pull request #382 from petrkr/networkmanager
Network manager
2026-03-10 19:40:14 -03:00
Ricardo Guzman (Richonguzman) 11c36a91fb Safe Poing V3.2.2 2026-03-08 10:32:18 -03:00
Ricardo Guzman (Richonguzman) a97ffe709f Merge pull request #407 from richonguzman/backBefore-FIX-OTA
Back before fix ota
2026-03-08 10:21:40 -03:00
Ricardo Guzman (Richonguzman) c80b565730 Merge branch 'main' into backBefore-FIX-OTA 2026-03-08 10:18:44 -03:00
Ricardo Guzman (Richonguzman) 84cbcc30e8 intentando arreglar 2026-03-08 10:12:57 -03:00
Ricardo Guzman (Richonguzman) 11d413cb17 corrigiendo desorden 2026-03-08 10:11:43 -03:00
Ricardo Guzman (Richonguzman) 19d767e3cb corrigiendo desorden 2026-03-08 10:11:10 -03:00
Ricardo Guzman (Richonguzman) 803bde1008 Merge pull request #405 from petrkr/fixconfig
Fix OTA update due bad configuration behaviour
2026-03-08 09:51:40 -03:00
Ricardo Guzman (Richonguzman) 8ab1f5ec24 many more libraries update 2026-03-08 09:21:34 -03:00
Ricardo Guzman (Richonguzman) cfc9474469 RadioLib 7.6.0 update 2026-03-08 09:12:56 -03:00
Ricardo Guzman (Richonguzman) 949807bc14 Merge pull request #249 from petrkr/updates
Updates
2026-03-08 09:00:06 -03:00
Petr Kracik 37e3a2f831 Wifi: use hasWifiNetworks 2026-03-06 04:08:17 +01:00
Petr Kracik 8bca896e31 Utils: Added Ethernet IP 2026-03-06 04:08:17 +01:00
Petr Kracik e241321abc Use AutoAP 2026-03-06 04:08:17 +01:00
Petr Kracik d8b658df30 Prepare AutoAP enable/disable 2026-03-06 04:08:17 +01:00
Petr Kracik 9775c601bb Wifi utils add multiple SSID 2026-03-06 04:08:17 +01:00
Petr Kracik b0d441a519 Use setHostname from network manager 2026-03-06 04:08:16 +01:00
Petr Kracik 413318e94b Use SoftAP from network manager 2026-03-06 04:08:16 +01:00
Petr Kracik ec28dc0978 Removed deprecated WiFiConnected extern 2026-03-06 04:08:16 +01:00
Petr Kracik e5fa6574f0 Syslog uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik 8a16be1bea NTP: Fix nullptr if network is connected after setup() 2026-03-06 04:08:16 +01:00
Petr Kracik 891fc906bb NTP uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik eb4087073b Lora uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik 10d1fb4ede Utils uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik f18d16a6fb ARPS Utils uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik 1edb333b1d WiFi utils uses networkManager 2026-03-06 04:08:16 +01:00
Petr Kracik ca567e720d NetworkManager: NM log prefix 2026-03-06 04:08:09 +01:00
Petr Kracík 35760b74f7 Fix OTA update due bad configuration behaviour 2026-03-06 03:49:11 +01:00
Petr Kracik 801641f781 Network manager supports basic Ethernet 2026-03-06 00:09:51 +01:00
Petr Kracik 447c2d4937 Network manager support more Wifi networks 2026-03-06 00:08:44 +01:00
Petr Kracik 1668fc1412 Network manager: AutoAP Disable 2026-03-06 00:08:44 +01:00
Petr Kracik 1ff5504956 Network manager: wifi connect use LED 2026-03-05 23:04:14 +01:00
Petr Kracik e0c6608055 Network manager supports setHostname 2026-03-05 23:04:14 +01:00
Petr Kracík d2c9bcb71d NetworkManager: Initial commit 2026-03-05 23:04:14 +01:00
Petr Kracik 630de55feb Main include WiFiClient instead generic WiFi 2026-03-05 23:04:14 +01:00
Petr Kracík b5690a2f37 Updated espressif SDK 2026-03-05 22:06:00 +01:00
Petr Kracík a7ae6c9fd4 Fix include WiFiClient in MQTT client 2026-03-05 22:01:35 +01:00
Ricardo Guzman (Richonguzman) b448e2fc6b ADC_CTRL_PIN fix 2026-03-05 10:06:22 -03:00
Ricardo Guzman (Richonguzman) c33720a5fb VEXT_CTRL_PIN definition missing fix 2026-03-04 12:24:16 -03:00
Ricardo Guzman (Richonguzman) e57fad6666 aprsis.active update in digi.ecoMode == 1 ? 2026-03-04 11:21:26 -03:00
Ricardo Guzman (Richonguzman) 2418291ac9 digiEcoMode forces APRS.active = false 2026-03-03 11:22:41 -03:00
Ricardo Guzman (Richonguzman) 796ba35357 add new ESP32 device 9M2IBR 2026-02-26 12:28:52 -03:00
Ricardo Guzman (Richonguzman) 57873e4181 readme and date update 2026-02-26 11:51:59 -03:00
Ricardo Guzman (Richonguzman) 453222d69f new board added 9M2IBR 2026-02-26 11:08:49 -03:00
Ricardo Guzman (Richonguzman) fbb347fb7f new update to kiss utils 2026-02-26 10:31:45 -03:00
Ricardo Guzman (Richonguzman) 50fd831fdb kiss correction of missing new indexOf 2026-02-26 10:23:40 -03:00
Ricardo Guzman (Richonguzman) 3a2c0304d0 status logic improved 2026-02-25 12:25:40 -03:00
Ricardo Guzman (Richonguzman) b24c2b2527 update build for heltec v2 915 2026-02-25 10:57:07 -03:00
Ricardo Guzman (Richonguzman) db0da96d7f ReadMe update 2026-02-25 10:45:49 -03:00
Ricardo Guzman (Richonguzman) bf6a0faa90 killing blank spaces 2026-02-24 23:33:58 -03:00
Ricardo Guzman (Richonguzman) 61cf118a3b cleaning Headers 2026-02-24 23:27:36 -03:00
Ricardo Guzman (Richonguzman) 44719083a6 new callsignIsValid update 2026-02-24 21:26:20 -03:00
Ricardo Guzman (Richonguzman) 808d740477 update indexOf kill 2026-02-24 19:29:20 -03:00
Ricardo Guzman (Richonguzman) 23c8257c80 date update 2026-02-24 07:55:53 -03:00
Ricardo Guzman (Richonguzman) db4582db13 telemetry EUP beacon update 2026-02-24 07:55:32 -03:00
Ricardo Guzman (Richonguzman) 09e06d36e8 checkCallsignList update 2026-02-24 06:50:27 -03:00
Ricardo Guzman (Richonguzman) e6d44c1b7f replaced string for hash in 25segBuffer 2026-02-23 18:59:27 -03:00
Ricardo Guzman (Richonguzman) d4fc99466f update digi2000 2026-02-23 18:07:26 -03:00
Ricardo Guzman (Richonguzman) 41f09af7b5 digipeater logic improved 2026-02-23 17:32:52 -03:00
Ricardo Guzman (Richonguzman) 0eec028c5d update SENSOR BUS 2026-02-23 12:00:55 -03:00
Ricardo Guzman (Richonguzman) c48dd15bd6 better ADC and VEXT control 2026-02-23 11:14:42 -03:00
Ricardo Guzman (Richonguzman) f26ded5b5c less memory fixes too 2026-02-20 17:36:26 -03:00
Ricardo Guzman (Richonguzman) c887689406 indexOf transformation 2026-02-20 17:03:39 -03:00
Ricardo Guzman (Richonguzman) 775e08a10a stationCallsignIsValid 2026-02-20 10:03:01 -03:00
Ricardo Guzman (Richonguzman) f9291821d2 2 decimales for QTH query 2026-02-18 11:03:06 -03:00
Ricardo Guzman (Richonguzman) 12d0bb760e version update 2026-02-16 23:46:03 -03:00
Ricardo Guzman (Richonguzman) f020eb7491 update a nombre CPU 2026-02-16 23:01:05 -03:00
Ricardo Guzman (Richonguzman) 63f4660ef6 power utils update con cpu 2026-02-16 22:48:28 -03:00
Ricardo Guzman (Richonguzman) efabe9b1f7 start Heltec V2 915 2026-02-16 21:37:54 -03:00
Ricardo Guzman (Richonguzman) beae88d557 less time 2026-02-11 01:03:54 -03:00
Ricardo Guzman (Richonguzman) 8b4d8c2d1d day reset of status tx 2026-02-10 23:01:49 -03:00
Ricardo Guzman (Richonguzman) 08ae1e322b Symbol Explanation added to index.html 2026-02-03 17:09:27 -03:00
Ricardo Guzman (Richonguzman) d576be0f2b fix telemetry tactical callsign 2026-01-31 11:47:27 -03:00
Ricardo Guzman (Richonguzman) 7c9903ac12 WEBUI update 2026-01-20 22:44:03 -03:00
Ricardo Guzman (Richonguzman) 0d7cff14f5 lastServerCheck update 2026-01-20 21:19:53 -03:00
Ricardo Guzman (Richonguzman) 5c89635a23 lastServerCheck 2026-01-20 21:08:28 -03:00
Ricardo Guzman (Richonguzman) 7a35f9e32a Server Alive mod 2026-01-20 20:56:20 -03:00
Ricardo Guzman (Richonguzman) 7695675a7c check server 2026-01-20 09:50:09 -03:00
Ricardo Guzman (Richonguzman) c8c0be636d check internet connection 2026-01-20 00:16:24 -03:00
Ricardo Guzman (Richonguzman) 76fe27a0ab APRSSSR into APRSSR fix 2026-01-16 09:41:20 -03:00
Ricardo Guzman (Richonguzman) da80391921 change Freq with bandWidth fix 2026-01-13 01:50:12 -03:00
Ricardo Guzman (Richonguzman) d638093dbf Tactical 6 update 2026-01-11 11:46:23 -03:00
Ricardo Guzman (Richonguzman) 66a5f03c12 de update for tactical 2026-01-09 00:02:29 -03:00
Ricardo Guzman (Richonguzman) ce8cf3a2fe minimal interval updated 2026-01-08 14:08:07 -03:00
Ricardo Guzman (Richonguzman) 4b45b90c88 minor path updates 2026-01-08 12:21:19 -03:00
Ricardo Guzman (Richonguzman) dbe980a081 tactical callsign ready for testing 2026-01-07 23:18:14 -03:00
Ricardo Guzman (Richonguzman) bb3d59a20d digi tactical changes 2026-01-07 17:25:45 -03:00
Ricardo Guzman (Richonguzman) fe590b41b0 tactical callsign displayShow 2026-01-07 14:30:32 -03:00
Ricardo Guzman (Richonguzman) 3f76005949 few delays killed 2026-01-07 12:19:51 -03:00
Ricardo Guzman (Richonguzman) 6d95231b9c web mods for tactical 2026-01-07 11:53:10 -03:00
Ricardo Guzman (Richonguzman) 81692010cf tactical validation 2026-01-07 11:44:00 -03:00
Ricardo Guzman (Richonguzman) 50b738d04b Spreading Factor update 2026-01-07 10:37:11 -03:00
Ricardo Guzman (Richonguzman) 026d6b2eeb starting Heltec V4 2026-01-07 10:14:42 -03:00
Ricardo Guzman (Richonguzman) fe705519cb V3.1.7 OTA and mode 2025-12-29 09:10:17 -03:00
Ricardo Guzman (Richonguzman) 1fa74b8697 GPS encoded payload comment fix 2025-12-28 13:45:29 -03:00
Ricardo Guzman (Richonguzman) a3794085b4 OTA update fixed 2025-12-28 11:59:05 -03:00
Ricardo Guzman (Richonguzman) 0a898a40e6 gps payload decoding fix2 2025-12-28 11:46:43 -03:00
Ricardo Guzman (Richonguzman) ff8c7581fa gps payload decoding fix 2025-12-28 10:06:34 -03:00
Ricardo Guzman (Richonguzman) 449a8557d2 definition fix for VisionMaster 2025-12-23 08:32:06 -03:00
Ricardo Guzman (Richonguzman) 4419c98920 Merge pull request #375 from richonguzman/richonguzman-patch-16
added Heltec VisionMaster E290 and WirelessPaper V1.2
2025-12-22 23:02:46 -03:00
Ricardo Guzman (Richonguzman) 45bf90817b added Heltec VisionMaster E290 and WirelessPaper V1.2 2025-12-22 22:59:21 -03:00
Ricardo Guzman (Richonguzman) ef30a1bf58 readme update 2025-12-22 21:34:58 -03:00
Ricardo Guzman (Richonguzman) 9a705d3dfa Heltec Wireless Paper V1.2 added 2025-12-22 21:29:50 -03:00
Ricardo Guzman (Richonguzman) 63f74396d2 Heltec Vision Master update 2025-12-22 21:15:03 -03:00
Ricardo Guzman (Richonguzman) 400b77c2d3 date update 2025-12-22 19:57:18 -03:00
Ricardo Guzman (Richonguzman) 31daddf917 invalid Passcode non blocking fix 2025-12-22 19:56:15 -03:00
Ricardo Guzman (Richonguzman) 529a44018f readme and version update 2025-12-18 09:47:56 -03:00
Ricardo Guzman (Richonguzman) 5ba9c5b382 test 2025-12-18 09:28:56 -03:00
Ricardo Guzman (Richonguzman) c7a0e3773b ina bme update 2025-12-14 05:53:43 -03:00
Ricardo Guzman (Richonguzman) a5f9e5b844 typo TCXO fix 2025-12-12 12:25:41 -03:00
Ricardo Guzman (Richonguzman) 24f407d51c TCXO add 2025-12-12 12:24:53 -03:00
Ricardo Guzman (Richonguzman) 2c6665b557 FIX for boards with GPS onboard 2025-12-11 13:44:06 -03:00
Ricardo Guzman (Richonguzman) 38f52564f1 lib def fix 2025-12-11 10:27:29 -03:00
Ricardo Guzman (Richonguzman) 49e92c622f README UPDATE for v3.1.5 manual 2025-12-07 08:46:45 -03:00
Ricardo Guzman (Richonguzman) 5370850ae1 update to fix ntp without wifi 2025-12-03 12:31:36 -03:00
Ricardo Guzman (Richonguzman) 984e9f9561 encoded telemetry fix for INA219 2025-12-01 19:53:14 -03:00
Ricardo Guzman (Richonguzman) f60252ef94 fix for same declaration of variables 2025-12-01 16:02:26 -03:00
Ricardo Guzman (Richonguzman) 63257d6329 readme update 2025-12-01 13:30:37 -03:00
Ricardo Guzman (Richonguzman) 18929ad379 INA219 final test 2025-12-01 13:29:54 -03:00
Ricardo Guzman (Richonguzman) cda901142d INA219 3 2025-12-01 12:45:14 -03:00
Ricardo Guzman (Richonguzman) 8008143267 configuration INA219 add 2025-12-01 10:55:31 -03:00
Ricardo Guzman (Richonguzman) 3416e21a8c ready radiolib excludes 2025-12-01 10:45:14 -03:00
Ricardo Guzman (Richonguzman) 5882b54c62 radiolib exclude start 2025-12-01 10:11:15 -03:00
Ricardo Guzman (Richonguzman) 6a4c0ef01a readme update 2025-12-01 09:38:37 -03:00
Ricardo Guzman (Richonguzman) 0bd3201ef7 AHT20 add 2025-12-01 09:37:02 -03:00
Ricardo Guzman (Richonguzman) b5d9103ea5 gps beacon fix 2025-11-30 10:47:12 -03:00
Ricardo Guzman (Richonguzman) 20a5029da8 index fix 2025-11-30 10:37:23 -03:00
Ricardo Guzman (Richonguzman) b3cc8af180 test update aprspacketlib 2025-11-30 10:15:01 -03:00
Ricardo Guzman (Richonguzman) ca16761f68 loadCallsignList update 2025-11-29 14:11:43 -03:00
Ricardo Guzman (Richonguzman) e593b2c670 version number update 2025-11-08 21:16:03 -03:00
Ricardo Guzman (Richonguzman) 08a7e0aac1 T-Beam Supreme Screen Fix 2025-11-08 21:13:57 -03:00
Ricardo Guzman (Richonguzman) 9b258c42ab checkValidCallsign update 2025-11-06 10:11:55 -03:00
richonguzman 8712122d33 raw images 2025-10-29 10:34:43 -03:00
richonguzman 9e34684627 update pdf y blob 2025-10-29 10:28:25 -03:00
richonguzman e4aa52241d sin gitattributes ahora 2025-10-29 10:11:18 -03:00
richonguzman c840ef01fa sin manual en github 2025-10-29 10:10:58 -03:00
richonguzman 98f85725f8 test con Vista Previa 2025-10-29 09:13:48 -03:00
richonguzman 10bde884b1 new gitattributes 2025-10-28 22:54:22 -03:00
richonguzman 546e4f63a4 update pdf to v1.4 2025-10-28 22:46:44 -03:00
Ricardo Guzman (Richonguzman) 87f2ec2b7b test new version pdf 20250128 2025-10-28 22:40:15 -03:00
Ricardo Guzman (Richonguzman) c9c7e24aae Merge pull request #356 from richonguzman/richonguzman-patch-15
Delete manual/LoRa_APRS_iGate_CA2RXU_Firmware_Manual.pdf
2025-10-28 22:38:23 -03:00
Ricardo Guzman (Richonguzman) b86133223d Delete manual/LoRa_APRS_iGate_CA2RXU_Firmware_Manual.pdf 2025-10-28 22:38:02 -03:00
richonguzman 880a41bfd8 update manual 20251028 2025-10-28 22:34:06 -03:00
richonguzman d610b3a90f mqtt beacon fix 2025-10-21 14:33:58 -03:00
richonguzman 23a33ed27c new message warning 2025-10-19 14:20:11 -03:00
richonguzman 72ef827900 new update pdf manual v314 2025-10-18 11:42:18 -03:00
richonguzman b756f97f55 v314 manual update 2025-10-18 10:50:54 -03:00
richonguzman 2625e7b91d update readme v314 2025-10-15 17:29:26 -03:00
richonguzman 75337d6c29 update to Tnc strip bytes 2025-10-15 17:12:38 -03:00
richonguzman a618383617 APRS Bridge for TNC 2025-10-15 15:11:07 -03:00
richonguzman 8add599838 NTP server change fix 2025-10-15 14:31:02 -03:00
richonguzman 83ec2265c6 Rx and Tx Freq selection fix 2025-10-15 14:18:43 -03:00
richonguzman bec4f4f473 beaconOnRxFreq into beacon config 2025-10-15 10:58:30 -03:00
richonguzman 87a67cd2c9 readmeupdate otro mas 2025-10-13 13:29:56 -03:00
richonguzman 4e48b2d02d manual update 2025-10-13 13:11:51 -03:00
richonguzman 607277bfe9 readme update2 2025-10-13 12:44:47 -03:00
richonguzman c2f8596667 readme update 2025-10-13 12:44:15 -03:00
richonguzman ba7ff2a2d9 WebUI change 2025-10-13 12:38:52 -03:00
richonguzman b44eb1028d no more speed standards on index 2025-10-13 02:03:18 -03:00
richonguzman d7602268f2 changes for Rx and Tx Lora Freq 2025-10-13 02:00:05 -03:00
richonguzman 2f6f9be28e Lora Config separated for Rx and Tx 2025-10-13 01:33:41 -03:00
richonguzman 92572245cd beacon Freq on digi mod 2025-10-12 21:45:23 -03:00
richonguzman 152217e71c startup delay added 2025-10-12 14:21:51 -03:00
richonguzman d334164b6f Beacon Freq on Digi Mode 2025-10-11 18:27:06 -03:00
richonguzman 61409ce683 index correction 2025-10-11 15:57:29 -03:00
richonguzman 742a6b6477 readme update 2025-10-11 13:23:39 -03:00
richonguzman dd2fce3908 igate beacon over mqtt 2025-10-11 13:22:08 -03:00
263 changed files with 7375 additions and 11326 deletions

No files matched your search

+69 -42
View File
@@ -13,12 +13,18 @@ jobs:
target:
- name: ttgo-lora32-v21
chip: esp32
- name: ttgo-lora32-v21_GPS
chip: esp32
- name: ttgo-lora32-v21_915
chip: esp32
- name: ttgo-lora32-v21_915_GPS
chip: esp32
- name: ttgo_lora32_t3s3_v1_2
chip: esp32s3
- name: heltec-lora32-v2
chip: esp32
- name: heltec-lora32-v2_915
chip: esp32
- name: heltec_wifi_lora_32_V3
chip: esp32s3
- name: heltec_wifi_lora_32_V3_2
@@ -31,17 +37,19 @@ jobs:
chip: esp32s3
- name: heltec_wireless_bridge
chip: esp32
- name: ESP32_DIY_LoRa
- name: ESP32_DIY_LoRa_SX1278
chip: esp32
- name: ESP32_DIY_LoRa_915
- name: ESP32_DIY_LoRa_915_SX1276
chip: esp32
- name: ESP32_DIY_1W_LoRa
- name: ESP32_DIY_1W_LoRa_E22_400M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_915
- name: ESP32_DIY_1W_LoRa_915_E22_900M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_LLCC68
- name: ESP32_DIY_1W_LoRa_E220_400M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_Mesh_V1_2
- name: ESP32_DIY_1W_LoRa_E32_400M30S
chip: esp32
- name: ESP32_DIY_1W_LoRa_Mesh_V1_2_E22_400M30S
chip: esp32
- name: ttgo-t-beam-v1_2
chip: esp32
@@ -53,6 +61,8 @@ jobs:
chip: esp32
- name: ttgo-t-beam-v1_SX1268
chip: esp32
- name: ttgo-t-beam-v1_SX1262
chip: esp32
- name: ttgo-t-beam-v1_2_SX1262
chip: esp32
- name: ttgo_t_deck_plus
@@ -61,9 +71,9 @@ jobs:
chip: esp32s3
- name: ttgo_t_beam_s3_SUPREME_v3
chip: esp32s3
- name: ESP32_DIY_LoRa_A7670
- name: ESP32_DIY_LoRa_A7670_SX1278
chip: esp32
- name: ESP32_DIY_LoRa_A7670_915
- name: ESP32_DIY_LoRa_A7670_915_SX1276
chip: esp32
- name: heltec_wireless_tracker
chip: esp32s3
@@ -71,41 +81,58 @@ jobs:
chip: esp32c3
- name: heltec_wireless_paper_v1
chip: esp32s3
- name: heltec_wireless_paper_v1_2
chip: esp32s3
- name: heltec_vision_master_e290
chip: esp32s3
- name: OE5HWN_MeshCom
chip: esp32
- name: WEMOS-LOLIN32-OLED-DIY
- name: WEMOS_LOLIN32_OLED_DIY_LoRa_SX1278
chip: esp32
- name: WEMOS-D1-R32-RA02
- name: WEMOS_D1_R32_DIY_LoRa_SX1278
chip: esp32
- name: WEMOS_S2_MINI_DIY_LoRa
- name: WEMOS_S2_MINI_DIY_LoRa_SX1278
chip: esp32s2
- name: esp32c3_DIY_1W_LoRa
- name: ESP32C3_DIY_1W_LoRa_E22_400M30S
chip: esp32c3
- name: esp32c3_DIY_1W_LoRa_915
- name: ESP32C3_DIY_1W_LoRa_915_E22_900M30S
chip: esp32c3
- name: ESP32_C3_OctopusLab_LoRa
- name: ESP32C3_OctopusLab_LoRa
chip: esp32c3
- name: ESP32C3_SuperMini_DIY_LoRa_SX1278
chip: esp32c3
- name: QRPLabs_LightGateway_1_0
chip: esp32s3
- name: QRPLabs_LightGateway_Plus_1_0
chip: esp32s3
- name: RPC_LORA_DIGIGATE_1W
chip: esp32
- name: XIAO_ESP32S3_WIO_SX1262
chip: esp32s3
- name: RAK3312
chip: esp32s3
- name: RAK3112
chip: esp32s3
- name: TROY_LoRa_APRS
chip: esp32
chip: esp32
- name: ESP32_9M2IBR_1W_LoRa_GPS_E22_400M30S
chip: esp32
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7
- uses: actions/setup-python@v4
- uses: actions/setup-python@v7
with:
python-version: "3.9"
python-version: "3.13"
- name: Install esptool
run: pip install "esptool>=5,<6"
- name: Install PlatformIO Core
run: pip install --upgrade platformio
- name: Build target
run: |
pio run -e ${{ matrix.target.name }}
pio run -e ${{ matrix.target.name }}
- name: Build FS
run: |
@@ -120,48 +147,48 @@ jobs:
cp .pio/build/${{ matrix.target.name }}/partitions.bin installer/firmware/
cp .pio/build/${{ matrix.target.name }}/spiffs.bin installer/firmware/
cp ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin installer/firmware/
- name: Merge for web flashing
run: |
if [ "${{ matrix.target.chip }}" == "esp32" ]; then
python installer/bin/esptool/esptool.py --chip esp32 merge_bin \
esptool --chip esp32 merge-bin \
-o installer/web_factory.bin \
--flash_mode dio \
--flash_freq 40m \
--flash_size 4MB \
--flash-mode dio \
--flash-freq 40m \
--flash-size 4MB \
0x1000 installer/firmware/bootloader.bin \
0x8000 installer/firmware/partitions.bin \
0xe000 installer/firmware/boot_app0.bin \
0x10000 installer/firmware/firmware.bin \
0x3D0000 installer/firmware/spiffs.bin
elif [ "${{ matrix.target.chip }}" == "esp32s2" ]; then
python installer/bin/esptool/esptool.py --chip esp32s2 merge_bin \
esptool --chip esp32s2 merge-bin \
-o installer/web_factory.bin \
--flash_mode dio \
--flash_freq 40m \
--flash_size 4MB \
--flash-mode dio \
--flash-freq 40m \
--flash-size 4MB \
0x1000 installer/firmware/bootloader.bin \
0x8000 installer/firmware/partitions.bin \
0xe000 installer/firmware/boot_app0.bin \
0x10000 installer/firmware/firmware.bin \
0x3D0000 installer/firmware/spiffs.bin
elif [ "${{ matrix.target.chip }}" == "esp32s3" ]; then
python installer/bin/esptool/esptool.py --chip esp32s3 merge_bin \
esptool --chip esp32s3 merge-bin \
-o installer/web_factory.bin \
--flash_mode dio \
--flash_freq 40m \
--flash_size 8MB \
--flash-mode dio \
--flash-freq 40m \
--flash-size 8MB \
0x0000 installer/firmware/bootloader.bin \
0x8000 installer/firmware/partitions.bin \
0xe000 installer/firmware/boot_app0.bin \
0x10000 installer/firmware/firmware.bin \
0x3D0000 installer/firmware/spiffs.bin
elif [ "${{ matrix.target.chip }}" == "esp32c3" ]; then
python installer/bin/esptool/esptool.py --chip esp32c3 merge_bin \
esptool --chip esp32c3 merge-bin \
-o installer/web_factory.bin \
--flash_mode dio \
--flash_freq 40m \
--flash_size 4MB \
--flash-mode dio \
--flash-freq 40m \
--flash-size 4MB \
0x1000 installer/firmware/bootloader.bin \
0x8000 installer/firmware/partitions.bin \
0xe000 installer/firmware/boot_app0.bin \
@@ -175,12 +202,12 @@ jobs:
- name: Archive Files
run: zip -r installer.zip installer/
- name: Rename zip for upload
run: mv installer.zip ${{ matrix.target.name }}.zip
- name: Upload Release Asset
uses: actions/upload-release-asset@v1
uses: softprops/action-gh-release@v3
with:
files: ${{ matrix.target.name }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ github.event.release.upload_url }}
asset_path: ./installer.zip
asset_name: ${{ matrix.target.name }}.zip
asset_content_type: application/zip
+3 -3
View File
@@ -15,11 +15,11 @@ jobs:
- name: ttgo-lora32-v21
chip: esp32
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7
- uses: actions/setup-python@v4
- uses: actions/setup-python@v7
with:
python-version: "3.9"
python-version: "3.13"
- name: Install PlatformIO Core
run: pip install --upgrade platformio
+2 -1
View File
@@ -7,4 +7,5 @@
/data_embed/*.gz
installer/firmware
installer/*.bin
**/__pycache__/
**/__pycache__/
.claude/
+2 -1
View File
@@ -5,6 +5,7 @@
"platformio.platformio-ide"
],
"unwantedRecommendations": [
"ms-vscode.cpptools-extension-pack"
"ms-vscode.cpptools-extension-pack",
"pioarduino.pioarduino-ide"
]
}
+43 -11
View File
@@ -2,7 +2,7 @@
This firmware is for using ESP32 based boards with LoRa Modules and GPS to live in the APRS world.
![Screenshot](https://github.com/richonguzman/LoRa_APRS_iGate/blob/main/images/iGateOledScreen.jpeg)
![Screenshot](https://github.com/richonguzman/LoRa_APRS_iGate/raw/main/images/iGateOledScreen.jpeg)
__(This iGate Firmware works with all LoRa Tracker Firmwares (specially this <a href="https://github.com/richonguzman/LoRa_APRS_Tracker" target="_blank">LoRa APRS Tracker Firmware</a>))__
<br />
@@ -12,48 +12,80 @@ ____________________________________________________
# <a href="https://richonguzman.github.io/lora-igate-web-flasher/installer.html" target="_blank">WEB FLASHER/INSTALLER</a>
# <a href="https://github.com/richonguzman/LoRa_APRS_iGate/blob/main/manual/LoRa_APRS_iGate_CA2RXU_Firmware_Manual.pdf" target="_blank">LoRa APRS iGate CA2RXU Firmware Manual</a>
# <a href="https://drive.google.com/file/d/1Hff_Szd7ks8RC7_RiV6POxPJlclbO05M/view?usp=sharing" target="_blank">LoRa APRS iGate CA2RXU Firmware Manual</a>
____________________________________________________
## New Web/UI
| New Menu | Map |
| --- | --- |
| ![new menu](images/1.png) | ![map](images/2.jpg) |
____________________________________________________
## You can support this project to continue to grow:
[<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/blob/main/images/github-sponsors.png">](https://github.com/sponsors/richonguzman) [<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/blob/main/images/paypalme.png">](http://paypal.me/richonguzman)
[<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/raw/main/images/github-sponsors.png">](https://github.com/sponsors/richonguzman) [<img src="https://github.com/richonguzman/LoRa_APRS_Tracker/raw/main/images/paypalme.png">](http://paypal.me/richonguzman)
____________________________________________________
## SUPPORTED BOARDS (<a href="https://github.com/richonguzman/LoRa_APRS_iGate/wiki/Supported-Boards-and-Buying-Links" target="_blank">Buying links</a>).
(NOTE: all boards with 433-868-915 MHz versions)
- TTGO Lilygo LoRa32 T3S3 V1.2 and LoRa32 V2.1 (V1.6 is the same).
- TTGO Lilygo LoRa32 LoRa32 V2.1 (V1.6 is the same) and T3S3 V1.2.
- TTGO T-Beam V1.0 , V1.1, V1.2 (also variations with SX1262 and SX1268 LoRa Modules) and Supreme V3.
- T-Deck Plus (and also regular T-Deck with/without GPS).
- HELTEC V2, V3, V3.2, T114, Wireless Stick, Wireless Stick Lite, HT-CT62, Wireless Tracker, Wireless Paper.
- HELTEC V2, V3, V3.2, V4, T114, Wireless Stick, Wireless Stick Lite V3/V3.2, HT-CT62, Wireless Tracker, Wireless Paper.
- RAK Wireless 4631 + 19007(or 19003)
- RAK Wireless 4631 + 19007(or 19003), RAK3312 and RAK3112.
- Faketec V3 (NRF52840 + Heltec HTRA62(SX1262))
- QRP Labs LightGateway 1.0 and Plus 1.0.
- ESP32 + SX1278 LoRa Module or Ebyte 400M30S (or 900M30S) 1W LoRa Module for a DIY Versions.
- ESP32 + SX1278 LoRa Module or Ebyte 400M30S (or 900M30S) or Ebyte E32 400M30S 1W LoRa Module for a DIY Versions.
- ESP32C3 + Ebyte 400M30S(or 900M30S) 1W LoRa Module for another DIY version.
- ESP32C3 SuperMini + SX1278 (RA-01/RA-02) LoRa Module DIY version.
- ESP32 + 4G/LTE A7670 Modem + SX1278 DIY Version.
- Wemos Lolin32 Oled + SX1278 DIY Version.
- and many more...
<br />
# Timeline (Versions):
- 2025-10-11 User defined NTP server added.
- 2025-10-10 Changed Wiki into a pdf manual.
- 2026-09-30 GPS baud rate auto-detection, CAD timeout (packet dropped after 10s of busy channel)
- 2026-09-23 APRSPacketLib V1.0.7 update and many code cleaning with this.
- 2026-09-17 Real APRS Symbols added to map and TNC2 output as a posibility in TNC mode.
- 2026-08-16 New UI (Web) and also Live View over a Map to see Rx stations.
- 2026-08-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added.
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
- 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added.
- 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
- 2026-02-15 Digipeater code/logic improved.
- 2026-02-08 Heltec V2 915MHz added.
- 2026-02-05 Improved "digiBackupMode" to check "APRS-IS" server connection.
- 2026-01-07 Tactical Callsign added.
- 2026-01-05 Heltec V4 support added.
- 2025-12-22 Heltec Wireless Paper V1.2 and VisionMaster E290 Added. Thanks HA5SZI.
- 2025-12-18 TCXO and packet decoding updates.
- 2025-12-01 APRSPacketLib updates, AHT20 sensor added, INA219 support added.
- 2025-10-15 APRS Bridge for TNC added.
- 2025-10-13 Rx and Tx Frequencies are now with fully configurable.
- 2025-10-13 Startup Delay to allow the Router/Modem to start WiFiAP before connecting.
- 2025-10-12 Choose to send Beacon on Rx or Tx frequency.
- 2025-10-11 User defined NTP server and send beacon over MQTT added.
- 2025-10-10 Converted the Wiki into a PDF manual.
- 2025-09-26 Heltec Wireless Bridge support added.
- 2025-09-09 MQTT added (pub+sub), Status defined by Op now and many fixes more.
- 2025-06-20 Digipeaters now with updated EcoMode (Board Sleeps until packet Rx reducing current consumption to almost 10% at idle).
@@ -77,7 +109,7 @@ ____________________________________________________
- 2024.10.08 New EcoMode for Remote Digipeaters without WiFi/WiFiAP, Screen, Leds (Example: LILYGO LoRa32 uses only 24mA, with WifiAP 150mA). APRS Message/Queries can start/stop this mode too.
- 2024.10.06 Cross Frequency Digipeater Rules added.
- 2024.09.23 Libraries Update for SDK3
- 2024.09.23 Added Enconded Telemetry for Battery (+ External Voltage) in Station GPS Beacon Packet.
- 2024.09.23 Added Enconded Telemetry for Battery (+ External Voltage) in Station GPS Beacon Packet.
- 2024.08.23 Wemos S2 Mini DIY LoRa added.
- 2024.08.19 HELTEC Wireless Paper working (still missing Epaper code).
- 2024.08.13 Web Authentication for WebUI. Thanks Mitja S57PNX.
+35 -35
View File
@@ -2,45 +2,45 @@
build_flags =
-Werror -Wall
-DELEGANTOTA_USE_ASYNC_WEBSERVER=1
-DRADIOLIB_EXCLUDE_CC1101=1
-DRADIOLIB_EXCLUDE_NRF24=1
-DRADIOLIB_EXCLUDE_RF69=1
-DRADIOLIB_EXCLUDE_SX1231=1
-DRADIOLIB_EXCLUDE_SX1233=1
-DRADIOLIB_EXCLUDE_SI443X=1
-DRADIOLIB_EXCLUDE_RFM2X=1
-DRADIOLIB_EXCLUDE_AFSK=1
-DRADIOLIB_EXCLUDE_BELL=1
-DRADIOLIB_EXCLUDE_HELLSCHREIBER=1
-DRADIOLIB_EXCLUDE_MORSE=1
-DRADIOLIB_EXCLUDE_RTTY=1
-DRADIOLIB_EXCLUDE_SSTV=1
-DRADIOLIB_EXCLUDE_AX25=1
-DRADIOLIB_EXCLUDE_DIRECT_RECEIVE=1
-DRADIOLIB_EXCLUDE_BELL=1
-DRADIOLIB_EXCLUDE_PAGER=1
-DRADIOLIB_EXCLUDE_FSK4=1
-DRADIOLIB_EXCLUDE_APRS=1
-DRADIOLIB_EXCLUDE_LORAWAN=1
-D RADIOLIB_EXCLUDE_CC1101=1
-D RADIOLIB_EXCLUDE_RF69=1
-D RADIOLIB_EXCLUDE_RFM2X=1
-D RADIOLIB_EXCLUDE_SX1231=1
-D RADIOLIB_EXCLUDE_SX1233=1
-D RADIOLIB_EXCLUDE_SI443X=1
-D RADIOLIB_EXCLUDE_NRF24=1
-D RADIOLIB_EXCLUDE_AFSK=1
-D RADIOLIB_EXCLUDE_APRS=1
-D RADIOLIB_EXCLUDE_AX25=1
-D RADIOLIB_EXCLUDE_BELL=1
-D RADIOLIB_EXCLUDE_FSK4=1
-D RADIOLIB_EXCLUDE_HELLSCHREIBER=1
-D RADIOLIB_EXCLUDE_LORAWAN=1
-D RADIOLIB_EXCLUDE_MORSE=1
-D RADIOLIB_EXCLUDE_PAGER=1
-D RADIOLIB_EXCLUDE_DIRECT_RECEIVE=1
-D RADIOLIB_EXCLUDE_RTTY=1
-D RADIOLIB_EXCLUDE_SSTV=1
-I variants/${PIOENV}
lib_deps =
adafruit/Adafruit Unified Sensor @ 1.1.14
adafruit/Adafruit BME280 Library @ 2.2.4
adafruit/Adafruit BMP280 Library @ 2.6.8
adafruit/Adafruit BME680 Library @ 2.0.4
adafruit/Adafruit Unified Sensor @ 1.1.15
adafruit/Adafruit AHTX0 @ 2.0.6
adafruit/Adafruit BME280 Library @ 2.3.0
adafruit/Adafruit BMP280 Library @ 3.0.0
adafruit/Adafruit BME680 Library @ 2.0.6
adafruit/Adafruit INA219 @ 1.2.3
adafruit/Adafruit Si7021 Library @ 1.5.3
arduino-libraries/NTPClient @ 3.2.1
ayushsharma82/ElegantOTA @ 3.1.5
bblanchon/ArduinoJson @ 6.21.3
jgromes/RadioLib @ 7.1.0
ayushsharma82/ElegantOTA @ 3.1.7
bblanchon/ArduinoJson @ 7.4.3
jgromes/RadioLib @ 7.6.0
knolleary/PubSubClient @ 2.8
mathieucarbou/AsyncTCP @ 3.2.5
mathieucarbou/ESPAsyncWebServer @ 3.2.3
mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @1.0.0
ESP32Async/AsyncTCP @ 3.5.0
ESP32Async/ESPAsyncWebServer @ 3.12.1
mikalhart/TinyGPSPlus @ 1.1.0
richonguzman/APRSPacketLib @ 1.0.10
display_libs =
adafruit/Adafruit GFX Library @ 1.11.9
adafruit/Adafruit SSD1306 @ 2.5.10
adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.17
usb_flags=
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1
-DARDUINO_USB_CDC_ON_BOOT=1
+24 -15
View File
@@ -17,35 +17,41 @@
"path": "WIDE1-1",
"sendViaAPRSIS": false,
"sendViaRF": false,
"beaconFreq": 1,
"statusActive": false,
"statusPacket": "",
"gpsActive": false,
"gpsAmbiguity": false
"ambiguityLevel": 0
},
"aprs_is": {
"active": false,
"messagesToRF": false,
"objectsToRF": false,
"server": "rotate.aprs2.net",
"passcode": "XYZVW",
"passcode": "XYZVW",
"port": 14580,
"filter": "m/10"
"filter": "m/10"
},
"personalNote": "",
"blacklist": "",
"digi": {
"mode": 0,
"ecoMode": 0
"ecoMode": 0,
"backupDigiMode": false
},
"lora": {
"txFreq": 433775000,
"rxActive": true,
"rxFreq": 433775000,
"spreadingFactor": 12,
"signalBandwidth": 125000,
"codingRate4": 5,
"power": 20,
"rxSpreadingFactor": 12,
"rxCodingRate4": 5,
"rxSignalBandwidth": 125000,
"txActive": false,
"rxActive": true
"txFreq": 433775000,
"txSpreadingFactor": 9,
"txCodingRate4": 7,
"txSignalBandwidth": 125000,
"power": 20,
"cadActive": true
},
"display": {
"alwaysOn": true,
@@ -57,11 +63,12 @@
"monitorInternalVoltage": false,
"internalSleepVoltage": 2.9,
"sendExternalVoltage": false,
"externalVoltagePin": 34,
"monitorExternalVoltage": false,
"externalSleepVoltage": 10.9,
"useExternalI2CSensor": false,
"voltageDividerR1": 100.0,
"voltageDividerR2": 27.0,
"externalVoltagePin": 34,
"sendVoltageAsTelemetry": false
},
"wxsensor": {
@@ -78,7 +85,8 @@
"tnc": {
"enableServer": false,
"enableSerial": false,
"acceptOwn": false
"acceptOwn": false,
"aprsBrigdeActive": false
},
"mqtt": {
"active": false,
@@ -86,7 +94,8 @@
"topic": "",
"username": "",
"password": "",
"port": 1883
"port": 1883,
"beaconOverMqtt": false
},
"ota": {
"username": "",
@@ -106,8 +115,8 @@
},
"other": {
"rememberStationTime": 30,
"backupDigiMode": false,
"rebootMode": false,
"rebootModeTime": 6
"rebootModeTime": 6,
"startupDelay": 0
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+1406 -1148
View File
File diff suppressed because it is too large. Load diff
+661
View File
@@ -0,0 +1,661 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
+6
View File
@@ -0,0 +1,6 @@
/* @preserve
* Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
* (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var e,i,n=1,o=arguments.length;n<o;n++)for(e in i=arguments[n])t[e]=i[e];return t}var R=Object.create||function(t){return N.prototype=t,new N};function N(){}function a(t,e){var i,n=Array.prototype.slice;return t.bind?t.bind.apply(t,n.call(arguments,1)):(i=n.call(arguments,2),function(){return t.apply(e,i.length?i.concat(n.call(arguments)):arguments)})}var D=0;function h(t){return"_leaflet_id"in t||(t._leaflet_id=++D),t._leaflet_id}function j(t,e,i){var n,o,s=function(){n=!1,o&&(r.apply(i,o),o=!1)},r=function(){n?o=arguments:(t.apply(i,arguments),setTimeout(s,e),n=!0)};return r}function H(t,e,i){var n=e[1],e=e[0],o=n-e;return t===n&&i?t:((t-e)%o+o)%o+e}function u(){return!1}function i(t,e){return!1===e?t:(e=Math.pow(10,void 0===e?6:e),Math.round(t*e)/e)}function W(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function F(t){return W(t).split(/\s+/)}function c(t,e){for(var i in Object.prototype.hasOwnProperty.call(t,"options")||(t.options=t.options?R(t.options):{}),e)t.options[i]=e[i];return t.options}function U(t,e,i){var n,o=[];for(n in t)o.push(encodeURIComponent(i?n.toUpperCase():n)+"="+encodeURIComponent(t[n]));return(e&&-1!==e.indexOf("?")?"&":"?")+o.join("&")}var V=/\{ *([\w_ -]+) *\}/g;function q(t,i){return t.replace(V,function(t,e){e=i[e];if(void 0===e)throw new Error("No value provided for variable "+t);return e="function"==typeof e?e(i):e})}var d=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function G(t,e){for(var i=0;i<t.length;i++)if(t[i]===e)return i;return-1}var K="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";function Y(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}var X=0;function J(t){var e=+new Date,i=Math.max(0,16-(e-X));return X=e+i,window.setTimeout(t,i)}var $=window.requestAnimationFrame||Y("RequestAnimationFrame")||J,Q=window.cancelAnimationFrame||Y("CancelAnimationFrame")||Y("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)};function x(t,e,i){if(!i||$!==J)return $.call(window,a(t,e));t.call(e)}function r(t){t&&Q.call(window,t)}var tt={__proto__:null,extend:l,create:R,bind:a,get lastId(){return D},stamp:h,throttle:j,wrapNum:H,falseFn:u,formatNum:i,trim:W,splitWords:F,setOptions:c,getParamString:U,template:q,isArray:d,indexOf:G,emptyImageUrl:K,requestFn:$,cancelFn:Q,requestAnimFrame:x,cancelAnimFrame:r};function et(){}et.extend=function(t){function e(){c(this),this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()}var i,n=e.__super__=this.prototype,o=R(n);for(i in(o.constructor=e).prototype=o,this)Object.prototype.hasOwnProperty.call(this,i)&&"prototype"!==i&&"__super__"!==i&&(e[i]=this[i]);if(t.statics&&l(e,t.statics),t.includes){var s=t.includes;if("undefined"!=typeof L&&L&&L.Mixin){s=d(s)?s:[s];for(var r=0;r<s.length;r++)s[r]===L.Mixin.Events&&console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.",(new Error).stack)}l.apply(null,[o].concat(t.includes))}return l(o,t),delete o.statics,delete o.includes,o.options&&(o.options=n.options?R(n.options):{},l(o.options,t.options)),o._initHooks=[],o.callInitHooks=function(){if(!this._initHooksCalled){n.callInitHooks&&n.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=o._initHooks.length;t<e;t++)o._initHooks[t].call(this)}},e},et.include=function(t){var e=this.prototype.options;return l(this.prototype,t),t.options&&(this.prototype.options=e,this.mergeOptions(t.options)),this},et.mergeOptions=function(t){return l(this.prototype.options,t),this},et.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i),this};var e={on:function(t,e,i){if("object"==typeof t)for(var n in t)this._on(n,t[n],e);else for(var o=0,s=(t=F(t)).length;o<s;o++)this._on(t[o],e,i);return this},off:function(t,e,i){if(arguments.length)if("object"==typeof t)for(var n in t)this._off(n,t[n],e);else{t=F(t);for(var o=1===arguments.length,s=0,r=t.length;s<r;s++)o?this._off(t[s]):this._off(t[s],e,i)}else delete this._events;return this},_on:function(t,e,i,n){"function"!=typeof e?console.warn("wrong listener type: "+typeof e):!1===this._listens(t,e,i)&&(e={fn:e,ctx:i=i===this?void 0:i},n&&(e.once=!0),this._events=this._events||{},this._events[t]=this._events[t]||[],this._events[t].push(e))},_off:function(t,e,i){var n,o,s;if(this._events&&(n=this._events[t]))if(1===arguments.length){if(this._firingCount)for(o=0,s=n.length;o<s;o++)n[o].fn=u;delete this._events[t]}else"function"!=typeof e?console.warn("wrong listener type: "+typeof e):!1!==(e=this._listens(Line truncated
//# sourceMappingURL=leaflet.js.map
+660 -93
View File
@@ -54,7 +54,9 @@ function loadSettings(settings) {
currentSettings = settings;
// General
document.getElementById("callsign").value = settings.callsign;
document.getElementById("tacticalCallsign").value = settings.tacticalCallsign;
document.getElementById("beacon.comment").value = settings.beacon.comment;
document.getElementById("beacon.rfCommentEveryXBeacons").value = settings.beacon.rfCommentEveryXBeacons;
document.getElementById("beacon.path").value = settings.beacon.path;
document.getElementById("beacon.symbol").value = settings.beacon.symbol;
document.getElementById("beacon.overlay").value = settings.beacon.overlay;
@@ -95,6 +97,7 @@ function loadSettings(settings) {
networksContainer.appendChild(networkElement);
networkCount++;
});
document.getElementById("startupDelay").value = settings.other.startupDelay;
// APRS-IS
document.getElementById("aprs_is.active").checked = settings.aprs_is.active;
@@ -116,9 +119,13 @@ function loadSettings(settings) {
document.getElementById("beacon.latitude").value = settings.beacon.latitude;
document.getElementById("beacon.longitude").value = settings.beacon.longitude;
document.getElementById("beacon.interval").value = settings.beacon.interval;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
document.getElementById("beacon.beaconFreq").value = settings.beacon.beaconFreq;
BeaconingViaRFCheckbox.checked = settings.beacon.sendViaRF;
BeaconingFrequency.disabled = !BeaconingViaRFCheckbox.checked;
document.getElementById("beacon.statusActive").checked = settings.beacon.statusActive;
document.getElementById("beacon.statusPacket").value = settings.beacon.statusPacket;
@@ -126,7 +133,7 @@ function loadSettings(settings) {
StatusPacket.disabled = !StatusCheckbox.checked;
document.getElementById("beacon.gpsActive").checked = settings.beacon.gpsActive;
document.getElementById("beacon.gpsAmbiguity").checked = settings.beacon.gpsAmbiguity;
document.getElementById("beacon.ambiguityLevel").value = settings.beacon.ambiguityLevel;
// Black List
document.getElementById("blacklist").value = settings.blacklist;
@@ -134,16 +141,34 @@ function loadSettings(settings) {
// Digi
document.getElementById("digi.mode").value = settings.digi.mode;
document.getElementById("digi.ecoMode").value = settings.digi.ecoMode;
document.getElementById("digi.backupDigiMode").checked = settings.digi.backupDigiMode;
// LoRa
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
document.getElementById("lora.rxFreq").value = settings.lora.rxFreq;
document.getElementById("lora.txActive").checked = settings.lora.txActive;
document.getElementById("lora.rxActive").checked = settings.lora.rxActive;
document.getElementById("lora.spreadingFactor").value = settings.lora.spreadingFactor;
document.getElementById("lora.signalBandwidth").value = settings.lora.signalBandwidth;
document.getElementById("lora.codingRate4").value = settings.lora.codingRate4;
document.getElementById("lora.rxFreq").value = settings.lora.rxFreq;
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
LoRaRxCheckbox.checked = settings.lora.rxActive;
LoRaRxFreq.disabled = !LoRaRxCheckbox.checked;
LoRaRxSpreadingFactor.disabled = !LoRaRxCheckbox.checked;
LoRaRxCodingRate.disabled = !LoRaRxCheckbox.checked;
LoRaRxSignalBandwidth.disabled = !LoRaRxCheckbox.checked;
document.getElementById("lora.txActive").checked = settings.lora.txActive;
document.getElementById("lora.txFreq").value = settings.lora.txFreq;
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
document.getElementById("lora.power").value = settings.lora.power;
document.getElementById("lora.cadActive").checked = settings.lora.cadActive;
LoRaTxCheckbox.checked = settings.lora.txActive;
LoRaTxFreq.disabled = !LoRaTxCheckbox.checked;
LoRaTxSpreadingFactor.disabled = !LoRaTxCheckbox.checked;
LoRaTxCodingRate.disabled = !LoRaTxCheckbox.checked;
LoRaTxSignalBandwidth.disabled = !LoRaTxCheckbox.checked;
LoRaPower.disabled = !LoRaTxCheckbox.checked;
LoRaCadActive.disabled = !LoRaTxCheckbox.checked;
// Display
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
@@ -160,13 +185,16 @@ function loadSettings(settings) {
MonitorInternalSleepVoltage.disabled = !MonitorInternalVoltageCheckbox.checked;
document.getElementById("battery.sendExternalVoltage").checked = settings.battery.sendExternalVoltage;
document.getElementById("battery.useExternalI2CSensor").checked = settings.battery.useExternalI2CSensor;
document.getElementById("battery.externalVoltagePin").value = settings.battery.externalVoltagePin;
document.getElementById("battery.voltageDividerR1").value = settings.battery.voltageDividerR1.toFixed(1);
document.getElementById("battery.voltageDividerR2").value = settings.battery.voltageDividerR2.toFixed(1);
SendExternalVoltageCheckbox.checked = settings.battery.sendExternalVoltage;
ExternalVoltagePin.disabled = !SendExternalVoltageCheckbox.checked;
ExternalVoltageDividerR1.disabled = !SendExternalVoltageCheckbox.checked;
ExternalVoltageDividerR2.disabled = !SendExternalVoltageCheckbox.checked;
UseExternalI2CSensorCheckbox.disabled = !SendExternalVoltageCheckbox.checked;
ExternalVoltagePin.disabled = !SendExternalVoltageCheckbox.checked || UseExternalI2CSensorCheckbox.checked;
ExternalVoltageDividerR1.disabled = !SendExternalVoltageCheckbox.checked || UseExternalI2CSensorCheckbox.checked;
ExternalVoltageDividerR2.disabled = !SendExternalVoltageCheckbox.checked || UseExternalI2CSensorCheckbox.checked;
document.getElementById("battery.monitorExternalVoltage").checked = settings.battery.monitorExternalVoltage;
document.getElementById("battery.externalSleepVoltage").value = settings.battery.externalSleepVoltage.toFixed(1);
@@ -182,7 +210,7 @@ function loadSettings(settings) {
TelemetryCheckbox.checked = settings.wxsensor.active;
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
// SYSLOG
document.getElementById("syslog.active").checked = settings.syslog.active;
document.getElementById("syslog.server").value = settings.syslog.server;
@@ -192,12 +220,14 @@ function loadSettings(settings) {
SyslogServer.disabled = !SyslogCheckbox.checked;
SyslogPort.disabled = !SyslogCheckbox.checked;
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
// TNC
if (settings.tnc) {
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
document.getElementById("tnc.enableSerial").checked = settings.tnc.enableSerial;
document.getElementById("tnc.acceptOwn").checked = settings.tnc.acceptOwn;
document.getElementById("tnc.aprsBridgeActive").checked = settings.tnc.aprsBridgeActive;
document.getElementById("tnc.kissProtocol").value = settings.tnc.kissProtocol ? "KISS" : "TNC2";
}
// MQTT
@@ -207,12 +237,14 @@ function loadSettings(settings) {
document.getElementById("mqtt.username").value = settings.mqtt.username;
document.getElementById("mqtt.password").value = settings.mqtt.password;
document.getElementById("mqtt.port").value = settings.mqtt.port;
MqttCheckbox.checked = settings.mqtt.active;
MqttServer.disabled = !MqttCheckbox.check;
MqttTopic.disabled = !MqttCheckbox.check;
MqttUsername.disabled = !MqttCheckbox.check;
MqttPassword.disabled = !MqttCheckbox.check;
MqttPort.disabled = !MqttCheckbox.check;
document.getElementById("mqtt.beaconOverMqtt").checked = settings.mqtt.beaconOverMqtt;
MqttCheckbox.checked = settings.mqtt.active;
MqttServer.disabled = !MqttCheckbox.checked;
MqttTopic.disabled = !MqttCheckbox.checked;
MqttUsername.disabled = !MqttCheckbox.checked;
MqttPassword.disabled = !MqttCheckbox.checked;
MqttPort.disabled = !MqttCheckbox.checked;
MqttBeaconOverMqtt.disabled = !MqttCheckbox.checked;
// Reboot
document.getElementById("other.rebootMode").checked = settings.other.rebootMode;
@@ -221,8 +253,10 @@ function loadSettings(settings) {
RebootModeTime.disabled = !RebootModeCheckbox.check;
// WiFi Auto AP
document.getElementById("wifi.autoAP.enabled").checked = settings.wifi.autoAP.enabled;
document.getElementById("wifi.autoAP.password").value = settings.wifi.autoAP.password;
document.getElementById("wifi.autoAP.timeout").value = settings.wifi.autoAP.timeout;
toggleWiFiAutoAPFields();
// OTA
document.getElementById("ota.username").value = settings.ota.username;
@@ -244,11 +278,9 @@ function loadSettings(settings) {
document.getElementById("ntp.server").value = settings.ntp.server;
document.getElementById("ntp.gmtCorrection").value = settings.ntp.gmtCorrection;
// Experimental
document.getElementById("other.backupDigiMode").checked = settings.other.backupDigiMode;
clampOutOfRangeInputs();
updateImage();
refreshSpeedStandard();
}
function showToast(message) {
@@ -300,6 +332,12 @@ function updateImage() {
}
}
// Beaconing Switches
const BeaconingViaRFCheckbox = document.querySelector('input[name="beacon.sendViaRF"]');
const BeaconingFrequency = document.querySelector('select[name="beacon.beaconFreq"]');
BeaconingViaRFCheckbox.addEventListener("change", function() {
BeaconingFrequency.disabled = !this.checked;
});
// Status Switch
const StatusCheckbox = document.querySelector('input[name="beacon.statusActive"]');
@@ -344,13 +382,21 @@ MonitorExternalVoltageCheckbox.addEventListener("change", function () {
MonitorExternalSleepVoltage.disabled = !this.checked;
});
const SendExternalVoltageCheckbox = document.querySelector('input[name="battery.sendExternalVoltage"]');
const UseExternalI2CSensorCheckbox = document.querySelector('input[name="battery.useExternalI2CSensor"]');
const ExternalVoltagePin = document.querySelector('input[name="battery.externalVoltagePin"]');
const ExternalVoltageDividerR1 = document.querySelector('input[name="battery.voltageDividerR1"]');
const ExternalVoltageDividerR2 = document.querySelector('input[name="battery.voltageDividerR2"]');
SendExternalVoltageCheckbox.addEventListener("change", function () {
ExternalVoltagePin.disabled = !this.checked;
ExternalVoltageDividerR1.disabled = !this.checked;
ExternalVoltageDividerR2.disabled = !this.checked;
UseExternalI2CSensorCheckbox.disabled = !this.checked;
ExternalVoltagePin.disabled = !this.checked || UseExternalI2CSensorCheckbox.checked;
ExternalVoltageDividerR1.disabled = !this.checked || UseExternalI2CSensorCheckbox.checked;
ExternalVoltageDividerR2.disabled = !this.checked || UseExternalI2CSensorCheckbox.checked;
});
UseExternalI2CSensorCheckbox.addEventListener("change", function () {
ExternalVoltagePin.disabled = this.checked;
ExternalVoltageDividerR1.disabled = this.checked;
ExternalVoltageDividerR2.disabled = this.checked;
});
// Telemetry Switches
@@ -362,6 +408,36 @@ TelemetryCheckbox.addEventListener("change", function () {
TelemetryTempCorrection.disabled = !this.checked;
});
// LoRa Rx Switch
const LoRaRxCheckbox = document.querySelector('input[name="lora.rxActive"]');
const LoRaRxFreq = document.querySelector('[name="lora.rxFreq"]');
const LoRaRxSpreadingFactor = document.querySelector('[name="lora.rxSpreadingFactor"]');
const LoRaRxCodingRate = document.querySelector('[name="lora.rxCodingRate4"]');
const LoRaRxSignalBandwidth = document.querySelector('[name="lora.rxSignalBandwidth"]');
LoRaRxCheckbox.addEventListener("change", function () {
LoRaRxFreq.disabled = !this.checked;
LoRaRxSpreadingFactor.disabled = !this.checked;
LoRaRxCodingRate.disabled = !this.checked;
LoRaRxSignalBandwidth.disabled = !this.checked;
});
// LoRa Tx Switch
const LoRaTxCheckbox = document.querySelector('input[name="lora.txActive"]');
const LoRaTxFreq = document.querySelector('[name="lora.txFreq"]');
const LoRaTxSpreadingFactor = document.querySelector('[name="lora.txSpreadingFactor"]');
const LoRaTxCodingRate = document.querySelector('[name="lora.txCodingRate4"]');
const LoRaTxSignalBandwidth = document.querySelector('[name="lora.txSignalBandwidth"]');
const LoRaPower = document.querySelector('[name="lora.power"]');
const LoRaCadActive = document.querySelector('[name="lora.cadActive"]');
LoRaTxCheckbox.addEventListener("change", function () {
LoRaTxFreq.disabled = !this.checked;
LoRaTxSpreadingFactor.disabled = !this.checked;
LoRaTxCodingRate.disabled = !this.checked;
LoRaTxSignalBandwidth.disabled = !this.checked;
LoRaPower.disabled = !this.checked;
LoRaCadActive.disabled = !this.checked;
});
// Syslog Switches
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
const SyslogServer = document.querySelector('input[name="syslog.server"]');
@@ -380,12 +456,14 @@ const MqttTopic = document.querySelector('input[name="mqtt.topic
const MqttUsername = document.querySelector('input[name="mqtt.username"]');
const MqttPassword = document.querySelector('input[name="mqtt.password"]');
const MqttPort = document.querySelector('input[name="mqtt.port"]');
const MqttBeaconOverMqtt = document.querySelector('input[name="mqtt.beaconOverMqtt"]');
MqttCheckbox.addEventListener("change", function () {
MqttServer.disabled = !this.checked;
MqttTopic.disabled = !this.checked;
MqttUsername.disabled = !this.checked;
MqttPassword.disabled = !this.checked;
MqttPort.disabled = !this.checked;
MqttBeaconOverMqtt.disabled = !this.checked;
});
// Reboot Switches
@@ -404,6 +482,17 @@ WebadminCheckbox.addEventListener("change", function () {
WebadminPassword.disabled = !this.checked;
});
// WiFi Auto AP Switches
const WiFiAutoAPCheckbox = document.querySelector('input[name="wifi.autoAP.enabled"]');
WiFiAutoAPCheckbox.addEventListener("change", function () {
toggleWiFiAutoAPFields();
});
function toggleWiFiAutoAPFields() {
const isEnabled = WiFiAutoAPCheckbox.checked;
const autoAPConfig = document.getElementById('wifi-autoap-config');
if (autoAPConfig) autoAPConfig.style.display = isEnabled ? 'block' : 'none';
}
document.querySelector(".new button").addEventListener("click", function () {
const networksContainer = document.querySelector(".list-networks");
@@ -452,65 +541,6 @@ document
updateImage();
});
const speedStandards = {
300: [125, 5, 12],
244: [125, 6, 12],
209: [125, 7, 12],
183: [125, 8, 12],
610: [125, 8, 10],
1200: [125, 7, 9],
};
function refreshSpeedStandard() {
const bw = Number(document.getElementById("lora.signalBandwidth").value);
const cr4 = Number(document.getElementById("lora.codingRate4").value);
const sf = Number(document.getElementById("lora.spreadingFactor").value);
let found = false;
for (const speed in speedStandards) {
const standard = speedStandards[speed];
if (standard[0] !== bw / 1000) continue;
if (standard[1] !== cr4) continue;
if (standard[2] !== sf) continue;
document.getElementById("action.speed").value = speed;
found = true;
break;
}
if (!found) {
document.getElementById("action.speed").value = "";
}
}
document
.getElementById("lora.signalBandwidth")
.addEventListener("focusout", refreshSpeedStandard);
document
.getElementById("lora.codingRate4")
.addEventListener("focusout", refreshSpeedStandard);
document
.getElementById("lora.spreadingFactor")
.addEventListener("focusout", refreshSpeedStandard);
document.getElementById("action.speed").addEventListener("change", function () {
const speed = document.getElementById("action.speed").value;
if (speed !== "") {
const value = speedStandards[Number(speed)];
const bw = value[0];
const cr4 = value[1];
const sf = value[2];
document.getElementById("lora.signalBandwidth").value = bw * 1000;
document.getElementById("lora.codingRate4").value = cr4;
document.getElementById("lora.spreadingFactor").value = sf;
}
});
const form = document.querySelector("form");
@@ -576,13 +606,12 @@ function loadReceivedPackets(packets) {
packets.forEach((packet) => {
const element = document.createElement("tr");
element.innerHTML = `
<td>${packet.rxTime}</td>
<td>${packet.packet}</td>
<td>${packet.RSSI}</td>
<td>${packet.SNR}</td>
`;
['rxDate', 'rxTime', 'packet', 'RSSI', 'SNR'].forEach((field) => {
const cell = document.createElement("td");
cell.textContent = String(packet[field]);
element.appendChild(cell);
});
container.appendChild(element);
})
@@ -609,8 +638,546 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
document.getElementById('received-packets').classList.remove('d-none');
document.getElementById('configuration').classList.add('d-none');
document.querySelector('button[type=submit]').remove();
fetchReceivedPackets();
})
})
/* ---------- Stations Map (Leaflet, CDN) ---------- */
let mapInstance = null;
let mapMarkers = null;
let mapTimer = null;
let mapTileErrShown = false;
let iGateLatLng = null;
let mapAutoFitDone = false;
function escapeHtml(value) { // shows received data as text inside HTML strings (map popups / icons)
return String(value ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function setMapMessage(text) {
const el = document.getElementById("map");
if (el) {
el.innerHTML = `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:var(--msh-text-dim,#8b949e);text-align:center;padding:20px;">${text}</div>`;
}
}
/*
* APRS symbol renderer for Stations Map
*
* s.symbol contains APRS table + symbol code, for example:
* "/>" Primary table
* "\\>" Alternate table
* "L&" Alternate table with overlay
*
* Sprite layout:
* aprs-symbols-24-0.png = Primary table
* aprs-symbols-24-1.png = Alternate table
* aprs-symbols-24-2.png = Overlay characters
*/
function getAprsSymbolIcon(symbol, callsign = "") {
if (!symbol || String(symbol).length < 2) {
return null;
}
const value = String(symbol);
let table = value.charAt(0);
let code = value.charAt(1);
let overlay = null;
/*
* Standard APRS:
* "/" = Primary
* "\\" = Alternate
*
* A-Z / 0-9 / a-j in table position are overlays
* for the Alternate table.
*/
if (
table !== "/" &&
table !== "\\"
) {
overlay = table;
table = "\\";
}
const ascii = code.charCodeAt(0);
/*
* APRS printable symbol codes are ASCII 33..126.
*/
if (
ascii < 33 ||
ascii > 126
) {
return null;
}
const index = ascii - 33;
/*
* Sprite sheet contains 16 columns x 6 rows.
* Each cell occupies 24x24 CSS px; the
* "background-size" below tells the browser to scale the sheet back
* down to its original 24px-per-cell layout, so all position math
* stays in 24px units regardless of source resolution.
*/
const columns = 16;
const sheetSize = "384px 144px"; // 16*24 x 6*24, same for all 3 sheets
const col = index % columns;
const row = Math.floor(index / columns);
const sprite =
table === "/"
? "/aprs-symbols-24-0.png"
: "/aprs-symbols-24-1.png";
const x = -(col * 24);
const y = -(row * 24);
let overlayHtml = "";
if (overlay) {
let displayOverlay = overlay;
/*
* APRS compressed numeric overlay:
* a-j -> 0-9
*/
if (
overlay >= "a" &&
overlay <= "j"
) {
displayOverlay =
String(
overlay.charCodeAt(0) -
"a".charCodeAt(0)
);
}
/*
* The overlay sprite sheet does NOT follow the ascii-33 layout used
* by the primary/alternate tables above; it has its own grid:
* "0" at row 0 col 15, "1".."9" at row 1 col 0-8, "A".."P" at row 2,
* "Q".."Z" at row 3 col 0-9. Verified by hand against the PNG.
*/
let overlayCol = null;
let overlayRow = null;
if (displayOverlay === "0") {
overlayCol = 15;
overlayRow = 0;
} else if (displayOverlay >= "1" && displayOverlay <= "9") {
overlayCol = displayOverlay.charCodeAt(0) - "1".charCodeAt(0);
overlayRow = 1;
} else if (displayOverlay >= "A" && displayOverlay <= "P") {
overlayCol = displayOverlay.charCodeAt(0) - "A".charCodeAt(0);
overlayRow = 2;
} else if (displayOverlay >= "Q" && displayOverlay <= "Z") {
overlayCol = displayOverlay.charCodeAt(0) - "Q".charCodeAt(0);
overlayRow = 3;
}
if (overlayCol !== null) {
const ox = -(overlayCol * 24);
const oy = -(overlayRow * 24);
overlayHtml = `
<span style="
position:absolute;
left:0;
top:0;
width:24px;
height:24px;
background-image:url('/aprs-symbols-24-2.png');
background-repeat:no-repeat;
background-position:${ox}px ${oy}px;
background-size:${sheetSize};
pointer-events:none;
"></span>
`;
}
}
return L.divIcon({
className: "",
html: `
<div style="
position:relative;
width:24px;
height:24px;
background-image:url('${sprite}');
background-repeat:no-repeat;
background-position:${x}px ${y}px;
background-size:${sheetSize};
">
${overlayHtml}
<span style="
position:absolute;
left:27px;
top:6px;
font:600 10px Arial,sans-serif;
line-height:12px;
white-space:nowrap;
color:#111;
text-shadow:
-1px -1px 0 #fff,
1px -1px 0 #fff,
-1px 1px 0 #fff,
1px 1px 0 #fff;
pointer-events:none;
">${escapeHtml(callsign)}</span>
</div>
`,
iconSize: [24, 24],
iconAnchor: [12, 12],
popupAnchor: [0, -12]
});
}
let mapStationsCache = [];
let mapRouteLayer = null;
function getUsedDigipeaters(path) {
if (!path) return [];
return path
.split(",")
.map((x) => x.trim())
.filter((x) => x.endsWith("*"))
.map((x) => x.replace(/\*+$/, ""))
.filter((x) =>
x &&
!x.startsWith("WIDE") &&
!x.startsWith("TRACE") &&
!x.startsWith("TCPIP") &&
!x.startsWith("TCPXX") &&
!x.startsWith("qA")
);
}
function describeMapPath(station) {
const digis = getUsedDigipeaters(station.path);
// heardDirect viene del firmware: true si ALGUNA VEZ se oyo directo,
// aunque el ultimo paquete haya llegado repetido.
if (station.heardDirect && digis.length) {
return "Direct+Via: " + digis.join(" → ");
}
if (digis.length) {
return "Via: " + digis.join(" → ");
}
return "Direct";
}
function clearMapRoute() {
if (mapRouteLayer && mapInstance) {
mapInstance.removeLayer(mapRouteLayer);
}
mapRouteLayer = null;
}
function drawStationRoute(station) {
if (!mapInstance || !iGateLatLng) return;
clearMapRoute();
const points = [
L.latLng(station.lat, station.lon)
];
const digis = getUsedDigipeaters(station.path);
digis.forEach((digiCall) => {
const digi = mapStationsCache.find((s) =>
String(s.callsign || "").toUpperCase() === digiCall.toUpperCase()
);
if (digi && !(digi.lat === 0 && digi.lon === 0)) {
points.push(L.latLng(digi.lat, digi.lon));
}
});
points.push(iGateLatLng);
const routeColor = digis.length > 0 ? "#2e7d32" : "#1565c0";
mapRouteLayer = L.polyline(points, {
color: routeColor,
weight: 3,
opacity: 0.9
}).addTo(mapInstance);
}
function loadMapStations(stations) {
if (!mapMarkers) return;
mapStationsCache = stations || [];
mapMarkers.clearLayers();
clearMapRoute();
let drawn = 0;
(stations || []).forEach((s) => {
if (s.lat === 0 && s.lon === 0) return; // descartar estaciones sin fix
const popup = `<b>${escapeHtml(s.callsign)}</b>`
+ (s.lastHeard ? `<br>Last: ${escapeHtml(s.lastHeard)}` : "")
+ `<br>RSSI ${s.RSSI} / SNR ${s.SNR}`
+ `<br>Packets: ${s.count}`
+ `<br><b>${escapeHtml(describeMapPath(s))}</b>`
+ (s.path ? `<br><small>Path: ${escapeHtml(s.path)}</small>` : "");
const aprsIcon =
getAprsSymbolIcon(s.symbol, s.callsign);
if (aprsIcon) {
const marker = L.marker(
[s.lat, s.lon],
{
icon: aprsIcon
}
)
.bindPopup(popup)
.addTo(mapMarkers);
marker.on("mouseover", function () {
drawStationRoute(s);
});
marker.on("mouseout", function () {
clearMapRoute();
});
} else { // Fallback: station with missing or invalid APRS symbol
const marker = L.circleMarker([s.lat, s.lon], {
radius: 6,
color: "#1d6fe0",
weight: 2,
fillColor: "#3b8cff",
fillOpacity: 0.9
})
.bindPopup(popup)
.addTo(mapMarkers);
marker.on("mouseover", function () {
drawStationRoute(s);
});
marker.on("mouseout", function () {
clearMapRoute();
});
}
drawn++;
});
if (!mapAutoFitDone && drawn > 0) { // auto-fit la 1ª vez que hay estaciones tras abrir
fitMapToStations();
mapAutoFitDone = true;
}
}
// Encuadra el mapa para que entren todas las estaciones (+ el iGate). 100% navegador.
function fitMapToStations() {
if (!mapInstance) return false;
const pts = [];
if (mapMarkers) {
mapMarkers.eachLayer((layer) => {
if (layer.getLatLng) pts.push(layer.getLatLng());
});
}
if (iGateLatLng) pts.push(iGateLatLng);
if (!pts.length) return false;
mapInstance.fitBounds(L.latLngBounds(pts), { padding: [30, 30], maxZoom: 14 });
return true;
}
function fetchMapStations() {
fetch("/stations.json")
.then((response) => response.json())
.then((stations) => {
loadMapStations(stations);
})
.catch((err) => {
console.error(err);
console.error(`Failed to load stations`);
});
}
window.showMap = function () {
if (typeof L === "undefined") { // sin internet el CDN de Leaflet no cargó
setMapMessage("Map unavailable &mdash; no internet connection.<br>The map needs internet to load (OpenStreetMap).");
return;
}
mapAutoFitDone = false; // re-encuadrar en cada apertura del Map
if (!mapInstance) {
mapInstance = L.map("map");
const tiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
attribution: "&copy; OpenStreetMap"
}).addTo(mapInstance);
tiles.on("tileerror", function () { // tiles no cargan (sin salida a internet)
if (!mapTileErrShown) {
mapTileErrShown = true;
if (window.showToast) showToast("Map tiles could not load — check internet connection.");
}
});
mapMarkers = L.layerGroup().addTo(mapInstance);
const FitControl = L.Control.extend({ // botón "Fit All Stations" (control nativo de Leaflet)
options: { position: "topright" },
onAdd: function () {
const btn = L.DomUtil.create("button", "");
btn.type = "button";
btn.title = "Fit all stations";
btn.textContent = "Fit All Stations";
btn.style.cssText = "background:#fff;color:#333;border:2px solid rgba(0,0,0,.2);border-radius:4px;padding:5px 9px;cursor:pointer;font:600 12px sans-serif;box-shadow:0 1px 4px rgba(0,0,0,.3);";
L.DomEvent.on(btn, "click", (e) => {
L.DomEvent.stop(e);
fitMapToStations();
});
return btn;
}
});
mapInstance.addControl(new FitControl());
fetch("/configuration.json") // centrar y marcar el iGate
.then((response) => response.json())
.then((config) => {
const lat = (config.beacon && config.beacon.latitude) || 0;
const lon = (config.beacon && config.beacon.longitude) || 0;
const callsign = config.callsign || "iGate";
mapInstance.setView([lat, lon], (lat || lon) ? 12 : 2);
if (lat || lon) { // rombo rojo en la posición del iGate
iGateLatLng = L.latLng(lat, lon); // guardar para incluirlo en el auto-fit
const iGateIcon = L.divIcon({
className: "igate-marker",
html: '<div style="width:14px;height:14px;background:#e23b3b;border:2px solid #fff;transform:rotate(45deg);box-shadow:0 0 4px rgba(0,0,0,.6);"></div>',
iconSize: [18, 18],
iconAnchor: [9, 9]
});
L.marker([lat, lon], { icon: iGateIcon })
.bindPopup(`<b>${callsign}</b><br>This station`)
.addTo(mapInstance); // directo al mapa: no se borra en los refrescos
}
})
.catch(() => {
mapInstance.setView([0, 0], 2);
});
}
setTimeout(function () { // el div estuvo oculto: recalcular tamaño
if (mapInstance) mapInstance.invalidateSize();
}, 0);
fetchMapStations();
if (mapTimer) clearInterval(mapTimer);
mapTimer = setInterval(function () { // refrescar solo si la sección está visible
const section = document.getElementById("sec-maps");
if (section && section.classList.contains("active")) {
fetchMapStations();
}
}, 15000);
}
/* ------------------------------------------------- keeping the form submittable
* A control that fails HTML5 validation blocks the whole form, and when it sits in
* a panel that is not the visible one the browser cannot show its bubble either:
* pressing Save then does nothing at all -- no message, no request, no clue. Two
* guards against that: pull stored values back inside the allowed range on load,
* and, if validation still fails, open the section holding the offending control
* and show what is wrong.
*/
function clampOutOfRangeInputs() {
const fixed = [];
document.querySelectorAll("#configuration input[type=number]").forEach((el) => {
if (el.disabled || el.value === "") return;
const value = parseFloat(el.value);
if (isNaN(value)) return;
const min = el.min !== "" ? parseFloat(el.min) : null;
const max = el.max !== "" ? parseFloat(el.max) : null;
let clamped = value;
if (max !== null && value > max) clamped = max;
if (min !== null && value < min) clamped = min;
if (clamped !== value) {
el.value = clamped;
fixed.push((el.name || el.id) + ": " + value + " &rarr; " + clamped);
}
});
if (fixed.length) {
showToast("Stored values outside the allowed range, adjusted:<br>" + fixed.join("<br>")
+ "<br><small>Save to keep them.</small>");
}
}
let invalidFieldReported = false;
form.addEventListener("invalid", function (event) {
const el = event.target;
if (!invalidFieldReported) { // only the first one: the rest would pile up toasts
invalidFieldReported = true;
setTimeout(() => { invalidFieldReported = false; }, 500);
const section = el.closest(".panel-section");
if (section) {
const link = document.querySelector('.side-link[data-target="' + section.id + '"]');
if (link) {
link.click(); // reuses the sidebar switcher (also shows the config view)
} else {
document.querySelectorAll(".panel-section").forEach((s) => s.classList.remove("active"));
section.classList.add("active");
document.getElementById("configuration").classList.remove("d-none");
}
}
showToast("<b>" + (el.name || el.id) + "</b>: " + (el.validationMessage || "invalid value"));
setTimeout(() => {
el.focus();
el.scrollIntoView({ block: "center", behavior: "smooth" });
}, 0);
}
}, true);
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 711 KiB

+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+8 -9
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -27,11 +27,10 @@ namespace APRS_IS_Utils {
void upload(const String& line);
void connect();
void checkStatus();
String checkForStartingBytes(const String& packet);
void updateWiFiAPRSISDisplayInfo();
String buildPacketToUpload(const String& packet);
bool processReceivedLoRaMessage(const String& sender, const String& packet, bool thirdParty);
bool processReceivedLoRaMessage(const String& sender, const String& packet);
void processLoRaPacket(const String& packet);
String buildPacketToTx(const String& aprsisPacket, uint8_t packetType);
+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+34 -21
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -32,6 +32,7 @@ public:
class WiFi_Auto_AP {
public:
bool enabled; // Enable Auto AP
String password;
int timeout;
};
@@ -41,16 +42,18 @@ public:
double latitude;
double longitude;
String comment;
int rfCommentEveryXBeacons;
int interval;
String overlay;
String symbol;
String path;
bool sendViaRF;
String path;
bool sendViaAPRSIS;
bool gpsActive;
bool gpsAmbiguity;
bool sendViaRF;
int beaconFreq;
bool statusActive;
String statusPacket;
bool gpsActive;
int ambiguityLevel;
};
class APRS_IS {
@@ -68,18 +71,23 @@ class DIGI {
public:
int mode;
int ecoMode; // 0 = Not Active | 1 = Ultra EcoMode | 2 = Not Active (WiFi OFF, Serial ON)
bool backupDigiMode;
};
class LoraModule {
public:
long txFreq;
long rxFreq;
bool txActive;
bool rxActive;
int spreadingFactor;
long signalBandwidth;
int codingRate4;
long rxFreq;
int rxSpreadingFactor;
int rxCodingRate4;
long rxSignalBandwidth;
bool txActive;
long txFreq;
int txSpreadingFactor;
int txCodingRate4;
long txSignalBandwidth;
int power;
bool cadActive;
};
class Display {
@@ -98,6 +106,7 @@ public:
int externalVoltagePin;
bool monitorExternalVoltage;
float externalSleepVoltage;
bool useExternalI2CSensor;
float voltageDividerR1;
float voltageDividerR2;
bool sendVoltageAsTelemetry;
@@ -123,6 +132,8 @@ public:
bool enableServer;
bool enableSerial;
bool acceptOwn;
bool aprsBridgeActive;
bool kissProtocol; // true = KISS, false = TNC2 -- applies to both Serial and TCP server
};
class OTA {
@@ -158,15 +169,17 @@ public:
String username;
String password;
int port;
bool beaconOverMqtt;
};
class Configuration {
public:
String callsign;
String tacticalCallsign;
int rememberStationTime;
bool backupDigiMode;
bool rebootMode;
int rebootModeTime;
int startupDelay;
String personalNote;
String blacklist;
std::vector<WiFi_AP> wifiAPs;
@@ -179,20 +192,20 @@ public:
BATTERY battery;
WXSENSOR wxsensor;
SYSLOG syslog;
TNC tnc;
TNC tnc;
OTA ota;
WEBADMIN webadmin;
NTP ntp;
NTP ntp;
REMOTE_MANAGEMENT remoteManagement;
MQTT mqtt;
void setup();
void setDefaultValues();
bool writeFile();
Configuration();
private:
bool readFile();
String _filePath;
};
#endif
#endif
+8 -9
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -24,10 +24,9 @@
namespace DIGI_Utils {
String buildPacket(const String& path, const String& packet, bool thirdParty, bool crossFreq);
String generateDigipeatedPacket(const String& packet, bool thirdParty);
String buildPacket(const String& path, const String& packet, bool crossFreq);
String generateDigipeatedPacket(const String& packet);
void processLoRaPacket(const String& packet);
void checkEcoMode();
}
+7 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -21,6 +21,7 @@
#include <Arduino.h>
#define SCREEN_ADDRESS 0x3C ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
void displaySetup();
+8 -14
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -23,15 +23,9 @@
namespace GPS_Utils {
String getiGateLoRaBeaconPacket();
char *ax25_base91enc(char *s, uint8_t n, uint32_t v);
String encodeGPS(float latitude, float longitude, const String& overlay, const String& symbol);
void generateBeaconFirstPart();
void generateBeacons();
String decodeEncodedGPS(const String& packet);
String getReceivedGPS(const String& packet);
String getDistanceAndComment(const String& packet);
String buildDistanceAndComment(float latitude, float longitude, const String& comment);
void setup();
void getData();
+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+7 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -24,6 +24,7 @@
namespace LoRa_Utils {
int validPower(int requested);
void setup();
void sendNewPacket(const String& newPacket);
String receivePacketFromSleep();
+47
View File
@@ -0,0 +1,47 @@
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate 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 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MAP_UTILS_H_
#define MAP_UTILS_H_
#include <Arduino.h>
#include <vector>
struct MapStation {
char callsign[10]; // "XX9XXX-NN" + null -> clave para deduplicar
float latitude; // grados decimales (+N / -S)
float longitude; // grados decimales (+E / -W)
char symbol[3]; // tabla + codigo APRS (ej "/>"), "" si no hay
int16_t rssi; // ultimo RSSI
float snr; // ultimo SNR
uint16_t count; // nro de paquetes oidos de esa estacion
char lastHeard[10]; // "HH:MM:SS" si hay NTP valido, "" si no
char path[96]; // last path received
uint32_t lastHeardMillis; // millis() -> ordenar / expirar (no se muestra)
bool heardDirect; // true si ALGUNA VEZ se oyo sin digipeaters (no se resetea)
};
namespace MAP_Utils {
void upsert(const String& callsign, float latitude, float longitude, const String& path, const String& symbol, int rssi, float snr);
String getStationsJson();
}
#endif
+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+106
View File
@@ -0,0 +1,106 @@
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
* LoRa APRS iGate 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 3 of the License, or
* (at your option) any later version.
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
#include <ETH.h>
#include <vector>
/**
* Class for managing network connections
*/
class NetworkManager
{
private:
class WiFiNetwork {
public:
String ssid;
String psk;
};
bool _wifiAPmode = false;
bool _wifiSTAmode = false;
bool _ethernetMode = false;
bool _ethernetConnected = false;
unsigned long _apStartup = 0;
unsigned long _apTimeout = 0;
String _hostName = "";
std::vector<WiFiNetwork> _wifiNetworks;
volatile uint8_t _lastWiFiDisconnectReason = 0;
String _wifiAuthFailSSID = "";
int _findWiFiNetworkIndex(const String& ssid) const;
bool _connectWiFi(const WiFiNetwork& network);
bool _isWiFiAuthFailure() const;
void _processAPTimeout();
void _onNetworkEvent(arduino_event_id_t event, arduino_event_info_t /*info*/);
public:
// Constructor
NetworkManager();
// Destructor
~NetworkManager();
// Initialize network module
bool setup();
void loop();
void setHostName(const String& hostName);
// WiFi methods
bool setupAP(String apName, String apPsk = "");
bool disableAP();
void setAPTimeout(unsigned long timeout);
void addWiFiNetwork(const String& ssid, const String& psk = "");
void clearWiFiNetworks();
bool hasWiFiNetworks() const;
size_t getWiFiNetworkCount() const;
bool connectWiFi();
bool connectWiFi(const String& ssid, const String& psk = "");
bool disconnectWiFi();
String getWiFiSSID() const;
String getWiFiAPSSID() const;
String getWiFiAuthFailSSID() const;
IPAddress getWiFiIP() const;
IPAddress getWiFiAPIP() const;
wifi_mode_t getWiFiMode() const;
uint8_t* getWiFimacAddress(uint8_t* mac);
String getWiFimacAddress(void) const;
// Ethernet methods
bool ethernetConnect(eth_phy_type_t type, uint8_t phy_addr, uint8_t mdc, uint8_t mdio, int power, eth_clock_mode_t clock_mode, bool use_mac_from_efuse = false);
bool setEthernetIP(const String& staticIP, const String& gateway, const String& subnet, const String& dns1, const String& dns2);
IPAddress getEthernetIP() const;
String getEthernetMACAddress() const;
// Check if any network is available
bool isConnected() const;
// Check if specific network is connected
bool isWiFiConnected() const;
bool isEthernetConnected() const;
bool isModemConnected() const;
bool isWifiAPActive() const;
// Check if any network interface was started (lwip TCP/IP stack is initialized)
bool hasActiveInterface() const;
};
+8 -7
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -24,9 +24,10 @@
namespace NTP_Utils {
void setup();
bool setup();
void update();
String getFormatedTime();
String getFormatedDate();
}
+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+11 -8
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -29,12 +29,12 @@
namespace POWER_Utils {
#ifdef VEXT_CTRL
#ifdef VEXT_CTRL_PIN
void vext_ctrl_ON();
void vext_ctrl_OFF();
#endif
#ifdef ADC_CTRL
#ifdef ADC_CTRL_PIN
void adc_ctrl_ON();
void adc_ctrl_OFF();
#endif
@@ -46,6 +46,9 @@ namespace POWER_Utils {
void activateLoRa();
void deactivateLoRa();
bool begin(TwoWire &port);
#ifdef SOLAR_CHARGE_PIN
void checkSolarCharge();
#endif
void setup();
}
+8 -7
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -24,7 +24,8 @@
namespace QUERY_Utils {
String process(const String& query, const String& station, bool queryFromAPRSIS, bool thirdParty);
bool isQuery(const String& message);
String process(const String& query, const String& station, bool queryFromAPRSIS, bool thirdParty);
}
+8 -7
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -21,8 +21,9 @@
#include <Arduino.h>
namespace SLEEP_Utils {
void setup();
void checkWakeUpFlag();
void startSleeping();
+8 -15
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -23,12 +23,6 @@
#include <Arduino.h>
struct Packet25SegBuffer {
uint32_t receivedTime;
String station;
String payload;
};
struct LastHeardStation {
uint32_t lastHeardTime;
String station;
@@ -43,11 +37,10 @@ namespace STATION_Utils {
void deleteNotHeard();
void updateLastHeard(const String& station);
bool wasHeard(const String& station);
void clean25SegBuffer();
bool check25SegBuffer(const String& station, const String& textMessage);
bool isIn25SegHashBuffer(const String& station, const String& textMessage);
void processOutputPacketBufferUltraEcoMode();
void processOutputPacketBuffer();
void addToOutputPacketBuffer(const String& packet);
void addToOutputPacketBuffer(const String& packet, bool flag = false);
}
+11 -7
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -19,12 +19,16 @@
#ifndef SYSLOG_H_
#define SYSLOG_H_
#include <APRSPacketLib.h>
#include <Arduino.h>
namespace SYSLOG_Utils {
void log(const uint8_t type ,const String& packet, const int rssi, const float snr, const int freqError);
void logLoRaRx(APRSPacket& aprsPacket, const String& packet, const int rssi, const float snr, const int freqError);
void logAPRSISTx(const String& packet);
void logLoRaTx(const String& packet);
void logCRCError(const String& packet, const int rssi, const float snr, const int freqError);
void setup();
}
+8 -7
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -27,7 +27,8 @@ namespace TELEMETRY_Utils {
void sendEquationsUnitsParameters();
String generateEncodedTelemetryBytes(float value, bool counterBytes, byte telemetryType);
String generateEncodedTelemetry();
void checkEUPInterval();
}
#endif
+8 -8
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -27,8 +27,8 @@ namespace TNC_Utils {
void setup();
void loop();
void sendToClients(const String& packet);
void sendToSerial(const String& packet);
void sendToClients(const String& packet, bool stripBytes = false);
void sendToSerial(const String& packet, bool stripBytes = false);
}
+14 -10
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -19,11 +19,12 @@
#ifndef UTILS_H_
#define UTILS_H_
#include <APRSPacketLib.h>
#include <Arduino.h>
class ReceivedPacket {
public:
String rxDate;
String rxTime;
String packet;
int RSSI;
@@ -35,17 +36,20 @@ namespace Utils {
void processStatus();
String getLocalIP();
void setupDisplay();
void activeStations();
void showActiveStations();
void checkBeaconInterval();
void checkDisplayInterval();
void validateFreqs();
void typeOfPacket(const String& packet, const uint8_t packetType);
String padForDisplay(const String& text);
void updateLoRaPacketDisplayInfo(APRSPacket& aprsPacket, const uint8_t packetType);
void updateAPRSISPacketDisplayInfo(const String& packet);
void print(const String& text);
void println(const String& text);
void checkRebootMode();
void checkRebootTime();
void checkSleepByLowBatteryVoltage(uint8_t mode);
bool checkValidCallsign(const String& callsign);
bool callsignIsValid(const String& callsign);
void startupDelay();
}
+6 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
+6 -7
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -27,7 +27,6 @@ namespace WIFI_Utils {
void checkWiFi();
void startAutoAP();
void startWiFi();
void checkAutoAPTimeout();
void setup();
}
+8 -6
View File
@@ -1,17 +1,17 @@
/* Copyright (C) 2025 Ricardo Guzman - CA2RXU
*
/* Copyright (C) 2026 Ricardo Guzman - CA2RXU
*
* This file is part of LoRa APRS iGate.
*
*
* LoRa APRS iGate 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 3 of the License, or
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
*
* LoRa APRS iGate is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* You should have received a copy of the GNU General Public License
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/
@@ -20,6 +20,7 @@
#define WX_UTILS_H_
#include <Adafruit_Sensor.h>
#include <Adafruit_AHTX0.h>
#include <Adafruit_BME280.h>
#include <Adafruit_BMP280.h>
#include <Adafruit_BME680.h>
@@ -35,6 +36,7 @@ namespace WX_Utils {
String generateHumString(const float sensorHum);
String generatePresString(const float sensorPres);
String readDataSensor();
float readBoardTemperature();
}
-34
View File
@@ -1,34 +0,0 @@
#!/usr/bin/env python
#
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
# This executable script is a thin wrapper around the main functionality
# in the esptool Python package
# When updating this script, please also update espefuse.py and espsecure.py
import contextlib
import os
import sys
if os.name != "nt":
# Linux/macOS: remove current script directory to avoid importing this file
# as a module; we want to import the installed esptool module instead
with contextlib.suppress(ValueError):
if sys.path[0].endswith("/bin"):
sys.path.pop(0)
sys.path.remove(os.path.dirname(sys.executable))
# Linux/macOS: delete imported module entry to force Python to load
# the module from scratch; this enables importing esptool module in
# other Python scripts
with contextlib.suppress(KeyError):
del sys.modules["esptool"]
import esptool
if __name__ == "__main__":
esptool._main()
File diff suppressed because it is too large. Load diff
@@ -1,9 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import esptool
if __name__ == "__main__":
esptool._main()
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-92
View File
@@ -1,92 +0,0 @@
# SPDX-FileCopyrightText: 2014-2023 Espressif Systems (Shanghai) CO LTD,
# other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import configparser
import os
CONFIG_OPTIONS = [
"timeout",
"chip_erase_timeout",
"max_timeout",
"sync_timeout",
"md5_timeout_per_mb",
"erase_region_timeout_per_mb",
"erase_write_timeout_per_mb",
"mem_end_rom_timeout",
"serial_write_timeout",
"connect_attempts",
"write_block_attempts",
"reset_delay",
"custom_reset_sequence",
]
def _validate_config_file(file_path, verbose=False):
if not os.path.exists(file_path):
return False
cfg = configparser.RawConfigParser()
try:
cfg.read(file_path, encoding="UTF-8")
# Only consider it a valid config file if it contains [esptool] section
if cfg.has_section("esptool"):
if verbose:
unknown_opts = list(set(cfg.options("esptool")) - set(CONFIG_OPTIONS))
unknown_opts.sort()
no_of_unknown_opts = len(unknown_opts)
if no_of_unknown_opts > 0:
suffix = "s" if no_of_unknown_opts > 1 else ""
print(
"Ignoring unknown config file option{}: {}".format(
suffix, ", ".join(unknown_opts)
)
)
return True
except (UnicodeDecodeError, configparser.Error) as e:
if verbose:
print(f"Ignoring invalid config file {file_path}: {e}")
return False
def _find_config_file(dir_path, verbose=False):
for candidate in ("esptool.cfg", "setup.cfg", "tox.ini"):
cfg_path = os.path.join(dir_path, candidate)
if _validate_config_file(cfg_path, verbose):
return cfg_path
return None
def load_config_file(verbose=False):
set_with_env_var = False
env_var_path = os.environ.get("ESPTOOL_CFGFILE")
if env_var_path is not None and _validate_config_file(env_var_path):
cfg_file_path = env_var_path
set_with_env_var = True
else:
home_dir = os.path.expanduser("~")
os_config_dir = (
f"{home_dir}/.config/esptool"
if os.name == "posix"
else f"{home_dir}/AppData/Local/esptool/"
)
# Search priority: 1) current dir, 2) OS specific config dir, 3) home dir
for dir_path in (os.getcwd(), os_config_dir, home_dir):
cfg_file_path = _find_config_file(dir_path, verbose)
if cfg_file_path:
break
cfg = configparser.ConfigParser()
cfg["esptool"] = {} # Create an empty esptool config for when no file is found
if cfg_file_path is not None:
# If config file is found and validated, read and parse it
cfg.read(cfg_file_path)
if verbose:
msg = " (set with ESPTOOL_CFGFILE)" if set_with_env_var else ""
print(
f"Loaded custom configuration from "
f"{os.path.abspath(cfg_file_path)}{msg}"
)
return cfg, cfg_file_path
File diff suppressed because it is too large. Load diff
-178
View File
@@ -1,178 +0,0 @@
# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import struct
import time
from .util import FatalError
# Used for resetting into bootloader on Unix-like systems
if os.name != "nt":
import fcntl
import termios
# Constants used for terminal status lines reading/setting.
# Taken from pySerial's backend for IO:
# https://github.com/pyserial/pyserial/blob/master/serial/serialposix.py
TIOCMSET = getattr(termios, "TIOCMSET", 0x5418)
TIOCMGET = getattr(termios, "TIOCMGET", 0x5415)
TIOCM_DTR = getattr(termios, "TIOCM_DTR", 0x002)
TIOCM_RTS = getattr(termios, "TIOCM_RTS", 0x004)
DEFAULT_RESET_DELAY = 0.05 # default time to wait before releasing boot pin after reset
class ResetStrategy(object):
def __init__(self, port, reset_delay=DEFAULT_RESET_DELAY):
self.port = port
self.reset_delay = reset_delay
def __call__():
pass
def _setDTR(self, state):
self.port.setDTR(state)
def _setRTS(self, state):
self.port.setRTS(state)
# Work-around for adapters on Windows using the usbser.sys driver:
# generate a dummy change to DTR so that the set-control-line-state
# request is sent with the updated RTS state and the same DTR state
self.port.setDTR(self.port.dtr)
def _setDTRandRTS(self, dtr=False, rts=False):
status = struct.unpack(
"I", fcntl.ioctl(self.port.fileno(), TIOCMGET, struct.pack("I", 0))
)[0]
if dtr:
status |= TIOCM_DTR
else:
status &= ~TIOCM_DTR
if rts:
status |= TIOCM_RTS
else:
status &= ~TIOCM_RTS
fcntl.ioctl(self.port.fileno(), TIOCMSET, struct.pack("I", status))
class ClassicReset(ResetStrategy):
"""
Classic reset sequence, sets DTR and RTS lines sequentially.
"""
def __call__(self):
self._setDTR(False) # IO0=HIGH
self._setRTS(True) # EN=LOW, chip in reset
time.sleep(0.1)
self._setDTR(True) # IO0=LOW
self._setRTS(False) # EN=HIGH, chip out of reset
time.sleep(self.reset_delay)
self._setDTR(False) # IO0=HIGH, done
class UnixTightReset(ResetStrategy):
"""
UNIX-only reset sequence with custom implementation,
which allows setting DTR and RTS lines at the same time.
"""
def __call__(self):
self._setDTRandRTS(False, False)
self._setDTRandRTS(True, True)
self._setDTRandRTS(False, True) # IO0=HIGH & EN=LOW, chip in reset
time.sleep(0.1)
self._setDTRandRTS(True, False) # IO0=LOW & EN=HIGH, chip out of reset
time.sleep(self.reset_delay)
self._setDTRandRTS(False, False) # IO0=HIGH, done
self._setDTR(False) # Needed in some environments to ensure IO0=HIGH
class USBJTAGSerialReset(ResetStrategy):
"""
Custom reset sequence, which is required when the device
is connecting via its USB-JTAG-Serial peripheral.
"""
def __call__(self):
self._setRTS(False)
self._setDTR(False) # Idle
time.sleep(0.1)
self._setDTR(True) # Set IO0
self._setRTS(False)
time.sleep(0.1)
self._setRTS(True) # Reset. Calls inverted to go through (1,1) instead of (0,0)
self._setDTR(False)
self._setRTS(True) # RTS set as Windows only propagates DTR on RTS setting
time.sleep(0.1)
self._setDTR(False)
self._setRTS(False) # Chip out of reset
class HardReset(ResetStrategy):
"""
Reset sequence for hard resetting the chip.
Can be used to reset out of the bootloader or to restart a running app.
"""
def __init__(self, port, uses_usb_otg=False):
super().__init__(port)
self.uses_usb_otg = uses_usb_otg
def __call__(self):
self._setRTS(True) # EN->LOW
if self.uses_usb_otg:
# Give the chip some time to come out of reset,
# to be able to handle further DTR/RTS transitions
time.sleep(0.2)
self._setRTS(False)
time.sleep(0.2)
else:
time.sleep(0.1)
self._setRTS(False)
class CustomReset(ResetStrategy):
"""
Custom reset strategy defined with a string.
CustomReset object is created as "rst = CustomReset(port, seq_str)"
and can be later executed simply with "rst()"
The seq_str input string consists of individual commands divided by "|".
Commands (e.g. R0) are defined by a code (R) and an argument (0).
The commands are:
D: setDTR - 1=True / 0=False
R: setRTS - 1=True / 0=False
U: setDTRandRTS (Unix-only) - 0,0 / 0,1 / 1,0 / or 1,1
W: Wait (time delay) - positive float number
e.g.
"D0|R1|W0.1|D1|R0|W0.05|D0" represents the ClassicReset strategy
"U1,1|U0,1|W0.1|U1,0|W0.05|U0,0" represents the UnixTightReset strategy
"""
format_dict = {
"D": "self.port.setDTR({})",
"R": "self.port.setRTS({})",
"W": "time.sleep({})",
"U": "self._setDTRandRTS({})",
}
def __call__(self):
exec(self.constructed_strategy)
def __init__(self, port, seq_str):
super().__init__(port)
self.constructed_strategy = self._parse_string_to_seq(seq_str)
def _parse_string_to_seq(self, seq_str):
try:
cmds = seq_str.split("|")
fn_calls_list = [self.format_dict[cmd[0]].format(cmd[1:]) for cmd in cmds]
except Exception as e:
raise FatalError(f'Invalid "custom_reset_sequence" option format: {e}')
return "\n".join(fn_calls_list)
@@ -1,31 +0,0 @@
from .esp32 import ESP32ROM
from .esp32c2 import ESP32C2ROM
from .esp32c3 import ESP32C3ROM
from .esp32c6 import ESP32C6ROM
from .esp32c6beta import ESP32C6BETAROM
from .esp32h2 import ESP32H2ROM
from .esp32h2beta1 import ESP32H2BETA1ROM
from .esp32h2beta2 import ESP32H2BETA2ROM
from .esp32s2 import ESP32S2ROM
from .esp32s3 import ESP32S3ROM
from .esp32s3beta2 import ESP32S3BETA2ROM
from .esp8266 import ESP8266ROM
CHIP_DEFS = {
"esp8266": ESP8266ROM,
"esp32": ESP32ROM,
"esp32s2": ESP32S2ROM,
"esp32s3beta2": ESP32S3BETA2ROM,
"esp32s3": ESP32S3ROM,
"esp32c3": ESP32C3ROM,
"esp32c6beta": ESP32C6BETAROM,
"esp32h2beta1": ESP32H2BETA1ROM,
"esp32h2beta2": ESP32H2BETA2ROM,
"esp32c2": ESP32C2ROM,
"esp32c6": ESP32C6ROM,
"esp32h2": ESP32H2ROM,
}
CHIP_LIST = list(CHIP_DEFS.keys())
ROM_LIST = list(CHIP_DEFS.values())
@@ -1,393 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import struct
import time
from ..loader import ESPLoader
from ..util import FatalError, NotSupportedError
class ESP32ROM(ESPLoader):
"""Access class for ESP32 ROM bootloader"""
CHIP_NAME = "ESP32"
IMAGE_CHIP_ID = 0
IS_STUB = False
FPGA_SLOW_BOOT = True
CHIP_DETECT_MAGIC_VALUE = [0x00F01D83]
IROM_MAP_START = 0x400D0000
IROM_MAP_END = 0x40400000
DROM_MAP_START = 0x3F400000
DROM_MAP_END = 0x3F800000
# ESP32 uses a 4 byte status reply
STATUS_BYTES_LENGTH = 4
SPI_REG_BASE = 0x3FF42000
SPI_USR_OFFS = 0x1C
SPI_USR1_OFFS = 0x20
SPI_USR2_OFFS = 0x24
SPI_MOSI_DLEN_OFFS = 0x28
SPI_MISO_DLEN_OFFS = 0x2C
EFUSE_RD_REG_BASE = 0x3FF5A000
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE + 0x18
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 7 # EFUSE_RD_DISABLE_DL_ENCRYPT
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_RD_REG_BASE # EFUSE_BLK0_WDATA0_REG
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7F << 20 # EFUSE_FLASH_CRYPT_CNT
EFUSE_RD_ABS_DONE_REG = EFUSE_RD_REG_BASE + 0x018
EFUSE_RD_ABS_DONE_0_MASK = 1 << 4
EFUSE_RD_ABS_DONE_1_MASK = 1 << 5
DR_REG_SYSCON_BASE = 0x3FF66000
APB_CTL_DATE_ADDR = DR_REG_SYSCON_BASE + 0x7C
APB_CTL_DATE_V = 0x1
APB_CTL_DATE_S = 31
SPI_W0_OFFS = 0x80
UART_CLKDIV_REG = 0x3FF40014
XTAL_CLK_DIVIDER = 1
RTCCALICFG1 = 0x3FF5F06C
TIMERS_RTC_CALI_VALUE = 0x01FFFFFF
TIMERS_RTC_CALI_VALUE_S = 7
FLASH_SIZES = {
"1MB": 0x00,
"2MB": 0x10,
"4MB": 0x20,
"8MB": 0x30,
"16MB": 0x40,
"32MB": 0x50,
"64MB": 0x60,
"128MB": 0x70,
}
FLASH_FREQUENCY = {
"80m": 0xF,
"40m": 0x0,
"26m": 0x1,
"20m": 0x2,
}
BOOTLOADER_FLASH_OFFSET = 0x1000
OVERRIDE_VDDSDIO_CHOICES = ["1.8V", "1.9V", "OFF"]
MEMORY_MAP = [
[0x00000000, 0x00010000, "PADDING"],
[0x3F400000, 0x3F800000, "DROM"],
[0x3F800000, 0x3FC00000, "EXTRAM_DATA"],
[0x3FF80000, 0x3FF82000, "RTC_DRAM"],
[0x3FF90000, 0x40000000, "BYTE_ACCESSIBLE"],
[0x3FFAE000, 0x40000000, "DRAM"],
[0x3FFE0000, 0x3FFFFFFC, "DIRAM_DRAM"],
[0x40000000, 0x40070000, "IROM"],
[0x40070000, 0x40078000, "CACHE_PRO"],
[0x40078000, 0x40080000, "CACHE_APP"],
[0x40080000, 0x400A0000, "IRAM"],
[0x400A0000, 0x400BFFFC, "DIRAM_IRAM"],
[0x400C0000, 0x400C2000, "RTC_IRAM"],
[0x400D0000, 0x40400000, "IROM"],
[0x50000000, 0x50002000, "RTC_DATA"],
]
FLASH_ENCRYPTED_WRITE_ALIGN = 32
""" Try to read the BLOCK1 (encryption key) and check if it is valid """
def is_flash_encryption_key_valid(self):
"""Bit 0 of efuse_rd_disable[3:0] is mapped to BLOCK1
this bit is at position 16 in EFUSE_BLK0_RDATA0_REG"""
word0 = self.read_efuse(0)
rd_disable = (word0 >> 16) & 0x1
# reading of BLOCK1 is NOT ALLOWED so we assume valid key is programmed
if rd_disable:
return True
else:
# reading of BLOCK1 is ALLOWED so we will read and verify for non-zero.
# When ESP32 has not generated AES/encryption key in BLOCK1,
# the contents will be readable and 0.
# If the flash encryption is enabled it is expected to have a valid
# non-zero key. We break out on first occurance of non-zero value
key_word = [0] * 7
for i in range(len(key_word)):
key_word[i] = self.read_efuse(14 + i)
# key is non-zero so break & return
if key_word[i] != 0:
return True
return False
def get_flash_crypt_config(self):
"""For flash encryption related commands we need to make sure
user has programmed all the relevant efuse correctly so before
writing encrypted write_flash_encrypt esptool will verify the values
of flash_crypt_config to be non zero if they are not read
protected. If the values are zero a warning will be printed
bit 3 in efuse_rd_disable[3:0] is mapped to flash_crypt_config
this bit is at position 19 in EFUSE_BLK0_RDATA0_REG"""
word0 = self.read_efuse(0)
rd_disable = (word0 >> 19) & 0x1
if rd_disable == 0:
"""we can read the flash_crypt_config efuse value
so go & read it (EFUSE_BLK0_RDATA5_REG[31:28])"""
word5 = self.read_efuse(5)
word5 = (word5 >> 28) & 0xF
return word5
else:
# if read of the efuse is disabled we assume it is set correctly
return 0xF
def get_encrypted_download_disabled(self):
return (
self.read_reg(self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG)
& self.EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT
)
def get_flash_encryption_enabled(self):
flash_crypt_cnt = (
self.read_reg(self.EFUSE_SPI_BOOT_CRYPT_CNT_REG)
& self.EFUSE_SPI_BOOT_CRYPT_CNT_MASK
)
# Flash encryption enabled when odd number of bits are set
return bin(flash_crypt_cnt).count("1") & 1 != 0
def get_secure_boot_enabled(self):
efuses = self.read_reg(self.EFUSE_RD_ABS_DONE_REG)
rev = self.get_chip_revision()
return efuses & self.EFUSE_RD_ABS_DONE_0_MASK or (
rev >= 300 and efuses & self.EFUSE_RD_ABS_DONE_1_MASK
)
def get_pkg_version(self):
word3 = self.read_efuse(3)
pkg_version = (word3 >> 9) & 0x07
pkg_version += ((word3 >> 2) & 0x1) << 3
return pkg_version
def get_chip_revision(self):
return self.get_major_chip_version() * 100 + self.get_minor_chip_version()
def get_minor_chip_version(self):
return (self.read_efuse(5) >> 24) & 0x3
def get_major_chip_version(self):
rev_bit0 = (self.read_efuse(3) >> 15) & 0x1
rev_bit1 = (self.read_efuse(5) >> 20) & 0x1
apb_ctl_date = self.read_reg(self.APB_CTL_DATE_ADDR)
rev_bit2 = (apb_ctl_date >> self.APB_CTL_DATE_S) & self.APB_CTL_DATE_V
combine_value = (rev_bit2 << 2) | (rev_bit1 << 1) | rev_bit0
revision = {
0: 0,
1: 1,
3: 2,
7: 3,
}.get(combine_value, 0)
return revision
def get_chip_description(self):
pkg_version = self.get_pkg_version()
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
rev3 = major_rev == 3
single_core = self.read_efuse(3) & (1 << 0) # CHIP_VER DIS_APP_CPU
chip_name = {
0: "ESP32-S0WDQ6" if single_core else "ESP32-D0WDQ6",
1: "ESP32-S0WD" if single_core else "ESP32-D0WD",
2: "ESP32-D2WD",
4: "ESP32-U4WDH",
5: "ESP32-PICO-V3" if rev3 else "ESP32-PICO-D4",
6: "ESP32-PICO-V3-02",
7: "ESP32-D0WDR2-V3",
}.get(pkg_version, "unknown ESP32")
# ESP32-D0WD-V3, ESP32-D0WDQ6-V3
if chip_name.startswith("ESP32-D0WD") and rev3:
chip_name += "-V3"
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
features = ["WiFi"]
word3 = self.read_efuse(3)
# names of variables in this section are lowercase
# versions of EFUSE names as documented in TRM and
# ESP-IDF efuse_reg.h
chip_ver_dis_bt = word3 & (1 << 1)
if chip_ver_dis_bt == 0:
features += ["BT"]
chip_ver_dis_app_cpu = word3 & (1 << 0)
if chip_ver_dis_app_cpu:
features += ["Single Core"]
else:
features += ["Dual Core"]
chip_cpu_freq_rated = word3 & (1 << 13)
if chip_cpu_freq_rated:
chip_cpu_freq_low = word3 & (1 << 12)
if chip_cpu_freq_low:
features += ["160MHz"]
else:
features += ["240MHz"]
pkg_version = self.get_pkg_version()
if pkg_version in [2, 4, 5, 6]:
features += ["Embedded Flash"]
if pkg_version == 6:
features += ["Embedded PSRAM"]
word4 = self.read_efuse(4)
adc_vref = (word4 >> 8) & 0x1F
if adc_vref:
features += ["VRef calibration in efuse"]
blk3_part_res = word3 >> 14 & 0x1
if blk3_part_res:
features += ["BLK3 partially reserved"]
word6 = self.read_efuse(6)
coding_scheme = word6 & 0x3
features += [
"Coding Scheme %s"
% {0: "None", 1: "3/4", 2: "Repeat (UNSUPPORTED)", 3: "Invalid"}[
coding_scheme
]
]
return features
def read_efuse(self, n):
"""Read the nth word of the ESP3x EFUSE region."""
return self.read_reg(self.EFUSE_RD_REG_BASE + (4 * n))
def chip_id(self):
raise NotSupportedError(self, "chip_id")
def read_mac(self):
"""Read MAC from EFUSE region"""
words = [self.read_efuse(2), self.read_efuse(1)]
bitstring = struct.pack(">II", *words)
bitstring = bitstring[2:8] # trim the 2 byte CRC
return tuple(bitstring)
def get_erase_size(self, offset, size):
return size
def override_vddsdio(self, new_voltage):
new_voltage = new_voltage.upper()
if new_voltage not in self.OVERRIDE_VDDSDIO_CHOICES:
raise FatalError(
"The only accepted VDDSDIO overrides are '1.8V', '1.9V' and 'OFF'"
)
RTC_CNTL_SDIO_CONF_REG = 0x3FF48074
RTC_CNTL_XPD_SDIO_REG = 1 << 31
RTC_CNTL_DREFH_SDIO_M = 3 << 29
RTC_CNTL_DREFM_SDIO_M = 3 << 27
RTC_CNTL_DREFL_SDIO_M = 3 << 25
# RTC_CNTL_SDIO_TIEH = (1 << 23)
# not used here, setting TIEH=1 would set 3.3V output,
# not safe for esptool.py to do
RTC_CNTL_SDIO_FORCE = 1 << 22
RTC_CNTL_SDIO_PD_EN = 1 << 21
reg_val = RTC_CNTL_SDIO_FORCE # override efuse setting
reg_val |= RTC_CNTL_SDIO_PD_EN
if new_voltage != "OFF":
reg_val |= RTC_CNTL_XPD_SDIO_REG # enable internal LDO
if new_voltage == "1.9V":
reg_val |= (
RTC_CNTL_DREFH_SDIO_M | RTC_CNTL_DREFM_SDIO_M | RTC_CNTL_DREFL_SDIO_M
) # boost voltage
self.write_reg(RTC_CNTL_SDIO_CONF_REG, reg_val)
print("VDDSDIO regulator set to %s" % new_voltage)
def read_flash_slow(self, offset, length, progress_fn):
BLOCK_LEN = 64 # ROM read limit per command (this limit is why it's so slow)
data = b""
while len(data) < length:
block_len = min(BLOCK_LEN, length - len(data))
r = self.check_command(
"read flash block",
self.ESP_READ_FLASH_SLOW,
struct.pack("<II", offset + len(data), block_len),
)
if len(r) < block_len:
raise FatalError(
"Expected %d byte block, got %d bytes. Serial errors?"
% (block_len, len(r))
)
# command always returns 64 byte buffer,
# regardless of how many bytes were actually read from flash
data += r[:block_len]
if progress_fn and (len(data) % 1024 == 0 or len(data) == length):
progress_fn(len(data), length)
return data
def get_rom_cal_crystal_freq(self):
"""
Get the crystal frequency calculated by the ROM
"""
# - Simulate the calculation in the ROM to get the XTAL frequency
# calculated by the ROM
cali_val = (
self.read_reg(self.RTCCALICFG1) >> self.TIMERS_RTC_CALI_VALUE_S
) & self.TIMERS_RTC_CALI_VALUE
clk_8M_freq = self.read_efuse(4) & (0xFF) # EFUSE_RD_CK8M_FREQ
rom_calculated_freq = cali_val * 15625 * clk_8M_freq / 40
return rom_calculated_freq
def change_baud(self, baud):
# It's a workaround to avoid esp32 CK_8M frequency drift.
rom_calculated_freq = self.get_rom_cal_crystal_freq()
valid_freq = 40000000 if rom_calculated_freq > 33000000 else 26000000
false_rom_baud = int(baud * rom_calculated_freq // valid_freq)
print(f"Changing baud rate to {baud}")
self.command(self.ESP_CHANGE_BAUDRATE, struct.pack("<II", false_rom_baud, 0))
print("Changed.")
self._set_port_baudrate(baud)
time.sleep(0.05) # get rid of garbage sent during baud rate change
self.flush_input()
class ESP32StubLoader(ESP32ROM):
"""Access class for ESP32 stub loader, runs on top of ROM."""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
def change_baud(self, baud):
ESPLoader.change_baud(self, baud)
ESP32ROM.STUB_CLASS = ESP32StubLoader
@@ -1,165 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import struct
import time
from .esp32c3 import ESP32C3ROM
from ..loader import ESPLoader
class ESP32C2ROM(ESP32C3ROM):
CHIP_NAME = "ESP32-C2"
IMAGE_CHIP_ID = 12
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42400000
DROM_MAP_START = 0x3C000000
DROM_MAP_END = 0x3C400000
# Magic value for ESP32C2 ECO0 and ECO1 respectively
CHIP_DETECT_MAGIC_VALUE = [0x6F51306F, 0x7C41A06F]
EFUSE_BASE = 0x60008800
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x040
MAC_EFUSE_REG = EFUSE_BASE + 0x040
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x30
EFUSE_SECURE_BOOT_EN_MASK = 1 << 21
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x30
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_BASE + 0x30
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 6
EFUSE_XTS_KEY_LENGTH_256_REG = EFUSE_BASE + 0x30
EFUSE_XTS_KEY_LENGTH_256 = 1 << 10
EFUSE_BLOCK_KEY0_REG = EFUSE_BASE + 0x60
EFUSE_RD_DIS_REG = EFUSE_BASE + 0x30
EFUSE_RD_DIS = 3
FLASH_FREQUENCY = {
"60m": 0xF,
"30m": 0x0,
"20m": 0x1,
"15m": 0x2,
}
MEMORY_MAP = [
[0x00000000, 0x00010000, "PADDING"],
[0x3C000000, 0x3C400000, "DROM"],
[0x3FCA0000, 0x3FCE0000, "DRAM"],
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
[0x3FF00000, 0x3FF50000, "DROM_MASK"],
[0x40000000, 0x40090000, "IROM_MASK"],
[0x42000000, 0x42400000, "IROM"],
[0x4037C000, 0x403C0000, "IRAM"],
]
def get_pkg_version(self):
num_word = 1
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 22) & 0x07
def get_chip_description(self):
chip_name = {
0: "ESP32-C2",
1: "ESP32-C2",
}.get(self.get_pkg_version(), "unknown ESP32-C2")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_minor_chip_version(self):
num_word = 1
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 16) & 0xF
def get_major_chip_version(self):
num_word = 1
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 20) & 0x3
def get_crystal_freq(self):
# The crystal detection algorithm of ESP32/ESP8266 works for ESP32-C2 as well.
return ESPLoader.get_crystal_freq(self)
def change_baud(self, baud):
rom_with_26M_XTAL = not self.IS_STUB and self.get_crystal_freq() == 26
if rom_with_26M_XTAL:
# The code is copied over from ESPLoader.change_baud().
# Probably this is just a temporary solution until the next chip revision.
# The ROM code thinks it uses a 40 MHz XTAL. Recompute the baud rate
# in order to trick the ROM code to set the correct baud rate for
# a 26 MHz XTAL.
false_rom_baud = baud * 40 // 26
print(f"Changing baud rate to {baud}")
self.command(
self.ESP_CHANGE_BAUDRATE, struct.pack("<II", false_rom_baud, 0)
)
print("Changed.")
self._set_port_baudrate(baud)
time.sleep(0.05) # get rid of garbage sent during baud rate change
self.flush_input()
else:
ESPLoader.change_baud(self, baud)
def _post_connect(self):
# ESP32C2 ECO0 is no longer supported by the flasher stub
if self.get_chip_revision() == 0:
self.stub_is_disabled = True
self.IS_STUB = False
""" Try to read (encryption key) and check if it is valid """
def is_flash_encryption_key_valid(self):
key_len_256 = (
self.read_reg(self.EFUSE_XTS_KEY_LENGTH_256_REG)
& self.EFUSE_XTS_KEY_LENGTH_256
)
word0 = self.read_reg(self.EFUSE_RD_DIS_REG) & self.EFUSE_RD_DIS
rd_disable = word0 == 3 if key_len_256 else word0 == 1
# reading of BLOCK3 is NOT ALLOWED so we assume valid key is programmed
if rd_disable:
return True
else:
# reading of BLOCK3 is ALLOWED so we will read and verify for non-zero.
# When chip has not generated AES/encryption key in BLOCK3,
# the contents will be readable and 0.
# If the flash encryption is enabled it is expected to have a valid
# non-zero key. We break out on first occurance of non-zero value
key_word = [0] * 7 if key_len_256 else [0] * 3
for i in range(len(key_word)):
key_word[i] = self.read_reg(self.EFUSE_BLOCK_KEY0_REG + i * 4)
# key is non-zero so break & return
if key_word[i] != 0:
return True
return False
class ESP32C2StubLoader(ESP32C2ROM):
"""Access class for ESP32C2 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32C2ROM.STUB_CLASS = ESP32C2StubLoader
@@ -1,210 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import struct
from .esp32 import ESP32ROM
from ..loader import ESPLoader
from ..util import FatalError, NotImplementedInROMError
class ESP32C3ROM(ESP32ROM):
CHIP_NAME = "ESP32-C3"
IMAGE_CHIP_ID = 5
FPGA_SLOW_BOOT = False
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42800000
DROM_MAP_START = 0x3C000000
DROM_MAP_END = 0x3C800000
SPI_REG_BASE = 0x60002000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
BOOTLOADER_FLASH_OFFSET = 0x0
# Magic value for ESP32C3 eco 1+2 and ESP32C3 eco3 respectivly
CHIP_DETECT_MAGIC_VALUE = [0x6921506F, 0x1B31506F]
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
UART_CLKDIV_REG = 0x60000014
EFUSE_BASE = 0x60008800
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
MAC_EFUSE_REG = EFUSE_BASE + 0x044
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
PURPOSE_VAL_XTS_AES128_KEY = 4
SUPPORTS_ENCRYPTED_FLASH = True
FLASH_ENCRYPTED_WRITE_ALIGN = 16
UARTDEV_BUF_NO = 0x3FCDF07C # Variable in ROM .bss which indicates the port in use
UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used
RTC_CNTL_WDT_WKEY = 0x50D83AA1
RTCCNTL_BASE_REG = 0x60008000
RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0090
RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00A8
MEMORY_MAP = [
[0x00000000, 0x00010000, "PADDING"],
[0x3C000000, 0x3C800000, "DROM"],
[0x3FC80000, 0x3FCE0000, "DRAM"],
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
[0x3FF00000, 0x3FF20000, "DROM_MASK"],
[0x40000000, 0x40060000, "IROM_MASK"],
[0x42000000, 0x42800000, "IROM"],
[0x4037C000, 0x403E0000, "IRAM"],
[0x50000000, 0x50002000, "RTC_IRAM"],
[0x50000000, 0x50002000, "RTC_DRAM"],
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
]
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
def get_minor_chip_version(self):
hi_num_word = 5
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
low_num_word = 3
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
return (hi << 3) + low
def get_major_chip_version(self):
num_word = 5
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
def get_chip_description(self):
chip_name = {
0: "ESP32-C3",
}.get(self.get_pkg_version(), "unknown ESP32-C3")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
return ["WiFi", "BLE"]
def get_crystal_freq(self):
# ESP32C3 XTAL is fixed to 40MHz
return 40
def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"VDD_SDIO overrides are not supported for ESP32-C3"
)
def read_mac(self):
mac0 = self.read_reg(self.MAC_EFUSE_REG)
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
bitstring = struct.pack(">II", mac1, mac0)[2:]
return tuple(bitstring)
def get_flash_crypt_config(self):
return None # doesn't exist on ESP32-C3
def get_secure_boot_enabled(self):
return (
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
& self.EFUSE_SECURE_BOOT_EN_MASK
)
def get_key_block_purpose(self, key_block):
if key_block < 0 or key_block > 5:
raise FatalError("Valid key block numbers must be in range 0-5")
reg, shift = [
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
][key_block]
return (self.read_reg(reg) >> shift) & 0xF
def is_flash_encryption_key_valid(self):
# Need to see an AES-128 key
purposes = [self.get_key_block_purpose(b) for b in range(6)]
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
def change_baud(self, baud):
ESPLoader.change_baud(self, baud)
def uses_usb_jtag_serial(self):
"""
Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used
"""
if self.secure_download_mode:
return False # Can't detect USB-JTAG/Serial in secure download mode
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL
def disable_rtc_watchdog(self):
# When USB-JTAG/Serial is used, the RTC watchdog is not reset
# and can then reset the board during flashing. Disable it.
if self.uses_usb_jtag_serial():
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY)
self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0)
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0)
def _post_connect(self):
if not self.sync_stub_detected: # Don't run if stub is reused
self.disable_rtc_watchdog()
class ESP32C3StubLoader(ESP32C3ROM):
"""Access class for ESP32C3 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32C3ROM.STUB_CLASS = ESP32C3StubLoader
@@ -1,189 +0,0 @@
# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import struct
from .esp32c3 import ESP32C3ROM
from ..util import FatalError, NotImplementedInROMError
class ESP32C6ROM(ESP32C3ROM):
CHIP_NAME = "ESP32-C6"
IMAGE_CHIP_ID = 13
FPGA_SLOW_BOOT = False
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42800000
DROM_MAP_START = 0x42800000
DROM_MAP_END = 0x43000000
BOOTLOADER_FLASH_OFFSET = 0x0
# Magic value for ESP32C6
CHIP_DETECT_MAGIC_VALUE = [0x2CE0806F]
SPI_REG_BASE = 0x60003000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
EFUSE_BASE = 0x600B0800
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
MAC_EFUSE_REG = EFUSE_BASE + 0x044
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
PURPOSE_VAL_XTS_AES128_KEY = 4
SUPPORTS_ENCRYPTED_FLASH = True
FLASH_ENCRYPTED_WRITE_ALIGN = 16
UARTDEV_BUF_NO = 0x4087F580 # Variable in ROM .bss which indicates the port in use
UARTDEV_BUF_NO_USB_JTAG_SERIAL = 3 # The above var when USB-JTAG/Serial is used
DR_REG_LP_WDT_BASE = 0x600B1C00
RTC_CNTL_WDTCONFIG0_REG = DR_REG_LP_WDT_BASE + 0x0 # LP_WDT_RWDT_CONFIG0_REG
RTC_CNTL_WDTWPROTECT_REG = DR_REG_LP_WDT_BASE + 0x0018 # LP_WDT_RWDT_WPROTECT_REG
FLASH_FREQUENCY = {
"80m": 0x0, # workaround for wrong mspi HS div value in ROM
"40m": 0x0,
"20m": 0x2,
}
MEMORY_MAP = [
[0x00000000, 0x00010000, "PADDING"],
[0x42800000, 0x43000000, "DROM"],
[0x40800000, 0x40880000, "DRAM"],
[0x40800000, 0x40880000, "BYTE_ACCESSIBLE"],
[0x4004AC00, 0x40050000, "DROM_MASK"],
[0x40000000, 0x4004AC00, "IROM_MASK"],
[0x42000000, 0x42800000, "IROM"],
[0x40800000, 0x40880000, "IRAM"],
[0x50000000, 0x50004000, "RTC_IRAM"],
[0x50000000, 0x50004000, "RTC_DRAM"],
[0x600FE000, 0x60100000, "MEM_INTERNAL2"],
]
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
def get_minor_chip_version(self):
hi_num_word = 5
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
low_num_word = 3
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
return (hi << 3) + low
def get_major_chip_version(self):
num_word = 5
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
def get_chip_description(self):
chip_name = {
0: "ESP32-C6",
}.get(self.get_pkg_version(), "unknown ESP32-C6")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
return ["WiFi 6", "BT 5", "IEEE802.15.4"]
def get_crystal_freq(self):
# ESP32C6 XTAL is fixed to 40MHz
return 40
def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"VDD_SDIO overrides are not supported for ESP32-C6"
)
def read_mac(self):
mac0 = self.read_reg(self.MAC_EFUSE_REG)
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
bitstring = struct.pack(">II", mac1, mac0)[2:]
return tuple(bitstring)
def get_flash_crypt_config(self):
return None # doesn't exist on ESP32-C6
def get_secure_boot_enabled(self):
return (
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
& self.EFUSE_SECURE_BOOT_EN_MASK
)
def get_key_block_purpose(self, key_block):
if key_block < 0 or key_block > 5:
raise FatalError("Valid key block numbers must be in range 0-5")
reg, shift = [
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
][key_block]
return (self.read_reg(reg) >> shift) & 0xF
def is_flash_encryption_key_valid(self):
# Need to see an AES-128 key
purposes = [self.get_key_block_purpose(b) for b in range(6)]
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
class ESP32C6StubLoader(ESP32C6ROM):
"""Access class for ESP32C6 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32C6ROM.STUB_CLASS = ESP32C6StubLoader
@@ -1,26 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from .esp32c3 import ESP32C3ROM
class ESP32C6BETAROM(ESP32C3ROM):
CHIP_NAME = "ESP32-C6(beta)"
IMAGE_CHIP_ID = 7
CHIP_DETECT_MAGIC_VALUE = [0x0DA1806F]
UART_DATE_REG_ADDR = 0x00000500
def get_chip_description(self):
chip_name = {
0: "ESP32-C6",
}.get(self.get_pkg_version(), "unknown ESP32-C6")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def _post_connect(self):
pass
@@ -1,65 +0,0 @@
# SPDX-FileCopyrightText: 2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from .esp32c6 import ESP32C6ROM
class ESP32H2ROM(ESP32C6ROM):
CHIP_NAME = "ESP32-H2"
IMAGE_CHIP_ID = 16
# Magic value for ESP32H2
CHIP_DETECT_MAGIC_VALUE = [0xD7B73E80]
FLASH_FREQUENCY = {
"48m": 0xF,
"24m": 0x0,
"16m": 0x1,
"12m": 0x2,
}
def get_pkg_version(self):
num_word = 3
block1_addr = self.EFUSE_BASE + 0x044
word3 = self.read_reg(block1_addr + (4 * num_word))
pkg_version = (word3 >> 21) & 0x0F
return pkg_version
def get_chip_description(self):
chip_name = {
0: "ESP32-H2",
}.get(self.get_pkg_version(), "unknown ESP32-H2")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
return ["BLE"]
def get_crystal_freq(self):
# ESP32H2 XTAL is fixed to 32MHz
return 32
class ESP32H2StubLoader(ESP32H2ROM):
"""Access class for ESP32H2 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32H2ROM.STUB_CLASS = ESP32H2StubLoader
@@ -1,164 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import struct
from .esp32c3 import ESP32C3ROM
from ..util import FatalError, NotImplementedInROMError
class ESP32H2BETA1ROM(ESP32C3ROM):
CHIP_NAME = "ESP32-H2(beta1)"
IMAGE_CHIP_ID = 10
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x42800000
DROM_MAP_START = 0x3C000000
DROM_MAP_END = 0x3C800000
SPI_REG_BASE = 0x60002000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
BOOTLOADER_FLASH_OFFSET = 0x0
CHIP_DETECT_MAGIC_VALUE = [0xCA26CC22]
UART_DATE_REG_ADDR = 0x60000000 + 0x7C
EFUSE_BASE = 0x6001A000
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
MAC_EFUSE_REG = EFUSE_BASE + 0x044
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
PURPOSE_VAL_XTS_AES128_KEY = 4
SUPPORTS_ENCRYPTED_FLASH = True
FLASH_ENCRYPTED_WRITE_ALIGN = 16
MEMORY_MAP = []
FLASH_FREQUENCY = {
"48m": 0xF,
"24m": 0x0,
"16m": 0x1,
"12m": 0x2,
}
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x0F
def get_minor_chip_version(self):
hi_num_word = 5
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
low_num_word = 3
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
return (hi << 3) + low
def get_major_chip_version(self):
num_word = 5
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
def get_chip_description(self):
chip_name = {
0: "ESP32-H2",
}.get(self.get_pkg_version(), "unknown ESP32-H2")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
return ["BLE", "IEEE802.15.4"]
def get_crystal_freq(self):
return 32
def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"VDD_SDIO overrides are not supported for ESP32-H2"
)
def read_mac(self):
mac0 = self.read_reg(self.MAC_EFUSE_REG)
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
bitstring = struct.pack(">II", mac1, mac0)[2:]
return tuple(bitstring)
def get_flash_crypt_config(self):
return None # doesn't exist on ESP32-H2
def get_key_block_purpose(self, key_block):
if key_block < 0 or key_block > 5:
raise FatalError("Valid key block numbers must be in range 0-5")
reg, shift = [
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
][key_block]
return (self.read_reg(reg) >> shift) & 0xF
def is_flash_encryption_key_valid(self):
# Need to see an AES-128 key
purposes = [self.get_key_block_purpose(b) for b in range(6)]
return any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes)
def _post_connect(self):
pass
class ESP32H2BETA1StubLoader(ESP32H2BETA1ROM):
"""Access class for ESP32H2BETA1 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32H2BETA1ROM.STUB_CLASS = ESP32H2BETA1StubLoader
@@ -1,43 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from .esp32h2beta1 import ESP32H2BETA1ROM
class ESP32H2BETA2ROM(ESP32H2BETA1ROM):
CHIP_NAME = "ESP32-H2(beta2)"
IMAGE_CHIP_ID = 14
CHIP_DETECT_MAGIC_VALUE = [0x6881B06F]
def get_chip_description(self):
chip_name = {
1: "ESP32-H2(beta2)",
}.get(self.get_pkg_version(), "unknown ESP32-H2")
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
class ESP32H2BETA2StubLoader(ESP32H2BETA2ROM):
"""Access class for ESP32H2BETA2 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32H2BETA2ROM.STUB_CLASS = ESP32H2BETA2StubLoader
@@ -1,305 +0,0 @@
# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import struct
from .esp32 import ESP32ROM
from ..loader import ESPLoader
from ..reset import HardReset
from ..util import FatalError, NotImplementedInROMError
class ESP32S2ROM(ESP32ROM):
CHIP_NAME = "ESP32-S2"
IMAGE_CHIP_ID = 2
FPGA_SLOW_BOOT = False
IROM_MAP_START = 0x40080000
IROM_MAP_END = 0x40B80000
DROM_MAP_START = 0x3F000000
DROM_MAP_END = 0x3F3F0000
CHIP_DETECT_MAGIC_VALUE = [0x000007C6]
SPI_REG_BASE = 0x3F402000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
MAC_EFUSE_REG = 0x3F41A044 # ESP32-S2 has special block for MAC efuses
UART_CLKDIV_REG = 0x3F400014
SUPPORTS_ENCRYPTED_FLASH = True
FLASH_ENCRYPTED_WRITE_ALIGN = 16
# todo: use espefuse APIs to get this info
EFUSE_BASE = 0x3F41A000
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x044
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x05C
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 19
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
EFUSE_RD_REPEAT_DATA3_REG = EFUSE_BASE + 0x3C
EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK = 1 << 9
PURPOSE_VAL_XTS_AES256_KEY_1 = 2
PURPOSE_VAL_XTS_AES256_KEY_2 = 3
PURPOSE_VAL_XTS_AES128_KEY = 4
UARTDEV_BUF_NO = 0x3FFFFD14 # Variable in ROM .bss which indicates the port in use
UARTDEV_BUF_NO_USB_OTG = 2 # Value of the above indicating that USB-OTG is in use
USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used
GPIO_STRAP_REG = 0x3F404038
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
RTC_CNTL_OPTION1_REG = 0x3F408128
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
MEMORY_MAP = [
[0x00000000, 0x00010000, "PADDING"],
[0x3F000000, 0x3FF80000, "DROM"],
[0x3F500000, 0x3FF80000, "EXTRAM_DATA"],
[0x3FF9E000, 0x3FFA0000, "RTC_DRAM"],
[0x3FF9E000, 0x40000000, "BYTE_ACCESSIBLE"],
[0x3FF9E000, 0x40072000, "MEM_INTERNAL"],
[0x3FFB0000, 0x40000000, "DRAM"],
[0x40000000, 0x4001A100, "IROM_MASK"],
[0x40020000, 0x40070000, "IRAM"],
[0x40070000, 0x40072000, "RTC_IRAM"],
[0x40080000, 0x40800000, "IROM"],
[0x50000000, 0x50002000, "RTC_DATA"],
]
def get_pkg_version(self):
num_word = 4
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 0) & 0x0F
def get_minor_chip_version(self):
hi_num_word = 3
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 20) & 0x01
low_num_word = 4
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 4) & 0x07
return (hi << 3) + low
def get_major_chip_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 18) & 0x03
def get_flash_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x0F
def get_psram_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 28) & 0x0F
def get_block2_version(self):
# BLK_VERSION_MINOR
num_word = 4
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 4) & 0x07
def get_chip_description(self):
chip_name = {
0: "ESP32-S2",
1: "ESP32-S2FH2",
2: "ESP32-S2FH4",
102: "ESP32-S2FNR2",
100: "ESP32-S2R2",
}.get(
self.get_flash_version() + self.get_psram_version() * 100,
"unknown ESP32-S2",
)
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{chip_name} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
features = ["WiFi"]
if self.secure_download_mode:
features += ["Secure Download Mode Enabled"]
flash_version = {
0: "No Embedded Flash",
1: "Embedded Flash 2MB",
2: "Embedded Flash 4MB",
}.get(self.get_flash_version(), "Unknown Embedded Flash")
features += [flash_version]
psram_version = {
0: "No Embedded PSRAM",
1: "Embedded PSRAM 2MB",
2: "Embedded PSRAM 4MB",
}.get(self.get_psram_version(), "Unknown Embedded PSRAM")
features += [psram_version]
block2_version = {
0: "No calibration in BLK2 of efuse",
1: "ADC and temperature sensor calibration in BLK2 of efuse V1",
2: "ADC and temperature sensor calibration in BLK2 of efuse V2",
}.get(self.get_block2_version(), "Unknown Calibration in BLK2")
features += [block2_version]
return features
def get_crystal_freq(self):
# ESP32-S2 XTAL is fixed to 40MHz
return 40
def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"VDD_SDIO overrides are not supported for ESP32-S2"
)
def read_mac(self):
mac0 = self.read_reg(self.MAC_EFUSE_REG)
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
bitstring = struct.pack(">II", mac1, mac0)[2:]
return tuple(bitstring)
def flash_type(self):
return (
1
if self.read_reg(self.EFUSE_RD_REPEAT_DATA3_REG)
& self.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK
else 0
)
def get_flash_crypt_config(self):
return None # doesn't exist on ESP32-S2
def get_secure_boot_enabled(self):
return (
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
& self.EFUSE_SECURE_BOOT_EN_MASK
)
def get_key_block_purpose(self, key_block):
if key_block < 0 or key_block > 5:
raise FatalError("Valid key block numbers must be in range 0-5")
reg, shift = [
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
][key_block]
return (self.read_reg(reg) >> shift) & 0xF
def is_flash_encryption_key_valid(self):
# Need to see either an AES-128 key or two AES-256 keys
purposes = [self.get_key_block_purpose(b) for b in range(6)]
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
return True
return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any(
p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes
)
def uses_usb_otg(self):
"""
Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used
"""
if self.secure_download_mode:
return False # can't detect native USB in secure download mode
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG
def _post_connect(self):
if self.uses_usb_otg():
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
def _check_if_can_reset(self):
"""
Check the strapping register to see if we can reset out of download mode.
"""
if os.getenv("ESPTOOL_TESTING") is not None:
print("ESPTOOL_TESTING is set, ignoring strapping mode check")
# Esptool tests over USB-OTG run with GPIO0 strapped low,
# don't complain in this case.
return
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG)
if (
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
):
print(
"WARNING: {} chip was placed into download mode using GPIO0.\n"
"esptool.py can not exit the download mode over USB. "
"To run the app, reset the chip manually.\n"
"To suppress this note, set --after option to 'no_reset'.".format(
self.get_chip_description()
)
)
raise SystemExit(1)
def hard_reset(self):
uses_usb_otg = self.uses_usb_otg()
if uses_usb_otg:
self._check_if_can_reset()
print("Hard resetting via RTS pin...")
HardReset(self._port, uses_usb_otg)()
def change_baud(self, baud):
ESPLoader.change_baud(self, baud)
class ESP32S2StubLoader(ESP32S2ROM):
"""Access class for ESP32-S2 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
if rom_loader.uses_usb_otg():
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK
ESP32S2ROM.STUB_CLASS = ESP32S2StubLoader
@@ -1,315 +0,0 @@
# SPDX-FileCopyrightText: 2014-2023 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import os
import struct
from .esp32 import ESP32ROM
from ..loader import ESPLoader
from ..reset import HardReset
from ..util import FatalError, NotImplementedInROMError
class ESP32S3ROM(ESP32ROM):
CHIP_NAME = "ESP32-S3"
IMAGE_CHIP_ID = 9
CHIP_DETECT_MAGIC_VALUE = [0x9]
FPGA_SLOW_BOOT = False
IROM_MAP_START = 0x42000000
IROM_MAP_END = 0x44000000
DROM_MAP_START = 0x3C000000
DROM_MAP_END = 0x3E000000
UART_DATE_REG_ADDR = 0x60000080
SPI_REG_BASE = 0x60002000
SPI_USR_OFFS = 0x18
SPI_USR1_OFFS = 0x1C
SPI_USR2_OFFS = 0x20
SPI_MOSI_DLEN_OFFS = 0x24
SPI_MISO_DLEN_OFFS = 0x28
SPI_W0_OFFS = 0x58
BOOTLOADER_FLASH_OFFSET = 0x0
SUPPORTS_ENCRYPTED_FLASH = True
FLASH_ENCRYPTED_WRITE_ALIGN = 16
# todo: use espefuse APIs to get this info
EFUSE_BASE = 0x60007000 # BLOCK0 read base address
EFUSE_BLOCK1_ADDR = EFUSE_BASE + 0x44
EFUSE_BLOCK2_ADDR = EFUSE_BASE + 0x5C
MAC_EFUSE_REG = EFUSE_BASE + 0x044
EFUSE_RD_REG_BASE = EFUSE_BASE + 0x030 # BLOCK0 read base address
EFUSE_PURPOSE_KEY0_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY0_SHIFT = 24
EFUSE_PURPOSE_KEY1_REG = EFUSE_BASE + 0x34
EFUSE_PURPOSE_KEY1_SHIFT = 28
EFUSE_PURPOSE_KEY2_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY2_SHIFT = 0
EFUSE_PURPOSE_KEY3_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY3_SHIFT = 4
EFUSE_PURPOSE_KEY4_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY4_SHIFT = 8
EFUSE_PURPOSE_KEY5_REG = EFUSE_BASE + 0x38
EFUSE_PURPOSE_KEY5_SHIFT = 12
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT_REG = EFUSE_RD_REG_BASE
EFUSE_DIS_DOWNLOAD_MANUAL_ENCRYPT = 1 << 20
EFUSE_SPI_BOOT_CRYPT_CNT_REG = EFUSE_BASE + 0x034
EFUSE_SPI_BOOT_CRYPT_CNT_MASK = 0x7 << 18
EFUSE_SECURE_BOOT_EN_REG = EFUSE_BASE + 0x038
EFUSE_SECURE_BOOT_EN_MASK = 1 << 20
EFUSE_RD_REPEAT_DATA3_REG = EFUSE_BASE + 0x3C
EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK = 1 << 9
PURPOSE_VAL_XTS_AES256_KEY_1 = 2
PURPOSE_VAL_XTS_AES256_KEY_2 = 3
PURPOSE_VAL_XTS_AES128_KEY = 4
UARTDEV_BUF_NO = 0x3FCEF14C # Variable in ROM .bss which indicates the port in use
UARTDEV_BUF_NO_USB_OTG = 3 # The above var when USB-OTG is used
UARTDEV_BUF_NO_USB_JTAG_SERIAL = 4 # The above var when USB-JTAG/Serial is used
RTC_CNTL_WDT_WKEY = 0x50D83AA1
RTCCNTL_BASE_REG = 0x60008000
RTC_CNTL_WDTCONFIG0_REG = RTCCNTL_BASE_REG + 0x0090
RTC_CNTL_WDTWPROTECT_REG = RTCCNTL_BASE_REG + 0x00B0
USB_RAM_BLOCK = 0x800 # Max block size USB-OTG is used
GPIO_STRAP_REG = 0x60004038
GPIO_STRAP_SPI_BOOT_MASK = 0x8 # Not download mode
RTC_CNTL_OPTION1_REG = 0x6000812C
RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK = 0x1 # Is download mode forced over USB?
UART_CLKDIV_REG = 0x60000014
MEMORY_MAP = [
[0x00000000, 0x00010000, "PADDING"],
[0x3C000000, 0x3D000000, "DROM"],
[0x3D000000, 0x3E000000, "EXTRAM_DATA"],
[0x600FE000, 0x60100000, "RTC_DRAM"],
[0x3FC88000, 0x3FD00000, "BYTE_ACCESSIBLE"],
[0x3FC88000, 0x403E2000, "MEM_INTERNAL"],
[0x3FC88000, 0x3FD00000, "DRAM"],
[0x40000000, 0x4001A100, "IROM_MASK"],
[0x40370000, 0x403E0000, "IRAM"],
[0x600FE000, 0x60100000, "RTC_IRAM"],
[0x42000000, 0x42800000, "IROM"],
[0x50000000, 0x50002000, "RTC_DATA"],
]
def get_pkg_version(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 21) & 0x07
def is_eco0(self, minor_raw):
# Workaround: The major version field was allocated to other purposes
# when block version is v1.1.
# Luckily only chip v0.0 have this kind of block version and efuse usage.
return (
(minor_raw & 0x7) == 0
and self.get_blk_version_major() == 1
and self.get_blk_version_minor() == 1
)
def get_minor_chip_version(self):
minor_raw = self.get_raw_minor_chip_version()
if self.is_eco0(minor_raw):
return 0
return minor_raw
def get_raw_minor_chip_version(self):
hi_num_word = 5
hi = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * hi_num_word)) >> 23) & 0x01
low_num_word = 3
low = (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * low_num_word)) >> 18) & 0x07
return (hi << 3) + low
def get_blk_version_major(self):
num_word = 4
return (self.read_reg(self.EFUSE_BLOCK2_ADDR + (4 * num_word)) >> 0) & 0x03
def get_blk_version_minor(self):
num_word = 3
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x07
def get_major_chip_version(self):
minor_raw = self.get_raw_minor_chip_version()
if self.is_eco0(minor_raw):
return 0
return self.get_raw_major_chip_version()
def get_raw_major_chip_version(self):
num_word = 5
return (self.read_reg(self.EFUSE_BLOCK1_ADDR + (4 * num_word)) >> 24) & 0x03
def get_chip_description(self):
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})"
def get_chip_features(self):
return ["WiFi", "BLE"]
def get_crystal_freq(self):
# ESP32S3 XTAL is fixed to 40MHz
return 40
def get_flash_crypt_config(self):
return None # doesn't exist on ESP32-S3
def get_key_block_purpose(self, key_block):
if key_block < 0 or key_block > 5:
raise FatalError("Valid key block numbers must be in range 0-5")
reg, shift = [
(self.EFUSE_PURPOSE_KEY0_REG, self.EFUSE_PURPOSE_KEY0_SHIFT),
(self.EFUSE_PURPOSE_KEY1_REG, self.EFUSE_PURPOSE_KEY1_SHIFT),
(self.EFUSE_PURPOSE_KEY2_REG, self.EFUSE_PURPOSE_KEY2_SHIFT),
(self.EFUSE_PURPOSE_KEY3_REG, self.EFUSE_PURPOSE_KEY3_SHIFT),
(self.EFUSE_PURPOSE_KEY4_REG, self.EFUSE_PURPOSE_KEY4_SHIFT),
(self.EFUSE_PURPOSE_KEY5_REG, self.EFUSE_PURPOSE_KEY5_SHIFT),
][key_block]
return (self.read_reg(reg) >> shift) & 0xF
def is_flash_encryption_key_valid(self):
# Need to see either an AES-128 key or two AES-256 keys
purposes = [self.get_key_block_purpose(b) for b in range(6)]
if any(p == self.PURPOSE_VAL_XTS_AES128_KEY for p in purposes):
return True
return any(p == self.PURPOSE_VAL_XTS_AES256_KEY_1 for p in purposes) and any(
p == self.PURPOSE_VAL_XTS_AES256_KEY_2 for p in purposes
)
def get_secure_boot_enabled(self):
return (
self.read_reg(self.EFUSE_SECURE_BOOT_EN_REG)
& self.EFUSE_SECURE_BOOT_EN_MASK
)
def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"VDD_SDIO overrides are not supported for ESP32-S3"
)
def read_mac(self):
mac0 = self.read_reg(self.MAC_EFUSE_REG)
mac1 = self.read_reg(self.MAC_EFUSE_REG + 4) # only bottom 16 bits are MAC
bitstring = struct.pack(">II", mac1, mac0)[2:]
return tuple(bitstring)
def flash_type(self):
return (
1
if self.read_reg(self.EFUSE_RD_REPEAT_DATA3_REG)
& self.EFUSE_RD_REPEAT_DATA3_REG_FLASH_TYPE_MASK
else 0
)
def uses_usb_otg(self):
"""
Check the UARTDEV_BUF_NO register to see if USB-OTG console is being used
"""
if self.secure_download_mode:
return False # can't detect native USB in secure download mode
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_OTG
def uses_usb_jtag_serial(self):
"""
Check the UARTDEV_BUF_NO register to see if USB-JTAG/Serial is being used
"""
if self.secure_download_mode:
return False # can't detect USB-JTAG/Serial in secure download mode
return self.get_uart_no() == self.UARTDEV_BUF_NO_USB_JTAG_SERIAL
def disable_rtc_watchdog(self):
# When USB-JTAG/Serial is used, the RTC watchdog is not reset
# and can then reset the board during flashing. Disable it.
if self.uses_usb_jtag_serial():
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, self.RTC_CNTL_WDT_WKEY)
self.write_reg(self.RTC_CNTL_WDTCONFIG0_REG, 0)
self.write_reg(self.RTC_CNTL_WDTWPROTECT_REG, 0)
def _post_connect(self):
if self.uses_usb_otg():
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
if not self.sync_stub_detected: # Don't run if stub is reused
self.disable_rtc_watchdog()
def _check_if_can_reset(self):
"""
Check the strapping register to see if we can reset out of download mode.
"""
if os.getenv("ESPTOOL_TESTING") is not None:
print("ESPTOOL_TESTING is set, ignoring strapping mode check")
# Esptool tests over USB-OTG run with GPIO0 strapped low,
# don't complain in this case.
return
strap_reg = self.read_reg(self.GPIO_STRAP_REG)
force_dl_reg = self.read_reg(self.RTC_CNTL_OPTION1_REG)
if (
strap_reg & self.GPIO_STRAP_SPI_BOOT_MASK == 0
and force_dl_reg & self.RTC_CNTL_FORCE_DOWNLOAD_BOOT_MASK == 0
):
print(
"WARNING: {} chip was placed into download mode using GPIO0.\n"
"esptool.py can not exit the download mode over USB. "
"To run the app, reset the chip manually.\n"
"To suppress this note, set --after option to 'no_reset'.".format(
self.get_chip_description()
)
)
raise SystemExit(1)
def hard_reset(self):
uses_usb_otg = self.uses_usb_otg()
if uses_usb_otg:
self._check_if_can_reset()
print("Hard resetting via RTS pin...")
HardReset(self._port, uses_usb_otg)()
def change_baud(self, baud):
ESPLoader.change_baud(self, baud)
class ESP32S3StubLoader(ESP32S3ROM):
"""Access class for ESP32S3 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
if rom_loader.uses_usb_otg():
self.ESP_RAM_BLOCK = self.USB_RAM_BLOCK
self.FLASH_WRITE_SIZE = self.USB_RAM_BLOCK
ESP32S3ROM.STUB_CLASS = ESP32S3StubLoader
@@ -1,42 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from .esp32s3 import ESP32S3ROM
class ESP32S3BETA2ROM(ESP32S3ROM):
CHIP_NAME = "ESP32-S3(beta2)"
IMAGE_CHIP_ID = 4
CHIP_DETECT_MAGIC_VALUE = [0xEB004136]
EFUSE_BASE = 0x6001A000 # BLOCK0 read base address
def get_chip_description(self):
major_rev = self.get_major_chip_version()
minor_rev = self.get_minor_chip_version()
return f"{self.CHIP_NAME} (revision v{major_rev}.{minor_rev})"
class ESP32S3BETA2StubLoader(ESP32S3BETA2ROM):
"""Access class for ESP32S3 stub loader, runs on top of ROM.
(Basically the same as ESP32StubLoader, but different base class.
Can possibly be made into a mixin.)
"""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
STATUS_BYTES_LENGTH = 2 # same as ESP8266, different to ESP32 ROM
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
ESP32S3BETA2ROM.STUB_CLASS = ESP32S3BETA2StubLoader
@@ -1,191 +0,0 @@
# SPDX-FileCopyrightText: 2014-2022 Fredrik Ahlberg, Angus Gratton,
# Espressif Systems (Shanghai) CO LTD, other contributors as noted.
#
# SPDX-License-Identifier: GPL-2.0-or-later
from ..loader import ESPLoader
from ..util import FatalError, NotImplementedInROMError
class ESP8266ROM(ESPLoader):
"""Access class for ESP8266 ROM bootloader"""
CHIP_NAME = "ESP8266"
IS_STUB = False
CHIP_DETECT_MAGIC_VALUE = [0xFFF0C101]
# OTP ROM addresses
ESP_OTP_MAC0 = 0x3FF00050
ESP_OTP_MAC1 = 0x3FF00054
ESP_OTP_MAC3 = 0x3FF0005C
SPI_REG_BASE = 0x60000200
SPI_USR_OFFS = 0x1C
SPI_USR1_OFFS = 0x20
SPI_USR2_OFFS = 0x24
SPI_MOSI_DLEN_OFFS = None
SPI_MISO_DLEN_OFFS = None
SPI_W0_OFFS = 0x40
UART_CLKDIV_REG = 0x60000014
XTAL_CLK_DIVIDER = 2
FLASH_SIZES = {
"512KB": 0x00,
"256KB": 0x10,
"1MB": 0x20,
"2MB": 0x30,
"4MB": 0x40,
"2MB-c1": 0x50,
"4MB-c1": 0x60,
"8MB": 0x80,
"16MB": 0x90,
}
FLASH_FREQUENCY = {
"80m": 0xF,
"40m": 0x0,
"26m": 0x1,
"20m": 0x2,
}
BOOTLOADER_FLASH_OFFSET = 0
MEMORY_MAP = [
[0x3FF00000, 0x3FF00010, "DPORT"],
[0x3FFE8000, 0x40000000, "DRAM"],
[0x40100000, 0x40108000, "IRAM"],
[0x40201010, 0x402E1010, "IROM"],
]
def get_efuses(self):
# Return the 128 bits of ESP8266 efuse as a single Python integer
result = self.read_reg(0x3FF0005C) << 96
result |= self.read_reg(0x3FF00058) << 64
result |= self.read_reg(0x3FF00054) << 32
result |= self.read_reg(0x3FF00050)
return result
def _get_flash_size(self, efuses):
# rX_Y = EFUSE_DATA_OUTX[Y]
r0_4 = (efuses & (1 << 4)) != 0
r3_25 = (efuses & (1 << 121)) != 0
r3_26 = (efuses & (1 << 122)) != 0
r3_27 = (efuses & (1 << 123)) != 0
if r0_4 and not r3_25:
if not r3_27 and not r3_26:
return 1
elif not r3_27 and r3_26:
return 2
if not r0_4 and r3_25:
if not r3_27 and not r3_26:
return 2
elif not r3_27 and r3_26:
return 4
return -1
def get_chip_description(self):
efuses = self.get_efuses()
is_8285 = (
efuses & ((1 << 4) | 1 << 80)
) != 0 # One or the other efuse bit is set for ESP8285
if is_8285:
flash_size = self._get_flash_size(efuses)
max_temp = (
efuses & (1 << 5)
) != 0 # This efuse bit identifies the max flash temperature
chip_name = {
1: "ESP8285H08" if max_temp else "ESP8285N08",
2: "ESP8285H16" if max_temp else "ESP8285N16",
}.get(flash_size, "ESP8285")
return chip_name
return "ESP8266EX"
def get_chip_features(self):
features = ["WiFi"]
if "ESP8285" in self.get_chip_description():
features += ["Embedded Flash"]
return features
def flash_spi_attach(self, hspi_arg):
if self.IS_STUB:
super(ESP8266ROM, self).flash_spi_attach(hspi_arg)
else:
# ESP8266 ROM has no flash_spi_attach command in serial protocol,
# but flash_begin will do it
self.flash_begin(0, 0)
def flash_set_parameters(self, size):
# not implemented in ROM, but OK to silently skip for ROM
if self.IS_STUB:
super(ESP8266ROM, self).flash_set_parameters(size)
def chip_id(self):
"""
Read Chip ID from efuse - the equivalent of the SDK system_get_chip_id() func
"""
id0 = self.read_reg(self.ESP_OTP_MAC0)
id1 = self.read_reg(self.ESP_OTP_MAC1)
return (id0 >> 24) | ((id1 & 0xFFFFFF) << 8)
def read_mac(self):
"""Read MAC from OTP ROM"""
mac0 = self.read_reg(self.ESP_OTP_MAC0)
mac1 = self.read_reg(self.ESP_OTP_MAC1)
mac3 = self.read_reg(self.ESP_OTP_MAC3)
if mac3 != 0:
oui = ((mac3 >> 16) & 0xFF, (mac3 >> 8) & 0xFF, mac3 & 0xFF)
elif ((mac1 >> 16) & 0xFF) == 0:
oui = (0x18, 0xFE, 0x34)
elif ((mac1 >> 16) & 0xFF) == 1:
oui = (0xAC, 0xD0, 0x74)
else:
raise FatalError("Unknown OUI")
return oui + ((mac1 >> 8) & 0xFF, mac1 & 0xFF, (mac0 >> 24) & 0xFF)
def get_erase_size(self, offset, size):
"""Calculate an erase size given a specific size in bytes.
Provides a workaround for the bootloader erase bug."""
sectors_per_block = 16
sector_size = self.FLASH_SECTOR_SIZE
num_sectors = (size + sector_size - 1) // sector_size
start_sector = offset // sector_size
head_sectors = sectors_per_block - (start_sector % sectors_per_block)
if num_sectors < head_sectors:
head_sectors = num_sectors
if num_sectors < 2 * head_sectors:
return (num_sectors + 1) // 2 * sector_size
else:
return (num_sectors - head_sectors) * sector_size
def override_vddsdio(self, new_voltage):
raise NotImplementedInROMError(
"Overriding VDDSDIO setting only applies to ESP32"
)
class ESP8266StubLoader(ESP8266ROM):
"""Access class for ESP8266 stub loader, runs on top of ROM."""
FLASH_WRITE_SIZE = 0x4000 # matches MAX_WRITE_BLOCK in stub_loader.c
IS_STUB = True
def __init__(self, rom_loader):
self.secure_download_mode = rom_loader.secure_download_mode
self._port = rom_loader._port
self._trace_enabled = rom_loader._trace_enabled
self.cache = rom_loader.cache
self.flush_input() # resets _slip_reader
def get_erase_size(self, offset, size):
return size # stub doesn't have same size bug as ROM loader
ESP8266ROM.STUB_CLASS = ESP8266StubLoader
@@ -1,7 +0,0 @@
{
"entry": 1074521560,
"text": "CAD0PxwA9D8AAPQ/AMD8PxAA9D82QQAh+v/AIAA4AkH5/8AgACgEICB0nOIGBQAAAEH1/4H2/8AgAKgEiAigoHTgCAALImYC54b0/yHx/8AgADkCHfAAAKDr/T8Ya/0/hIAAAEBAAABYq/0/pOv9PzZBALH5/yCgdBARIKXHAJYaBoH2/5KhAZCZEZqYwCAAuAmR8/+goHSaiMAgAJIYAJCQ9BvJwMD0wCAAwlgAmpvAIACiSQDAIACSGACB6v+QkPSAgPSHmUeB5f+SoQGQmRGamMAgAMgJoeX/seP/h5wXxgEAfOiHGt7GCADAIACJCsAgALkJRgIAwCAAuQrAIACJCZHX/5qIDAnAIACSWAAd8AAA+CD0P/gw9D82QQCR/f/AIACICYCAJFZI/5H6/8AgAIgJgIAkVkj/HfAAAAAQIPQ/ACD0PwAAAAg2QQAQESCl/P8h+v8MCMAgAIJiAJH6/4H4/8AgAJJoAMAgAJgIVnn/wCAAiAJ88oAiMCAgBB3wAAAAAEA2QQAQESDl+/8Wav+B7P+R+//AIACSaADAIACYCFZ5/x3wAAAMwPw/////AAQg9D82QQAh/P84QhaDBhARIGX4/xb6BQz4DAQ3qA2YIoCZEIKgAZBIg0BAdBARICX6/xARICXz/4giDBtAmBGQqwHMFICrAbHt/7CZELHs/8AgAJJrAJHO/8AgAKJpAMAgAKgJVnr/HAkMGkCag5AzwJqIOUKJIh3wAAAskgBANkEAoqDAgf3/4AgAHfAAADZBAIKgwK0Ch5IRoqDbgff/4AgAoqDcRgQAAAAAgqDbh5IIgfL/4AgAoqDdgfD/4AgAHfA2QQA6MsYCAACiAgAbIhARIKX7/zeS8R3wAAAAfNoFQNguBkCc2gVAHNsFQDYhIaLREIH6/+AIAEYLAAAADBRARBFAQ2PNBL0BrQKB9f/gCACgoHT8Ws0EELEgotEQgfH/4AgASiJAM8BWA/0iogsQIrAgoiCy0RCB7P/gCACtAhwLEBEgpff/LQOGAAAioGMd8AAA/GcAQNCSAEAIaABANkEhYqEHwGYRGmZZBiwKYtEQDAVSZhqB9//gCAAMGECIEUe4AkZFAK0GgdT/4AgAhjQAAJKkHVBzwOCZERqZQHdjiQnNB70BIKIggc3/4AgAkqQd4JkRGpmgoHSICYyqDAiCZhZ9CIYWAAAAkqQd4JkREJmAgmkAEBEgJer/vQetARARIKXt/xARICXp/80HELEgYKYggbv/4AgAkqQd4JkRGpmICXAigHBVgDe1sJKhB8CZERqZmAmAdcCXtwJG3P+G5v8MCIJGbKKkGxCqoIHK/+AIAFYK/7KiC6IGbBC7sBARIKWPAPfqEvZHD7KiDRC7sHq7oksAG3eG8f9867eawWZHCIImGje4Aoe1nCKiCxAisGC2IK0CgZv/4AgAEBEgpd//rQIcCxARICXj/xARIKXe/ywKgbH/4AgAHfAIIPQ/cOL6P0gkBkDwIgZANmEAEBEg5cr/EKEggfv/4AgAPQoMEvwqiAGSogCQiBCJARARIKXP/5Hy/6CiAcAgAIIpAKCIIMAgAIJpALIhAKHt/4Hu/+AIAKAjgx3wAAD/DwAANkEAgTv/DBmSSAAwnEGZKJH7/zkYKTgwMLSaIiozMDxBDAIpWDlIEBEgJfj/LQqMGiKgxR3wAABQLQZANkEAQSz/WDRQM2MWYwRYFFpTUFxBRgEAEBEgZcr/iESmGASIJIel7xARIKXC/xZq/6gUzQO9AoHx/+AIAKCgdIxKUqDEUmQFWBQ6VVkUWDQwVcBZNB3wAADA/D9PSEFJqOv9P3DgC0AU4AtADAD0PzhA9D///wAAjIAAABBAAACs6/0/vOv9PwTA/D8IwPw/BOz9PxQA9D/w//8AqOv9Pxjr/D8kwPw/fGgAQOxnAEBYhgBAbCoGQDgyBkAULAZAzCwGQEwsBkA0hQBAzJAAQHguBkAw7wVAWJIAQEyCAEA2wQAh3v8MCiJhCEKgAIHu/+AIACHZ/zHa/8YAAEkCSyI3MvgQESBlw/8MS6LBIBARIOXG/yKhARARICXC/1GR/pAiESolMc//sc//wCAAWQIheP4MDAxaMmIAgdz/4AgAMcr/QqEBwCAAKAMsCkAiIMAgACkDgTH/4AgAgdX/4AgAIcP/wCAAKALMuhzDMCIQIsL4DBMgo4MMC4HO/+AIAPG8/wwdwqABDBvioQBA3REAzBGAuwGioACBx//gCAAhtv8MBCpVIcP+ctIrwCAAKAUWcv/AIAA4BQwSwCAASQUiQRAiAwEMKCJBEYJRCUlRJpIHHDiHEh4GCAAiAwOCAwKAIhGAIiBmQhEoI8AgACgCKVFGAQAAHCIiUQkQESCls/8Mi6LBEBARIGW3/4IDAyIDAoCIESCIICGY/yAg9IeyHKKgwBARICWy/6Kg7hARIKWx/xARICWw/4bb/wAAIgMBHDknOTT2IhjG1AAAACLCLyAgdPZCcJGJ/5AioCgCoAIAIsL+ICB0HBknuQLGywCRhP+QIqAoAqACAJLCMJCQdLZZyQbGACxKbQQioMCnGAIGxABJUQxyrQQQESDlqv+tBBARIGWq/xARIOWo/xARIKWo/wyLosEQIsL/EBEg5av/ViL9RikADBJWyCyCYQ+Bev/gCACI8aAog8auACaIBAwSxqwAmCNoM2CJIICAtFbY/pnBEBEgZcf/mMFqKZwqBvf/AACgrEGBbf/gCABW6vxi1vBgosDMJgaBAACgkPRWGf6GBACgoPWZwYFl/+AIAJjBVpr6kGbADBkAmRFgosBnOeEGBAAAAKCsQYFc/+AIAFaq+GLW8GCiwFam/sZvAABtBCKgwCaIAoaNAG0EDALGiwAAACa484ZhAAwSJrgCBoUAuDOoIxARIOWh/6AkgwaBAAwcZrhTiEMgrBFtBCKgwoe6AoZ+ALhTqCPJ4RARIOXA/8YLAAwcZrgviEMgrBFtBCKgwoe6AoZ1ACgzuFOoIyBogsnhEBEgZb7/ITT+SWIi0itpIsjhoMSDLQyGaQChL/5tBLIKACKgxhY7GpgjgsjwIqDAh5kBKFoMCaKg70YCAJqzsgsYG5mwqjCHKfKCAwWSAwSAiBGQiCCSAwZtBACZEYCZIIIDB4CIAZCIIICqwIKgwaAok0ZVAIEY/m0EoggAIqDGFnoUqDgioMhW+hMoWKJIAMZNAByKbQQMEqcYAsZKAPhz6GPYU8hDuDOoI4EM/+AIAG0KoCSDRkQAAAwSJkgCRj8AqCO9BIEE/+AIAAYeAICwNG0EIqDAVgsPgGRBi8N8/UYOAKg8ucHJ4dnRgQD/4AgAyOG4wSgsmByoDNIhDZCSECYCDsAgAOIqACAtMOAiECCZIMAgAJkKG7vCzBBnO8LGm/9mSAJGmv9tBCKgwAYmAAwSJrgCRiEAIdz+mFOII5kCIdv+iQItBIYcAGHX/gwb2AaCyPCtBC0EgCuT0KuDIKoQbQQioMZW6gXB0f4ioMnoDIc+U4DwFCKgwFavBC0KRgIAKqOoaksiqQmtCyD+wCqdhzLtFprfIcT++QyZAsZ7/wwSZogWIcH+iAIWKACCoMhJAiG9/kkCDBKAJINtBEYBAABtBCKg/yCgdBARIOV5/2CgdBARIGV5/xARIOV3/1aiviIDARwoJzge9jICBvf+IsL9ICB0DPgnuAKG8/6BrP6AIqAoAqACAIKg0ocSUoKg1IcSegbt/gAAAIgzoqJxwKoRaCOJ8YGw/uAIACGh/pGi/sAgACgCiPEgNDXAIhGQIhAgIyCAIoKtBGCywoGn/uAIAKKj6IGk/uAIAAbb/gAA2FPIQ7gzqCMQESAlff9G1v4AsgMDIgMCgLsRILsgssvwosMYEBEgZZn/Rs/+ACIDA4IDAmGP/YAiEZg2gCIgIsLwkCJjFiKymBaakpCcQUYCAJnBEBEgZWL/mMGoRqYaBKgmp6nrEBEgpVr/Fmr/qBbNArLDGIGG/uAIAIw6MqDEOVY4FiozORY4NiAjwCk2xrX+ggMCIsMYMgMDDByAMxGAMyAyw/AGIwCBbP6RHf3oCDlx4JnAmWGYJwwal7MBDDqJ8anR6cEQESAlW/+o0ZFj/ujBqQGhYv7dCb0CwsEc8sEYmcGBa/7gCAC4J80KqHGI8aC7wLknoDPAuAiqIqhhmMGqu90EDBq5CMDag5C7wNDgdMx90tuA0K6TFmoBrQmJ8ZnByeEQESAlif+I8ZjByOGSaABhTv2INoyjwJ8xwJnA1ikAVvj11qwAMUn9IqDHKVNGAACMPJwIxoL+FoigYUT9IqDIKVZGf/4AMUH9IqDJKVNGfP4oI1bCnq0EgUX+4AgAoqJxwKoRgT7+4AgAgUL+4AgAxnP+AAAoMxaCnK0EgTz+4AgAoqPogTb+4AgA4AIARmz+HfAAAAA2QQCdAoKgwCgDh5kPzDIMEoYHAAwCKQN84oYPACYSByYiGIYDAAAAgqDbgCkjh5kqDCIpA3zyRggAAAAioNwnmQoMEikDLQgGBAAAAIKg3Xzyh5kGDBIpAyKg2x3wAAA=",
"text_start": 1074520064,
"data": "GOv8P9jnC0Bx6AtA8+wLQO3oC0CP6AtA7egLQEnpC0AG6gtAeOoLQCHqC0CB5wtAo+kLQPjpC0Bn6QtAmuoLQI7pC0Ca6gtAXegLQLPoC0Dt6AtASekLQHfoC0BM6wtAs+wLQKXmC0DX7AtApeYLQKXmC0Cl5gtApeYLQKXmC0Cl5gtApeYLQKXmC0Dz6gtApeYLQM3rC0Cz7AtA",
"data_start": 1073605544
}
@@ -1,7 +0,0 @@
{
"entry": 1077413304,
"text": "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",
"text_start": 1077411840,
"data": "GGvKP+AIOEAsCThAhAk4QCgKOECUCjhAQgo4QKgHOEDkCThAJAo4QJgJOEBYBzhAzAk4QFgHOEC6CDhA/gg4QCwJOECECThAzAg4QBIIOEBCCDhAyAg4QOYMOEAsCThArAs4QJoMOECkBjhAxAw4QKQGOECkBjhApAY4QKQGOECkBjhApAY4QKQGOECkBjhASAs4QKQGOEDICzhAmgw4QA==",
"data_start": 1070295976
}
@@ -1,7 +0,0 @@
{
"entry": 1077413532,
"text": "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",
"text_start": 1077411840,
"data": "IGvIP1YKOECmCjhA/go4QKILOEAODDhAvAs4QCIJOEBeCzhAngs4QBILOEDSCDhARgs4QNIIOEAwCjhAdgo4QKYKOED+CjhAQgo4QIYJOEC2CThAPgo4QGAOOECmCjhAJg04QBgOOEASCDhAQA44QBIIOEASCDhAEgg4QBIIOEASCDhAEgg4QBIIOEASCDhAwgw4QBIIOEBEDThAGA44QA==",
"data_start": 1070164912
}
Loaded 100 of 263 files, more files were not shown because too many files have changed in this diff. Show more