kopia lustrzana https://github.com/dl9rdz/rdz_ttgo_sonde
Porównaj commity
4 Commity
1687117bec
...
d5393ca8eb
Autor | SHA1 | Data |
---|---|---|
Hansi, dl9rdz | d5393ca8eb | |
Hansi, dl9rdz | 1081fc6373 | |
Hansi, dl9rdz | 5ce1d36b97 | |
Hansi, dl9rdz | 6cf330b143 |
26
.travis.yml
26
.travis.yml
|
@ -1,9 +1,21 @@
|
|||
# safelist
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- devel
|
||||
|
||||
language: c
|
||||
dist: focal
|
||||
os: linux
|
||||
env:
|
||||
global:
|
||||
- python /home/travis/.arduino15/packages/esp32/tools/esptool_py/3.0.0/esptool.py
|
||||
- ESP32TOOLS=/home/travis/.arduino15/packages/esp32/hardware/esp32/1.0.6/tools
|
||||
- MKSPIFFS=/home/travis/.arduino15/packages/esp32/tools/mkspiffs/0.2.3/mkspiffs
|
||||
before_install:
|
||||
- sudo apt-get install python-is-python3
|
||||
- pip uninstall pyserial
|
||||
- pip install pyserial
|
||||
- "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_1.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :1 -ac -screen 0 1280x1024x16"
|
||||
- sleep 3
|
||||
- export DISPLAY=:1.0
|
||||
|
@ -11,20 +23,20 @@ before_install:
|
|||
- tar xf arduino-1.8.16-linux64.tar.xz
|
||||
- sudo mv arduino-1.8.16 /usr/local/share/arduino
|
||||
- sudo ln -s /usr/local/share/arduino/arduino /usr/local/bin/arduino
|
||||
- wget https://github.com/me-no-dev/ESPAsyncWebServer/archive/master.zip
|
||||
- wget --secure-protocol=TLSv1_2 https://github.com/me-no-dev/ESPAsyncWebServer/archive/master.zip
|
||||
- unzip master.zip
|
||||
- rm master.zip
|
||||
- sudo mv ESPAsyncWebServer-master /usr/local/share/arduino/libraries/ESPAsyncWebServer
|
||||
- wget https://github.com/me-no-dev/AsyncTCP/archive/master.zip
|
||||
- wget --secure-protocol=TLSv1_2 https://github.com/me-no-dev/AsyncTCP/archive/master.zip
|
||||
- unzip master.zip
|
||||
- rm master.zip
|
||||
- sudo mv AsyncTCP-master /usr/local/share/arduino/libraries/AsyncTCP
|
||||
- wget https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/download/1.0/ESP32FS-1.0.zip
|
||||
- wget https://github.com/lewisxhe/AXP202X_Library/archive/refs/tags/V1.1.3.zip
|
||||
- wget --secure-protocol=TLSv1_2 https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/download/1.0/ESP32FS-1.0.zip
|
||||
- wget --secure-protocol=TLSv1_2 https://github.com/lewisxhe/AXP202X_Library/archive/refs/tags/V1.1.3.zip
|
||||
- unzip V1.1.3.zip
|
||||
- sudo mv AXP202X_Library-1.1.3 /usr/local/share/arduino/libraries/
|
||||
|
||||
- wget https://github.com/dx168b/async-mqtt-client/archive/master.zip
|
||||
- wget --secure-protocol=TLSv1_2 https://github.com/dx168b/async-mqtt-client/archive/master.zip
|
||||
- unzip master.zip
|
||||
- rm master.zip
|
||||
- sudo mv async-mqtt-client-master /usr/local/share/arduino/libraries/
|
||||
|
@ -47,9 +59,9 @@ install:
|
|||
- arduino --pref "build.path=$PWD/build" --save-prefs
|
||||
- arduino --install-boards esp32:esp32:1.0.6 --save-prefs
|
||||
- ln -s $PWD/libraries/SondeLib /usr/local/share/arduino/libraries/SondeLib
|
||||
- arduino --install-library "U8g2:2.29.11"
|
||||
- arduino --install-library "U8g2:2.34.22"
|
||||
- arduino --install-library "MicroNMEA"
|
||||
- arduino --install-library "GFX Library for Arduino:1.1.5"
|
||||
- arduino --install-library "GFX Library for Arduino:1.2.9"
|
||||
script:
|
||||
- arduino --board esp32:esp32:t-beam --verify $PWD/RX_FSK/RX_FSK.ino
|
||||
- ESPPATH=`arduino --get-pref runtime.tools.xtensa-esp32-elf-gcc.path`
|
||||
|
|
|
@ -1,11 +1,51 @@
|
|||
GPL License Exceptions:
|
||||
GPL License Exceptions
|
||||
|
||||
- The SX1278FSK library is based on
|
||||
Code in the repository:
|
||||
|
||||
- The SX1278FSK library (src/SX1278FSK.cpp) is based on
|
||||
https://github.com/pdelmo/lora_shield_arduino.git
|
||||
and licensed under
|
||||
GNU Lesser General Public License, version 2.1 (SPDX short identifier: LGPL-2.1)
|
||||
|
||||
- Leaflet sidebar v2 plugin is (c) 2013 Tobias Bieniek based on
|
||||
https://github.com/Turbo87/sidebar-v2/
|
||||
and licensed under
|
||||
MIT License
|
||||
- General purpose Reed-Solomon decoder for 8-bit symbols or less
|
||||
Copyright 2003 Phil Karn, KA9Q
|
||||
May be used under the terms of the GNU Lesser General Public License (LGPL) - lgpl-2.1.txt
|
||||
|
||||
- some of the decoder code is more or less loosly inspired by
|
||||
* oe5dxl aprs toolchain (http://oe5dxl.hamspirit.at:8025), licensed under GNU GPL (gpl-3.0.txt)
|
||||
* Zilogs RS decoder (https://github.com/rs1729/RS), licensed under GNU GPL (gpl-3.0.txt)
|
||||
|
||||
- Fonts in src/fonts (FreeMono*, FreeSans*, Picopixel) taken from Adafruit-GFX-Library
|
||||
licensed under BSD License
|
||||
Font src/fonts/Terminal11x16.h taken from https://github.com/Nkawu/TFT_22_ILI9225/
|
||||
licensed under GPL-3.0
|
||||
|
||||
External libraries used by the project:
|
||||
|
||||
- Espressif 32: development platform for PlatformIO
|
||||
https://github.com/platformio/platform-espressif32
|
||||
licensed under Apache-2.0 licsense - apache.txt
|
||||
|
||||
- U8glib library for monochrome displays, version 2
|
||||
https://github.com/olikraus/u8g2
|
||||
licensed under the new-bsd license (two-clause bsd license) - LICENSE-u8g2.txt
|
||||
|
||||
- MicroNMEA - A compact Arduino library to parse NMEA sentences
|
||||
https://github.com/stevemarple/MicroNMEA
|
||||
licsensed under LGPL-2.1 license - lgpl-2.1.txt
|
||||
|
||||
- ESPAsyncWebServer
|
||||
see https://github.com/me-no-dev/ESPAsyncWebServer/blob/master/src/ESPAsyncWebServer.h#L7-L19
|
||||
licsensed under LGPL-2.1 license - lgpl-2.1.txt
|
||||
|
||||
- Arduino GFX developing for various color displays
|
||||
https://github.com/moononournation/Arduino_GFX
|
||||
licensed under BSD License - LICENSE-Arduino_GFX.txt
|
||||
|
||||
- asynchronous MQTT client implementation
|
||||
https://github.com/dx168b/async-mqtt-client
|
||||
licensed under MIT license - LICENSE-mqtt.txt
|
||||
|
||||
- Time library for Arduino
|
||||
https://github.com/PaulStoffregen/Time
|
||||
licensed under LGPL-2.1 licsense - lgpl-2.1txt
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
Software License Agreement (BSD License)
|
||||
|
||||
Copyright (c) 2012 Adafruit Industries. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
- Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
- Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Marvin Roger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,79 @@
|
|||
|
||||
The U8g2lib code (https://github.com/olikraus/u8g2) is licensed under the terms of
|
||||
the new-bsd license (two-clause bsd license).
|
||||
See also: http://www.opensource.org/licenses/bsd-license.php
|
||||
|
||||
Fonts are licensed under different conditions.
|
||||
See
|
||||
https://github.com/olikraus/u8g2/wiki/fntgrp
|
||||
for detailed information on the licensing conditions for each font.
|
||||
|
||||
The example code in sys/raspi_gpio/hal will use the bcm2835 lib from Mike McCauley
|
||||
which is licensed under GPL V3: http://www.airspayce.com/mikem/bcm2835/
|
||||
|
||||
============ X11 Fonts COUR, HELV, NCEN, TIM, SYMB ============
|
||||
|
||||
For fonts derived from the following files, the license below applies.
|
||||
COURB08.BDF COURB10.BDF COURB12.BDF COURB14.BDF COURB18.BDF
|
||||
COURB24.BDF COURR08.BDF COURR10.BDF COURR12.BDF COURR14.BDF
|
||||
COURR18.BDF COURR24.BDF HELVB08.BDF HELVB10.BDF HELVB12.BDF HELVB14.BDF
|
||||
HELVB18.BDF HELVB24.BDF HELVR08.BDF HELVR10.BDF HELVR12.BDF HELVR14.BDF
|
||||
HELVR18.BDF HELVR24.BDF NCENB08.BDF NCENB10.BDF NCENB12.BDF
|
||||
NCENB14.BDF NCENB18.BDF NCENB24.BDF NCENR08.BDF NCENR10.BDF
|
||||
NCENR12.BDF NCENR14.BDF NCENR18.BDF NCENR24.BDF SYMB08.BDF SYMB10.BDF
|
||||
SYMB12.BDF SYMB14.BDF SYMB18.BDF SYMB24.BDF TIMB08.BDF TIMB10.BDF
|
||||
TIMB12.BDF TIMB14.BDF TIMB18.BDF TIMB24.BDF TIMR08.BDF TIMR10.BDF
|
||||
TIMR12.BDF TIMR14.BDF TIMR18.BDF TIMR24.BDF
|
||||
|
||||
Copyright 1984-1989, 1994 Adobe Systems Incorporated.
|
||||
Copyright 1988, 1994 Digital Equipment Corporation.
|
||||
|
||||
Adobe is a trademark of Adobe Systems Incorporated which may be
|
||||
registered in certain jurisdictions.
|
||||
Permission to use these trademarks is hereby granted only in
|
||||
association with the images described in this file.
|
||||
|
||||
Permission to use, copy, modify, distribute and sell this software
|
||||
and its documentation for any purpose and without fee is hereby
|
||||
granted, provided that the above copyright notices appear in all
|
||||
copies and that both those copyright notices and this permission
|
||||
notice appear in supporting documentation, and that the names of
|
||||
Adobe Systems and Digital Equipment Corporation not be used in
|
||||
advertising or publicity pertaining to distribution of the software
|
||||
without specific, written prior permission. Adobe Systems and
|
||||
Digital Equipment Corporation make no representations about the
|
||||
suitability of this software for any purpose. It is provided "as
|
||||
is" without express or implied warranty.
|
||||
|
||||
|
||||
============ BSD License for U8g2lib Code ============
|
||||
|
||||
Universal 8bit Graphics Library (https://github.com/olikraus/u8g2)
|
||||
|
||||
Copyright (c) 2016, olikraus@gmail.com
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this list
|
||||
of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
||||
list of conditions and the following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
Plik diff jest za duży
Load Diff
|
@ -42,6 +42,7 @@ var cfgs = [
|
|||
[ "axudp.port", "AXUDP port"],
|
||||
[ "axudp.highrate", "Rate limit"],
|
||||
[ "tcp.active", "APRS TCP active"],
|
||||
[ "tcp.timeout", "APRS TCP timeout [s] (0=off, 25=on)"],
|
||||
[ "tcp.host", "APRS TCP host"],
|
||||
[ "tcp.port", "APRS TCP port"],
|
||||
[ "tcp.highrate", "Rate limit"],
|
||||
|
|
|
@ -9,16 +9,15 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="wrapper"><div class="header">
|
||||
<h2>rdzTTGOSonde Server</h2>
|
||||
<div class="topnav" id="myTopnav">
|
||||
<a href="#" onclick="selTab(event,'QRG')" class="tablinks" id="defaultTab">QRG</a>
|
||||
<a href="#" onclick="selTab(event,'WiFi')" class="tablinks">WiFi</a>
|
||||
<a href="#" onclick="selTab(event,'Data')" class="tablinks">Data</a>
|
||||
<a href="#" onclick="document.location.href='livemap.html'" class="tablinks">LiveMap</a>
|
||||
<a href="#" onclick="selTab(event,'Map')" class="tablinks">Map</a>
|
||||
<a href="#" onclick="selTab(event,'Config')" class="tablinks">Config</a>
|
||||
<a href="#" onclick="selTab(event,'Control')" class="tablinks">Control</a>
|
||||
<a href="#" onclick="selTab(event,'About')" class="tablinks">About</a>
|
||||
<a href="#qrg" onclick="selTab(event,'QRG')" class="tablinks" id="defaultTab">QRG</a>
|
||||
<a href="#data" onclick="selTab(event,'Data')" class="tablinks">Data</a>
|
||||
<a href="#map" onclick="selTab(event,'Map')" class="tablinks">Map</a>
|
||||
<a href="#livemap" onclick="document.location.href='livemap.html'" class="tablinks">LiveMap</a>
|
||||
<a href="#ctrl" onclick="selTab(event,'Control')" class="tablinks">Control</a>
|
||||
<a href="#config" onclick="selTab(event,'Config')" class="tablinks">Config</a>
|
||||
<a href="#wifi" onclick="selTab(event,'WiFi')" class="tablinks">WiFi</a>
|
||||
<a href="#about" onclick="selTab(event,'About')" class="tablinks">About</a>
|
||||
<a href="javascript:void(0);" class="icon" onclick="myFunction()">
|
||||
<span class="hamburger"></span>
|
||||
</a>
|
||||
|
@ -52,7 +51,7 @@
|
|||
<div id="About" class="tabcontent">
|
||||
<div class="tci">
|
||||
%VERSION_NAME%<br>
|
||||
Copyright © 2019-2021 by Hansi Reiser, DL9RDZ<br>
|
||||
Copyright © 2019-2022 by Hansi Reiser, DL9RDZ<br>
|
||||
(version %VERSION_ID%)<br><br>
|
||||
|
||||
<a href="/upd.html">Check for update (requires TTGO internet connection via WiFi)</a><br><br>
|
||||
|
@ -76,6 +75,7 @@
|
|||
See <a href="https://www.gnu.org/licenses/gpl-2.0.txt">https://www.gnu.org/licenses/gpl-2.0.txt</a>
|
||||
for details.
|
||||
</div>
|
||||
<div class="footer"><span></span><span class="ttgoinfo">rdzTTGOserver %VERSION_ID%</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<html>
|
||||
<head>
|
||||
<script>
|
||||
maptype = "SH"; // TODO: Get from config
|
||||
|
||||
const maps = {
|
||||
"WS": ["https://www.wettersonde.net/map.php", (s)=>"?sonde="+s, (lat,lon)=>""],
|
||||
"SH": ["https://sondehub.org/", (s)=>s, (lat,lon)=>'#!mz=8&mc=' + lat + ',' + lon],
|
||||
"RS": ["https://radiosondy.info/", (s)=>"sonde.php?sondenumber="+s, (lat,lon)=>""],
|
||||
"OW": ["https://v2.openwx.de/start.php", (s)=>"?sonde="+s, (lat,lon)=>"?mode=mobile"],
|
||||
};
|
||||
|
||||
data = ["T4420541",63.5,-20.0];
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
fetch('live.json')
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
if (data.gps===undefined) { data.gps={} };
|
||||
console.log(data.gps.lat, data.gps.lon, data.sonde.ser);
|
||||
if( (data.sonde.ser||'')=='' && !(data.gps.lat===undefined) ) {
|
||||
urlarg = maps[maptype][2](data.gps.lat, data.gps.lon);
|
||||
} else {
|
||||
urlarg = maps[maptype][1](data.sonde.ser||'');
|
||||
}
|
||||
iftxt='<iframe src="' + maps[maptype][0] + urlarg + '" style="border:1px solid #00A3D3;border-radius:20px;height:98vh;width:100%"></iframe>';
|
||||
document.getElementsByTagName('body')[0].innerHTML = iftxt;
|
||||
})
|
||||
})
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
|
@ -3,10 +3,19 @@ stypes.set('4', 'RS41');
|
|||
stypes.set('R', 'RS92');
|
||||
stypes.set('D', 'DFM');
|
||||
stypes.set('M', 'M10/M20');
|
||||
//stypes.set('2', 'M10/M20');
|
||||
stypes.set('3', 'MP3H');
|
||||
|
||||
/* (no longer) Used by qrg.html in RX_FSK.ino */
|
||||
function footer() {
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
var form = document.querySelector(".wrapper");
|
||||
form.addEventListener("input", function() {
|
||||
document.querySelector(".save").disabled = false;
|
||||
});
|
||||
document.querySelector(".save").disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
/* Used by qrg.html in RX_FSK.ino */
|
||||
function prep() {
|
||||
var stlist=document.querySelectorAll("input.stype");
|
||||
for(txt of stlist){
|
||||
|
@ -28,16 +37,18 @@ function prep() {
|
|||
function qrgTable() {
|
||||
var tab=document.getElementById("divTable");
|
||||
|
||||
var table = "<table><tr><th>ID</th><th>Active</th><th>Freq</th><th>Launchsite</th><th>Mode</th></tr>";
|
||||
var table = "<table><tr><th>Ch</th><th>Active</th><th>Frequency</th><th>Decoder</th><th>Launchsite</th></tr>";
|
||||
for(i=0; i<qrgs.length; i++) {
|
||||
var ck = "";
|
||||
if(qrgs[i][0]) ck="checked";
|
||||
table += "<tr><td>" + (i+1) + "</td><td><input name=\"A" + (i+1) + "\" type=\"checkbox\" " + ck + "/></td>";
|
||||
table += "<td><input name=\"F" + (i+1) + "\" type=\"text\" width=12 value=\"" + qrgs[i][1] + "\"></td>";
|
||||
table += "<td><input name=\"S" + (i+1) + "\" type=\"text\" value=\"" + qrgs[i][2] +"\"></td>";
|
||||
table += "<td><input class=\"stype\" name=\"T" + (i+1) + "\" value=\"" + qrgs[i][3] + "\"></td></tr>";
|
||||
table += "<tr><td class=\"ch\">" + (i+1) + "</td><td class=\"act\"><input name=\"A" + (i+1) + "\" type=\"checkbox\" " + ck + "/></td>";
|
||||
table += "<td><input name=\"F" + (i+1) + "\" type=\"text\" size=7 value=\"" + qrgs[i][1] + "\"></td>";
|
||||
table += "<td><input class=\"stype\" name=\"T" + (i+1) + "\" value=\"" + qrgs[i][3] + "\"></td>";
|
||||
table += "<td><input name=\"S" + (i+1) + "\" type=\"text\" value=\"" + qrgs[i][2] +"\"></td></tr>";
|
||||
}
|
||||
table += "</table>";
|
||||
tab.innerHTML = table;
|
||||
prep();
|
||||
footer();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ body, html {
|
|||
th.cfg {
|
||||
padding:5pt
|
||||
}
|
||||
table.stat {
|
||||
margin:0px 0px 5px 0px;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
position: relative;
|
||||
|
@ -43,8 +46,18 @@ th.cfg {
|
|||
flex-grow: 1; border: none; margin: 0; padding: 0;
|
||||
}
|
||||
.footer {
|
||||
background-color: #333;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
td.ch {
|
||||
text-align: right;
|
||||
padding: 0px 8px;
|
||||
}
|
||||
td.act {
|
||||
text-align: center;
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
border-collapse: collapse;
|
||||
|
@ -71,7 +84,6 @@ td#sfreq {
|
|||
.tabcontent {
|
||||
display: none;
|
||||
flex: 1;
|
||||
padding: 6px 12px;
|
||||
border-top: none;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
|
@ -90,24 +102,16 @@ h1{
|
|||
p{
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
.canberemoved_button {
|
||||
display: inline-block;
|
||||
background-color: #008CBA;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
padding: 16px 40px;
|
||||
text-decoration: none;
|
||||
font-size: 30px;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button2 {
|
||||
background-color: #f44336;
|
||||
}
|
||||
:disabled.save {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.save {
|
||||
background-color: #0F3376;
|
||||
border: black;
|
||||
background-color: #CC1111; /* 0F33C6; */
|
||||
border: white;
|
||||
border-width: 1;
|
||||
color: white;
|
||||
padding: 8px 30px;
|
||||
|
@ -117,6 +121,14 @@ p{
|
|||
font-size: 14px;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.ttgoinfo {
|
||||
color: white;
|
||||
padding: 8px 10px;
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
margin: 0
|
||||
}
|
||||
.ctlbtn {
|
||||
background-color: #ccc;
|
||||
border: black;
|
||||
|
|
|
@ -2,12 +2,17 @@
|
|||
// Configuration flags for including/excluding fuctionality from the compiled binary
|
||||
// set flag to 0 for exclude/1 for include
|
||||
|
||||
/* data feed to sondehubv2 */
|
||||
/* needs about 4k4 code, 200b data, 200b stack, 200b heap */
|
||||
// Selection of data output connectors to be included in firmware
|
||||
// APRS includes AXUDP (e.g. for aprsmap) and APRS-IS (TCP) (e.g. for wettersonde.net, radiosondy.info)
|
||||
#define FEATURE_SONDEHUB 1
|
||||
|
||||
#define FEATURE_CHASEMAPPER 1
|
||||
#define FEATURE_MQTT 1
|
||||
#define FEATURE_SDCARD 0
|
||||
#define FEATURE_APRS 1
|
||||
|
||||
|
||||
// Additional optional components
|
||||
#define FEATURE_RS92 1
|
||||
|
||||
/* Most recent version support fonts in a dedicated flash parition "fonts".
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
#ifndef _CHASEMAPPER_H
|
||||
#define _CHASEMAPPER_H
|
||||
|
||||
#include "Sonde.h"
|
||||
//#include <WiFi.h>
|
||||
#include <WiFiUdp.h>
|
||||
#include <time.h>
|
||||
class Chasemapper {
|
||||
public:
|
||||
static int send(WiFiUDP &udb, SondeInfo *si);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,6 +16,13 @@
|
|||
|
||||
#define MAXIDAGE 1800
|
||||
|
||||
|
||||
const char *dfmSubtypeLong[] = { "", "DFMxx", "DFM06", "DFM06P", "PS15",
|
||||
"DFM09", "DFM09P", "DFM17", "DFM17P"};
|
||||
const char *dfmSubtypeShort[] = { "", "DFMx", "DFM6", "DF6P", "PS15",
|
||||
"DFM9", "DF9P", "DF17", "D17P"};
|
||||
|
||||
|
||||
/*
|
||||
* observed DAT patterns for DFM-9:
|
||||
* A: 0+1; 2+3; 4+5; 6+7; 8+0 => keep frame in shadowFrame
|
||||
|
@ -44,7 +51,10 @@ static struct st_dfmstat {
|
|||
uint8_t nameregtop;
|
||||
uint8_t lastdat;
|
||||
uint8_t cycledone; // 0=no; 1=OK, 2=partially/with errors
|
||||
float meas[5+2];
|
||||
float meas[9];
|
||||
uint16_t measok; // Bit-mask showing which meas entries have been received
|
||||
uint8_t ptu_chan; // always the max channel. used as subtype before, but 0xC can be DFM09 (with P) or DFM17 (w/o P)
|
||||
uint8_t sensP; // P channel in data (0xC DMF09 (but not 0xC DFM17), 0xD DFM17P, 0x8 DFM6P (but not PS15) (0 or 1)
|
||||
} dfmstate;
|
||||
|
||||
decoderSetupCfg DFMSetupCfg {
|
||||
|
@ -280,8 +290,12 @@ void DFM::finddfname(uint8_t *b)
|
|||
snprintf(sd->id, 10, "D%x ", id);
|
||||
memcpy(sd->ser, sd->id+1, 9);
|
||||
sd->validID = true;
|
||||
sd->subtype = (st>>4)&0x0F;
|
||||
strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
|
||||
//sd->subtype = (st>>4)&0x0F;
|
||||
//strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
|
||||
// Subtype is set later, as we need more data to distingish 0xC DFM09P and DFM17
|
||||
sd->subtype = 0;
|
||||
dfmstate.ptu_chan = (st>>4)&0x0F;
|
||||
strcpy(sd->typestr, "DFM");
|
||||
return;
|
||||
}
|
||||
dfmstate.lastfrcnt = 0;
|
||||
|
@ -332,8 +346,12 @@ void DFM::finddfname(uint8_t *b)
|
|||
Serial.println(sd->id);
|
||||
memcpy(sd->ser, sd->id+1, 9);
|
||||
sd->validID = true;
|
||||
sd->subtype = (st>>4)&0x0F;
|
||||
strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
|
||||
//sd->subtype = (st>>4)&0x0F;
|
||||
//strncpy(sd->typestr, typestr[ (st>>4)&0x0F ], 5);
|
||||
// Subtype is set later, as we need more data to distingish 0xC DFM09P and DFM17
|
||||
sd->subtype = 0;
|
||||
dfmstate.ptu_chan = (st>>4)&0x0F;
|
||||
strcpy(sd->typestr, "DFM");
|
||||
}
|
||||
if(dfmstate.nameregok==i) {
|
||||
Serial.print(" ID OK");
|
||||
|
@ -361,13 +379,13 @@ void DFM::finddfname(uint8_t *b)
|
|||
|
||||
static float get_Temp() {
|
||||
SondeData *si = &(sonde.si()->d);
|
||||
if(!si->validID) { // type not yet known, so don't try to decode
|
||||
if(!si->subtype) { // type not yet known, so don't try to decode
|
||||
return NAN;
|
||||
}
|
||||
float f = dfmstate.meas[0],
|
||||
f1 = dfmstate.meas[3],
|
||||
f2 = dfmstate.meas[4];
|
||||
if(si->subtype >= 0x0C) {
|
||||
if(dfmstate.sensP) {
|
||||
f = dfmstate.meas[1];
|
||||
f1 = dfmstate.meas[5];
|
||||
f2 = dfmstate.meas[6];
|
||||
|
@ -377,7 +395,8 @@ static float get_Temp() {
|
|||
float BB0 = 3260.0; // B/Kelvin, fit -55C..+40C
|
||||
float T0 = 25 + 273.15; // t0=25C
|
||||
float R0 = 5.0e3; // R0=R25=5k
|
||||
float Rf = 220e3; // Rf = 220k
|
||||
float Rf = 220e3; // Rf = 220k, for DFM17: 332k
|
||||
if( si->subtype==DFM_17 || si->subtype==DFM_17P ) Rf = 332e3;
|
||||
float g = f2/Rf;
|
||||
float R = (f-f1) / g; // meas[0,3,4] > 0 ?
|
||||
float T = 0; // T/Kelvin
|
||||
|
@ -398,16 +417,41 @@ void DFM::decodeCFG(uint8_t *cfg)
|
|||
finddfname(cfg);
|
||||
// get meas
|
||||
uint8_t conf_id = (*cfg)>>4;
|
||||
if(conf_id<=6) {
|
||||
if(conf_id<=8) {
|
||||
uint32_t val = (cfg[1]<<12) | (cfg[2]<<4) | cfg[3];
|
||||
uint8_t exp = cfg[0] & 0xF;
|
||||
dfmstate.meas[conf_id] = val / (float)(1<<exp);
|
||||
dfmstate.measok |= (1 << conf_id);
|
||||
Serial.printf("meas %d is %f (%d,%d)\n", conf_id, dfmstate.meas[conf_id], val, exp);
|
||||
}
|
||||
|
||||
// get type (if we have an ID, but no type yet, and (needed only for 0xC), meas[6])
|
||||
if( si->validID && si->subtype==0 && (dfmstate.measok & (1<<6)) ) {
|
||||
switch(dfmstate.ptu_chan) {
|
||||
case 0x6: si->subtype = DFM_06; break;
|
||||
case 0x7: case 0x8:
|
||||
si->subtype = DFM_06P; dfmstate.sensP = 1; break; // (TODO: OR PS15)
|
||||
case 0xA: si->subtype = DFM_09; break;
|
||||
case 0xB: si->subtype = DFM_17; break;
|
||||
case 0xC: // DFM-09P or DFM-17
|
||||
if(dfmstate.meas[6]<220e3) { si->subtype = DFM_09P; dfmstate.sensP = 1; }
|
||||
else si->subtype = DFM_17;
|
||||
break;
|
||||
case 0xD: si->subtype = DFM_17P; dfmstate.sensP = 1; break;
|
||||
default: si->subtype = DFM_UNK;
|
||||
}
|
||||
if( si->subtype == DFM_UNK ) {
|
||||
snprintf(si->typestr, 5, "DFx%x", dfmstate.ptu_chan);
|
||||
si->subtype |= (dfmstate.ptu_chan<<4);
|
||||
} else {
|
||||
strcpy(si->typestr, dfmSubtypeShort[si->subtype]);
|
||||
}
|
||||
}
|
||||
|
||||
// get batt
|
||||
if(si->validID && si->subtype>=0x0A) {
|
||||
if(si->validID && dfmstate.ptu_chan>=0x0A && si->subtype>0 ) {
|
||||
// otherwise don't try, as we might not have the right type yet...
|
||||
int cid = (si->subtype >= 0x0C) ? 0x7 : 0x5;
|
||||
int cid = (dfmstate.sensP) ? 0x7 : 0x5;
|
||||
if(conf_id == cid) {
|
||||
uint16_t val = cfg[1]<<8 | cfg[2];
|
||||
si->batteryVoltage = val / 1000.0;
|
||||
|
|
|
@ -20,6 +20,11 @@
|
|||
#define DFM_NORMAL 0
|
||||
#define DFM_INVERSE 1
|
||||
|
||||
enum DFMSubtype { DFM_UNDEF, DFM_UNK, DFM_06, DFM_06P, DFM_PS15, DFM_09, DFM_09P, DFM_17, DFM_17P };
|
||||
|
||||
extern const char *dfmSubtypeLong[];
|
||||
extern const char *dfmSubtypeShort[];
|
||||
|
||||
/* Main class */
|
||||
class DFM : public DecoderBase
|
||||
{
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
#include "SX1278FSK.h"
|
||||
#include "Sonde.h"
|
||||
|
||||
#define DECODERBASE_DEBUG 1
|
||||
#define DECODERBASE_DEBUG 0
|
||||
|
||||
#if DECODERBASE_DEBUG
|
||||
#define DBG(x) x
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
#include <U8x8lib.h>
|
||||
#include <U8g2lib.h>
|
||||
#include <SPIFFS.h>
|
||||
#include <axp20x.h>
|
||||
#include <MicroNMEA.h>
|
||||
#include "Display.h"
|
||||
#include "Sonde.h"
|
||||
#include "pmu.h"
|
||||
|
||||
int readLine(Stream &stream, char *buffer, int maxlen);
|
||||
|
||||
|
@ -22,8 +22,7 @@ extern const char *version_id;
|
|||
|
||||
extern Sonde sonde;
|
||||
|
||||
extern AXP20X_Class axp;
|
||||
extern bool axp192_found;
|
||||
extern PMU *pmu;
|
||||
extern SemaphoreHandle_t axpSemaphore;
|
||||
|
||||
extern xSemaphoreHandle globalLock;
|
||||
|
@ -33,8 +32,6 @@ extern xSemaphoreHandle globalLock;
|
|||
} while (xSemaphoreTake(globalLock, portMAX_DELAY) != pdPASS)
|
||||
#define SPI_MUTEX_UNLOCK() xSemaphoreGive(globalLock)
|
||||
|
||||
struct GpsPos gpsPos;
|
||||
|
||||
//SPIClass spiDisp(HSPI);
|
||||
|
||||
byte myIP_tiles[8*11];
|
||||
|
@ -318,7 +315,7 @@ void U8x8Display::drawString(uint16_t x, uint16_t y, const char *s, int16_t widt
|
|||
}
|
||||
if(width<0) {
|
||||
int l = strlen(buf);
|
||||
memset(buf, ' ', width-l);
|
||||
memset(buf, ' ', -width-l);
|
||||
utf2latin15(s, buf+l, 50-l);
|
||||
}
|
||||
u8x8->drawString(x, y, buf);
|
||||
|
@ -1554,12 +1551,12 @@ void Display::drawGPS(DispEntry *de) {
|
|||
// equirectangular approximation is good enough
|
||||
if( !VALIDPOS(sonde.si()->d.validPos) ) {
|
||||
snprintf(buf, 16, "no pos ");
|
||||
if(de->extra && *de->extra=='5') buf[5]=0;
|
||||
if( de->extra[1]=='5') buf[5]=0;
|
||||
} else if( disp.gpsDist < 0 ) {
|
||||
snprintf(buf, 16, "no gps ");
|
||||
if(de->extra && *de->extra=='5') buf[5]=0;
|
||||
if( de->extra[1]=='5') buf[5]=0;
|
||||
} else {
|
||||
if(de->extra && *de->extra=='5') { // 5-character version: ****m / ***km / **e6m
|
||||
if( de->extra[1]=='5') { // 5-character version: ****m / ***km / **e6m
|
||||
if(disp.gpsDist>999999) snprintf(buf, 16, "%de6m ", (int)(disp.gpsDist/1000000));
|
||||
if(disp.gpsDist>9999) snprintf(buf, 16, "%dkm ", (int)(disp.gpsDist/1000));
|
||||
else snprintf(buf, 16, "%dm ", (int)disp.gpsDist);
|
||||
|
@ -1670,13 +1667,14 @@ void Display::drawGPS(DispEntry *de) {
|
|||
void Display::drawBatt(DispEntry *de) {
|
||||
float val;
|
||||
char buf[30];
|
||||
if (!axp192_found) {
|
||||
if (!pmu) {
|
||||
if (sonde.config.batt_adc<0) return;
|
||||
switch (de->extra[0])
|
||||
{
|
||||
case 'V':
|
||||
val = (float)(analogRead(sonde.config.batt_adc)) / 4095 * 2 * 3.3 * 1.1;
|
||||
snprintf(buf, 30, "%.2f%s", val, de->extra + 1);
|
||||
Serial.printf("Batt: %s", buf);
|
||||
break;
|
||||
default:
|
||||
*buf = 0;
|
||||
|
@ -1684,51 +1682,67 @@ void Display::drawBatt(DispEntry *de) {
|
|||
rdis->setFont(de->fmt);
|
||||
drawString(de, buf);
|
||||
} else {
|
||||
*buf = 0;
|
||||
xSemaphoreTake( axpSemaphore, portMAX_DELAY );
|
||||
switch(de->extra[0]) {
|
||||
case 'S':
|
||||
if(!axp.isBatteryConnect()) {
|
||||
if(axp.isVBUSPlug()) { strcpy(buf, "U"); }
|
||||
if(!pmu->isBatteryConnected()) {
|
||||
if(pmu->isVbusIn()) { strcpy(buf, "U"); }
|
||||
else { strcpy(buf, "N"); } // no battary
|
||||
}
|
||||
else if (axp.isChargeing()) { strcpy(buf, "C"); } // charging
|
||||
else if (pmu->isCharging()) { strcpy(buf, "C"); } // charging
|
||||
else { strcpy(buf, "B"); } // battery, but not charging
|
||||
Serial.printf("Battery: %s\n", buf);
|
||||
break;
|
||||
case 'V':
|
||||
val = axp.getBattVoltage();
|
||||
val = pmu->getBattVoltage();
|
||||
snprintf(buf, 30, "%.2f%s", val/1000, de->extra+1);
|
||||
Serial.printf("Vbatt: %s\n", buf);
|
||||
break;
|
||||
case 'C':
|
||||
val = axp.getBattChargeCurrent();
|
||||
}
|
||||
if(pmu->type==TYPE_AXP192) {
|
||||
switch(de->extra[0]) {
|
||||
case 'C':
|
||||
val = pmu->getBattChargeCurrent();
|
||||
snprintf(buf, 30, "%.2f%s", val, de->extra+1);
|
||||
Serial.printf("Icharge: %s\n", buf);
|
||||
break;
|
||||
case 'D':
|
||||
val = axp.getBattDischargeCurrent();
|
||||
case 'D':
|
||||
val = pmu->getBattDischargeCurrent();
|
||||
snprintf(buf, 30, "%.2f%s", val, de->extra+1);
|
||||
Serial.printf("Idischarge: %s\n", buf);
|
||||
break;
|
||||
case 'U':
|
||||
case 'U':
|
||||
if(sonde.config.type == TYPE_M5_CORE2) {
|
||||
val = axp.getAcinVoltage();
|
||||
val = pmu->getAcinVoltage();
|
||||
} else {
|
||||
val = axp.getVbusVoltage();
|
||||
val = pmu->getVbusVoltage();
|
||||
}
|
||||
snprintf(buf, 30, "%.2f%s", val/1000, de->extra+1);
|
||||
Serial.printf("Vbus: %s\n", buf);
|
||||
break;
|
||||
case 'I':
|
||||
case 'I':
|
||||
if(sonde.config.type == TYPE_M5_CORE2) {
|
||||
val = axp.getAcinCurrent();
|
||||
val = pmu->getAcinCurrent();
|
||||
} else {
|
||||
val = axp.getVbusCurrent();
|
||||
val = pmu->getVbusCurrent();
|
||||
}
|
||||
snprintf(buf, 30, "%.2f%s", val, de->extra+1);
|
||||
Serial.printf("Ibus: %s\n", buf);
|
||||
break;
|
||||
case 'T':
|
||||
val = axp.getTemp(); // fixed in newer versions of libraray: -144.7 no longer needed here!
|
||||
case 'T':
|
||||
val = pmu->getTemperature();
|
||||
snprintf(buf, 30, "%.2f%s", val, de->extra+1);
|
||||
Serial.printf("temp: %s\n", buf);
|
||||
break;
|
||||
default:
|
||||
*buf=0;
|
||||
}
|
||||
}
|
||||
} else if (pmu->type == TYPE_AXP2101) {
|
||||
*buf = 0;
|
||||
if(de->extra[0]=='T') {
|
||||
val = pmu->getTemperature();
|
||||
snprintf(buf, 30, "%.2f%s", val, de->extra+1);
|
||||
}
|
||||
}
|
||||
xSemaphoreGive( axpSemaphore );
|
||||
rdis->setFont(de->fmt);
|
||||
drawString(de, buf);
|
||||
|
|
|
@ -9,18 +9,7 @@
|
|||
#include <U8x8lib.h>
|
||||
#include <SPIFFS.h>
|
||||
|
||||
struct GpsPos {
|
||||
double lat;
|
||||
double lon;
|
||||
int alt;
|
||||
int course;
|
||||
float speed;
|
||||
int sat;
|
||||
int accuracy;
|
||||
int hdop;
|
||||
int valid;
|
||||
};
|
||||
extern struct GpsPos gpsPos;
|
||||
#include "posinfo.h"
|
||||
|
||||
#define WIDTH_AUTO 9999
|
||||
struct DispEntry {
|
||||
|
@ -163,7 +152,7 @@ public:
|
|||
DispInfo *layouts;
|
||||
int nLayouts;
|
||||
static RawDisplay *rdis;
|
||||
char dispstate;
|
||||
uint16_t dispstate;
|
||||
|
||||
Display();
|
||||
void init();
|
||||
|
|
|
@ -304,6 +304,9 @@ int M10M20::decodeframeM10(uint8_t *data) {
|
|||
Serial.println("Decoding...");
|
||||
//SondeInfo *si = sonde.si();
|
||||
SondeData *si = &(sonde.si()->d);
|
||||
// Set type info to M10
|
||||
memcpy(si->typestr, "M10 ", 5);
|
||||
si->subtype = 1; // subtype 1: M10
|
||||
|
||||
// Its a M10
|
||||
// getid...
|
||||
|
@ -441,7 +444,7 @@ void M10M20::processM10data(uint8_t dt)
|
|||
rxsearching = false;
|
||||
rxbitc = 0;
|
||||
rxp = 0;
|
||||
isM20 = false;
|
||||
//isM20 = false;
|
||||
headerDetected = 1;
|
||||
#if 1
|
||||
int rssi=sx1278.getRSSI();
|
||||
|
@ -463,17 +466,17 @@ void M10M20::processM10data(uint8_t dt)
|
|||
// 64 9F 20 => M10
|
||||
// 64 49 0x => M10 (?) -- not used here
|
||||
// 45 20 7x => M20
|
||||
if(rxp==2 && dataptr[0]==0x45 && dataptr[1]==0x20) { isM20 = true; }
|
||||
if(rxp==2) {
|
||||
// Update: change type only if valid type information in received data
|
||||
if(dataptr[0]==0x45 && dataptr[1]==0x20) { isM20 = true; }
|
||||
else if(/*dataptr[0]==0x64 &&*/ dataptr[1]==0x9F) { isM20 = false; }
|
||||
}
|
||||
if(isM20) {
|
||||
memcpy(sonde.si()->d.typestr, "M20 ", 5);
|
||||
sonde.si()->d.subtype = 2;
|
||||
if(rxp>=M20_FRAMELEN) {
|
||||
rxsearching = true;
|
||||
haveNewFrame = decodeframeM20(dataptr);
|
||||
}
|
||||
} else {
|
||||
memcpy(sonde.si()->d.typestr, "M10 ", 5);
|
||||
sonde.si()->d.subtype = 1;
|
||||
if(rxp>=M10_FRAMELEN) {
|
||||
rxsearching = true;
|
||||
haveNewFrame = decodeframeM10(dataptr);
|
||||
|
@ -578,6 +581,8 @@ int M10M20::decodeframeM20(uint8_t *data) {
|
|||
|
||||
Serial.println("Decoding...");
|
||||
// Its a M20
|
||||
memcpy(si->typestr, "M20 ", 5);
|
||||
si->subtype = 2; // subtype 2: M20
|
||||
// getid...
|
||||
// TODO: Adjust ID calculation and serial number reconstruction
|
||||
char ids[11]={'M','E','0','0','0','0','0','0','0','0','0'};
|
||||
|
|
|
@ -790,6 +790,7 @@ int RS41::decode41(byte *data, int maxlen)
|
|||
posrs41(data+p, len, 0);
|
||||
break;
|
||||
case 'z': // 0x7a is character z - 7A-MEAS temperature and humidity frame
|
||||
case '\x7f': //0x7f - short MEAS, no pressure
|
||||
{
|
||||
uint32_t tempMeasMain = getint24(data, 560, p+0);
|
||||
uint32_t tempMeasRef1 = getint24(data, 560, p+3);
|
||||
|
@ -800,15 +801,23 @@ int RS41::decode41(byte *data, int maxlen)
|
|||
uint32_t tempHumiMain = getint24(data, 560, p+18);
|
||||
uint32_t tempHumiRef1 = getint24(data, 560, p+21);
|
||||
uint32_t tempHumiRef2 = getint24(data, 560, p+24);
|
||||
uint32_t pressureMain = getint24(data, 560, p+27);
|
||||
uint32_t pressureRef1 = getint24(data, 560, p+30);
|
||||
uint32_t pressureRef2 = getint24(data, 560, p+33);
|
||||
int16_t ptraw = getint16(data, 560, p+38);
|
||||
uint32_t pressureMain;
|
||||
uint32_t pressureRef1;
|
||||
uint32_t pressureRef2;
|
||||
int16_t ptraw;
|
||||
if (typ == 'z') {
|
||||
pressureMain = getint24(data, 560, p+27);
|
||||
pressureRef1 = getint24(data, 560, p+30);
|
||||
pressureRef2 = getint24(data, 560, p+33);
|
||||
ptraw = getint16(data, 560, p+38);
|
||||
}
|
||||
#if 0
|
||||
Serial.printf( "External temp: tempMeasMain = %ld, tempMeasRef1 = %ld, tempMeasRef2 = %ld\n", tempMeasMain, tempMeasRef1, tempMeasRef2 );
|
||||
Serial.printf( "Rel Humidity: humidityMain = %ld, humidityRef1 = %ld, humidityRef2 = %ld\n", humidityMain, humidityRef1, humidityRef2 );
|
||||
Serial.printf( "Humid sensor: tempHumiMain = %ld, tempHumiRef1 = %ld, tempHumiRef2 = %ld\n", tempHumiMain, tempHumiRef1, tempHumiRef2 );
|
||||
Serial.printf( "Pressure sens: pressureMain = %ld, pressureRef1 = %ld, pressureRef2 = %ld\n", pressureMain, pressureRef1, pressureRef2 );
|
||||
if (typ == 'z') {
|
||||
Serial.printf( "Pressure sens: pressureMain = %ld, pressureRef1 = %ld, pressureRef2 = %ld\n", pressureMain, pressureRef1, pressureRef2 );
|
||||
}
|
||||
#endif
|
||||
struct subframeBuffer *calibration = (struct subframeBuffer *)(sonde.si()->extra);
|
||||
// temp: 0xF8==bits 3..7 : we need refResistorlow/high, taylorT, polyT
|
||||
|
@ -818,7 +827,7 @@ int RS41::decode41(byte *data, int maxlen)
|
|||
bool validHumidity = calibration!=NULL && (calibration->valid & 0x7FE2001FFFF8) == 0x7FE2001FFFF8;
|
||||
|
||||
// pressure: bits 33 and 37..42 (variant; x25..x2a: matrixP) /// CALIB_P is 0x7E200000000)
|
||||
bool validPressure = calibration!=NULL && (calibration->valid & CALIB_P)==CALIB_P && calibration->value.names.variant[7]=='P';
|
||||
bool validPressure = calibration!=NULL && (calibration->valid & CALIB_P)==CALIB_P && calibration->value.names.variant[7]=='P' && (typ == 'z');
|
||||
|
||||
if ( validPressure ) {
|
||||
si->pressure = GetRAP( pressureMain, pressureRef1, pressureRef2, ptraw );
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_RS92
|
||||
|
||||
/* RS92 decoder functions */
|
||||
#include "RS92.h"
|
||||
|
@ -587,3 +589,4 @@ int RS92::waitRXcomplete() {
|
|||
|
||||
|
||||
RS92 rs92 = RS92();
|
||||
#endif
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
#include <U8x8lib.h>
|
||||
#include <U8g2lib.h>
|
||||
|
||||
#include "../features.h"
|
||||
#include "Sonde.h"
|
||||
#include "RS41.h"
|
||||
#if FEATURE_RS92
|
||||
#include "RS92.h"
|
||||
#endif
|
||||
#include "DFM.h"
|
||||
#include "M10M20.h"
|
||||
#include "MP3H.h"
|
||||
|
@ -25,6 +28,7 @@ const char *sondeTypeStr[NSondeTypes] = { "DFM ", "RS41", "RS92", "Mxx ", "M10 "
|
|||
const char *sondeTypeLongStr[NSondeTypes] = { "DFM (all)", "RS41", "RS92", "M10/M20", "M10 ", "M20 ", "MP3-H1" };
|
||||
const char sondeTypeChar[NSondeTypes] = { 'D', '4', 'R', 'M', 'M', '2', '3' };
|
||||
const char *manufacturer_string[]={"Graw", "Vaisala", "Vaisala", "Meteomodem", "Meteomodem", "Meteomodem", "Meteo-Radiy"};
|
||||
const char *DEFEPH="gssc.esa.int/gnss/data/daily/%1$04d/brdc/brdc%2$03d0.%3$02dn.gz";
|
||||
|
||||
int fingerprintValue[]={ 17, 31, 64, 4, 55, 48, 23, 128+23, 119, 128+119, -1 };
|
||||
const char *fingerprintText[]={
|
||||
|
@ -255,7 +259,7 @@ void Sonde::defaultConfig() {
|
|||
config.tcpfeed.port = 12345;
|
||||
config.tcpfeed.highrate = 10;
|
||||
config.kisstnc.active = 0;
|
||||
strcpy(config.ephftp,"igs.bkg.bund.de/IGS/BRDC/");
|
||||
strcpy(config.ephftp,DEFEPH);
|
||||
|
||||
config.mqtt.active = 0;
|
||||
strcpy(config.mqtt.id, "rdz_sonde_server");
|
||||
|
@ -275,6 +279,8 @@ void Sonde::checkConfig() {
|
|||
if(config.sondehub.fimaxage>48) config.sondehub.fimaxage = 48;
|
||||
if(config.sondehub.fimaxdist==0) config.sondehub.fimaxdist = 150;
|
||||
if(config.sondehub.fimaxage==0) config.sondehub.fimaxage = 2;
|
||||
// auto upgrade config to new version with format arguments in string
|
||||
if(!strchr(sonde.config.ephftp,'%')) strcpy(sonde.config.ephftp,DEFEPH);
|
||||
}
|
||||
void Sonde::setConfig(const char *cfg) {
|
||||
while(*cfg==' '||*cfg=='\t') cfg++;
|
||||
|
@ -431,7 +437,9 @@ void Sonde::setup() {
|
|||
dfm.setup( sondeList[rxtask.currentSonde].freq * 1000000, sondeList[rxtask.currentSonde].type );
|
||||
break;
|
||||
case STYPE_RS92:
|
||||
#if FEATURE_RS92
|
||||
rs92.setup( sondeList[rxtask.currentSonde].freq * 1000000);
|
||||
#endif
|
||||
break;
|
||||
case STYPE_M10:
|
||||
case STYPE_M20:
|
||||
|
@ -462,7 +470,9 @@ void Sonde::receive() {
|
|||
res = rs41.receive();
|
||||
break;
|
||||
case STYPE_RS92:
|
||||
#if FEATURE_RS92
|
||||
res = rs92.receive();
|
||||
#endif
|
||||
break;
|
||||
case STYPE_M10:
|
||||
case STYPE_M20:
|
||||
|
@ -562,7 +572,9 @@ rxloop:
|
|||
rs41.waitRXcomplete();
|
||||
break;
|
||||
case STYPE_RS92:
|
||||
#if FEATURE_RS92
|
||||
rs92.waitRXcomplete();
|
||||
#endif
|
||||
break;
|
||||
case STYPE_M10:
|
||||
case STYPE_M20:
|
||||
|
|
|
@ -191,6 +191,7 @@ struct st_feedinfo {
|
|||
int lowrate;
|
||||
int highrate;
|
||||
int lowlimit;
|
||||
int timeout;
|
||||
};
|
||||
|
||||
// maybe extend for external Bluetooth interface?
|
||||
|
@ -281,7 +282,7 @@ typedef struct st_rdzconfig {
|
|||
struct st_dfmconfig dfm;
|
||||
struct st_m10m20config m10m20;
|
||||
struct st_mp3hconfig mp3h;
|
||||
char ephftp[40];
|
||||
char ephftp[80];
|
||||
// data feed configuration
|
||||
// for now, one feed for each type is enough, but might get extended to more?
|
||||
char call[10]; // APRS callsign
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -1,471 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-3.0
|
||||
// original source: https://github.com/Nkawu/TFT22_ILI9225
|
||||
|
||||
#ifndef TFT22_ILI9225_h
|
||||
#define TFT22_ILI9225_h
|
||||
|
||||
#ifdef __STM32F1__
|
||||
#define ARDUINO_STM32_FEATHER
|
||||
#define PROGMEM
|
||||
// if 'SPI_CHANNEL' is not defined, 'SPI' is used, only valid for STM32F1
|
||||
//#define SPI_CHANNEL SPI_2
|
||||
#endif
|
||||
|
||||
#define USE_STRING_CLASS
|
||||
|
||||
#ifdef USE_STRING_CLASS
|
||||
#define STRING String
|
||||
#else
|
||||
#define STRING const char *
|
||||
#endif
|
||||
|
||||
#if ARDUINO >= 100
|
||||
#include "Arduino.h"
|
||||
#else
|
||||
#include "WProgram.h"
|
||||
#endif
|
||||
#include <SPI.h>
|
||||
#include "gfxfont.h"
|
||||
|
||||
#if defined(ARDUINO_STM32_FEATHER) || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1) || defined(STM32F1)
|
||||
typedef volatile uint32 RwReg;
|
||||
#endif
|
||||
#if defined(ARDUINO_FEATHER52)
|
||||
typedef volatile uint32_t RwReg;
|
||||
#endif
|
||||
|
||||
/* ILI9225 screen size */
|
||||
#define ILI9225_LCD_WIDTH 176
|
||||
#define ILI9225_LCD_HEIGHT 220
|
||||
|
||||
/* ILI9225 LCD Registers */
|
||||
#define ILI9225_DRIVER_OUTPUT_CTRL (0x01u) // Driver Output Control
|
||||
#define ILI9225_LCD_AC_DRIVING_CTRL (0x02u) // LCD AC Driving Control
|
||||
#define ILI9225_ENTRY_MODE (0x03u) // Entry Mode
|
||||
#define ILI9225_DISP_CTRL1 (0x07u) // Display Control 1
|
||||
#define ILI9225_BLANK_PERIOD_CTRL1 (0x08u) // Blank Period Control
|
||||
#define ILI9225_FRAME_CYCLE_CTRL (0x0Bu) // Frame Cycle Control
|
||||
#define ILI9225_INTERFACE_CTRL (0x0Cu) // Interface Control
|
||||
#define ILI9225_OSC_CTRL (0x0Fu) // Osc Control
|
||||
#define ILI9225_POWER_CTRL1 (0x10u) // Power Control 1
|
||||
#define ILI9225_POWER_CTRL2 (0x11u) // Power Control 2
|
||||
#define ILI9225_POWER_CTRL3 (0x12u) // Power Control 3
|
||||
#define ILI9225_POWER_CTRL4 (0x13u) // Power Control 4
|
||||
#define ILI9225_POWER_CTRL5 (0x14u) // Power Control 5
|
||||
#define ILI9225_VCI_RECYCLING (0x15u) // VCI Recycling
|
||||
#define ILI9225_RAM_ADDR_SET1 (0x20u) // Horizontal GRAM Address Set
|
||||
#define ILI9225_RAM_ADDR_SET2 (0x21u) // Vertical GRAM Address Set
|
||||
#define ILI9225_GRAM_DATA_REG (0x22u) // GRAM Data Register
|
||||
#define ILI9225_GATE_SCAN_CTRL (0x30u) // Gate Scan Control Register
|
||||
#define ILI9225_VERTICAL_SCROLL_CTRL1 (0x31u) // Vertical Scroll Control 1 Register
|
||||
#define ILI9225_VERTICAL_SCROLL_CTRL2 (0x32u) // Vertical Scroll Control 2 Register
|
||||
#define ILI9225_VERTICAL_SCROLL_CTRL3 (0x33u) // Vertical Scroll Control 3 Register
|
||||
#define ILI9225_PARTIAL_DRIVING_POS1 (0x34u) // Partial Driving Position 1 Register
|
||||
#define ILI9225_PARTIAL_DRIVING_POS2 (0x35u) // Partial Driving Position 2 Register
|
||||
#define ILI9225_HORIZONTAL_WINDOW_ADDR1 (0x36u) // Horizontal Address Start Position
|
||||
#define ILI9225_HORIZONTAL_WINDOW_ADDR2 (0x37u) // Horizontal Address End Position
|
||||
#define ILI9225_VERTICAL_WINDOW_ADDR1 (0x38u) // Vertical Address Start Position
|
||||
#define ILI9225_VERTICAL_WINDOW_ADDR2 (0x39u) // Vertical Address End Position
|
||||
#define ILI9225_GAMMA_CTRL1 (0x50u) // Gamma Control 1
|
||||
#define ILI9225_GAMMA_CTRL2 (0x51u) // Gamma Control 2
|
||||
#define ILI9225_GAMMA_CTRL3 (0x52u) // Gamma Control 3
|
||||
#define ILI9225_GAMMA_CTRL4 (0x53u) // Gamma Control 4
|
||||
#define ILI9225_GAMMA_CTRL5 (0x54u) // Gamma Control 5
|
||||
#define ILI9225_GAMMA_CTRL6 (0x55u) // Gamma Control 6
|
||||
#define ILI9225_GAMMA_CTRL7 (0x56u) // Gamma Control 7
|
||||
#define ILI9225_GAMMA_CTRL8 (0x57u) // Gamma Control 8
|
||||
#define ILI9225_GAMMA_CTRL9 (0x58u) // Gamma Control 9
|
||||
#define ILI9225_GAMMA_CTRL10 (0x59u) // Gamma Control 10
|
||||
|
||||
#define ILI9225C_INVOFF 0x20
|
||||
#define ILI9225C_INVON 0x21
|
||||
|
||||
// autoincrement modes (register ILI9225_ENTRY_MODE, bit 5..3 )
|
||||
enum autoIncMode_t { R2L_BottomUp, BottomUp_R2L, L2R_BottomUp, BottomUp_L2R, R2L_TopDown, TopDown_R2L, L2R_TopDown, TopDown_L2R };
|
||||
|
||||
/* RGB 16-bit color table definition (RG565) */
|
||||
#define COLOR_BLACK 0x0000 /* 0, 0, 0 */
|
||||
#define COLOR_WHITE 0xFFFF /* 255, 255, 255 */
|
||||
#define COLOR_BLUE 0x001F /* 0, 0, 255 */
|
||||
#define COLOR_GREEN 0x07E0 /* 0, 255, 0 */
|
||||
#define COLOR_RED 0xF800 /* 255, 0, 0 */
|
||||
#define COLOR_NAVY 0x000F /* 0, 0, 128 */
|
||||
#define COLOR_DARKBLUE 0x0011 /* 0, 0, 139 */
|
||||
#define COLOR_DARKGREEN 0x03E0 /* 0, 128, 0 */
|
||||
#define COLOR_DARKCYAN 0x03EF /* 0, 128, 128 */
|
||||
#define COLOR_CYAN 0x07FF /* 0, 255, 255 */
|
||||
#define COLOR_TURQUOISE 0x471A /* 64, 224, 208 */
|
||||
#define COLOR_INDIGO 0x4810 /* 75, 0, 130 */
|
||||
#define COLOR_DARKRED 0x8000 /* 128, 0, 0 */
|
||||
#define COLOR_OLIVE 0x7BE0 /* 128, 128, 0 */
|
||||
#define COLOR_GRAY 0x8410 /* 128, 128, 128 */
|
||||
#define COLOR_GREY 0x8410 /* 128, 128, 128 */
|
||||
#define COLOR_SKYBLUE 0x867D /* 135, 206, 235 */
|
||||
#define COLOR_BLUEVIOLET 0x895C /* 138, 43, 226 */
|
||||
#define COLOR_LIGHTGREEN 0x9772 /* 144, 238, 144 */
|
||||
#define COLOR_DARKVIOLET 0x901A /* 148, 0, 211 */
|
||||
#define COLOR_YELLOWGREEN 0x9E66 /* 154, 205, 50 */
|
||||
#define COLOR_BROWN 0xA145 /* 165, 42, 42 */
|
||||
#define COLOR_DARKGRAY 0x7BEF /* 128, 128, 128 */
|
||||
#define COLOR_DARKGREY 0x7BEF /* 128, 128, 128 */
|
||||
#define COLOR_SIENNA 0xA285 /* 160, 82, 45 */
|
||||
#define COLOR_LIGHTBLUE 0xAEDC /* 172, 216, 230 */
|
||||
#define COLOR_GREENYELLOW 0xAFE5 /* 173, 255, 47 */
|
||||
#define COLOR_SILVER 0xC618 /* 192, 192, 192 */
|
||||
#define COLOR_LIGHTGRAY 0xC618 /* 192, 192, 192 */
|
||||
#define COLOR_LIGHTGREY 0xC618 /* 192, 192, 192 */
|
||||
#define COLOR_LIGHTCYAN 0xE7FF /* 224, 255, 255 */
|
||||
#define COLOR_VIOLET 0xEC1D /* 238, 130, 238 */
|
||||
#define COLOR_AZUR 0xF7FF /* 240, 255, 255 */
|
||||
#define COLOR_BEIGE 0xF7BB /* 245, 245, 220 */
|
||||
#define COLOR_MAGENTA 0xF81F /* 255, 0, 255 */
|
||||
#define COLOR_TOMATO 0xFB08 /* 255, 99, 71 */
|
||||
#define COLOR_GOLD 0xFEA0 /* 255, 215, 0 */
|
||||
#define COLOR_ORANGE 0xFD20 /* 255, 165, 0 */
|
||||
#define COLOR_SNOW 0xFFDF /* 255, 250, 250 */
|
||||
#define COLOR_YELLOW 0xFFE0 /* 255, 255, 0 */
|
||||
|
||||
|
||||
/* Font defines */
|
||||
#define FONT_HEADER_SIZE 4 // 1: pixel width of 1 font character, 2: pixel height,
|
||||
#define readFontByte(x) pgm_read_byte(&cfont.font[x])
|
||||
|
||||
extern uint8_t Terminal6x8[];
|
||||
extern uint8_t Terminal11x16[];
|
||||
extern uint8_t Terminal12x16[];
|
||||
extern uint8_t Trebuchet_MS16x21[];
|
||||
|
||||
struct _currentFont
|
||||
{
|
||||
uint8_t* font;
|
||||
uint8_t width;
|
||||
uint8_t height;
|
||||
uint8_t offset;
|
||||
uint8_t numchars;
|
||||
uint8_t nbrows;
|
||||
bool monoSp;
|
||||
};
|
||||
#define MONOSPACE 1
|
||||
|
||||
#if defined (ARDUINO_STM32_FEATHER)
|
||||
#undef USE_FAST_PINIO
|
||||
#elif defined (__AVR__) || defined(TEENSYDUINO) || defined(ESP8266) || defined(__arm__)
|
||||
#define USE_FAST_PINIO
|
||||
#endif
|
||||
|
||||
/// Main and core class
|
||||
class TFT22_ILI9225 {
|
||||
|
||||
public:
|
||||
|
||||
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t SDI, int8_t CLK, int8_t LED);
|
||||
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t LED);
|
||||
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t SDI, int8_t CLK, int8_t LED, uint8_t brightness);
|
||||
TFT22_ILI9225(int8_t RST, int8_t RS, int8_t CS, int8_t LED, uint8_t brightness);
|
||||
|
||||
/// Initialization
|
||||
#ifndef ESP32
|
||||
void begin(void);
|
||||
#else
|
||||
void begin(SPIClass &spi=SPI);
|
||||
#endif
|
||||
|
||||
/// Clear the screen
|
||||
void clear(void);
|
||||
|
||||
/// Invert screen
|
||||
/// @param flag true to invert, false for normal screen
|
||||
void invert(boolean flag);
|
||||
|
||||
/// Switch backlight on or off
|
||||
/// @param flag true=on, false=off
|
||||
void setBacklight(boolean flag);
|
||||
|
||||
/// Set backlight brightness
|
||||
/// @param brightness sets backlight brightness 0-255
|
||||
void setBacklightBrightness(uint8_t brightness);
|
||||
|
||||
/// Switch display on or off
|
||||
/// @param flag true=on, false=off
|
||||
void setDisplay(boolean flag);
|
||||
|
||||
/// Set orientation
|
||||
/// @param orientation orientation, 0=portrait, 1=right rotated landscape, 2=reverse portrait, 3=left rotated landscape
|
||||
void setOrientation(uint8_t orientation);
|
||||
|
||||
/// Get orientation
|
||||
/// @return orientation orientation, 0=portrait, 1=right rotated landscape, 2=reverse portrait, 3=left rotated landscape
|
||||
uint8_t getOrientation(void);
|
||||
|
||||
/// Font size, x-axis
|
||||
/// @return horizontal size of current font, in pixels
|
||||
// uint8_t fontX(void);
|
||||
|
||||
/// Font size, y-axis
|
||||
/// @return vertical size of current font, in pixels
|
||||
// uint8_t fontY(void);
|
||||
|
||||
/// Screen size, x-axis
|
||||
/// @return horizontal size of the screen, in pixels
|
||||
/// @note 240 means 240 pixels and thus 0..239 coordinates (decimal)
|
||||
uint16_t maxX(void);
|
||||
|
||||
/// Screen size, y-axis
|
||||
/// @return vertical size of the screen, in pixels
|
||||
/// @note 220 means 220 pixels and thus 0..219 coordinates (decimal)
|
||||
uint16_t maxY(void);
|
||||
|
||||
/// Draw circle
|
||||
/// @param x0 center, point coordinate, x-axis
|
||||
/// @param y0 center, point coordinate, y-axis
|
||||
/// @param radius radius
|
||||
/// @param color 16-bit color
|
||||
void drawCircle(uint16_t x0, uint16_t y0, uint16_t radius, uint16_t color);
|
||||
|
||||
/// Draw solid circle
|
||||
/// @param x0 center, point coordinate, x-axis
|
||||
/// @param y0 center, point coordinate, y-axis
|
||||
/// @param radius radius
|
||||
/// @param color 16-bit color
|
||||
void fillCircle(uint8_t x0, uint8_t y0, uint8_t radius, uint16_t color);
|
||||
|
||||
/// Set background color
|
||||
/// @param color background color, default=black
|
||||
void setBackgroundColor(uint16_t color = COLOR_BLACK);
|
||||
|
||||
/// Draw line, rectangle coordinates
|
||||
/// @param x1 start point coordinate, x-axis
|
||||
/// @param y1 start point coordinate, y-axis
|
||||
/// @param x2 end point coordinate, x-axis
|
||||
/// @param y2 end point coordinate, y-axis
|
||||
/// @param color 16-bit color
|
||||
void drawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
|
||||
|
||||
/// Draw rectangle, rectangle coordinates
|
||||
/// @param x1 top left coordinate, x-axis
|
||||
/// @param y1 top left coordinate, y-axis
|
||||
/// @param x2 bottom right coordinate, x-axis
|
||||
/// @param y2 bottom right coordinate, y-axis
|
||||
/// @param color 16-bit color
|
||||
void drawRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
|
||||
|
||||
/// Draw solid rectangle, rectangle coordinates
|
||||
/// @param x1 top left coordinate, x-axis
|
||||
/// @param y1 top left coordinate, y-axis
|
||||
/// @param x2 bottom right coordinate, x-axis
|
||||
/// @param y2 bottom right coordinate, y-axis
|
||||
/// @param color 16-bit color
|
||||
void fillRectangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
|
||||
|
||||
/// Draw pixel
|
||||
/// @param x1 point coordinate, x-axis
|
||||
/// @param y1 point coordinate, y-axis
|
||||
/// @param color 16-bit color
|
||||
void drawPixel(uint16_t x1, uint16_t y1, uint16_t color);
|
||||
|
||||
/// Draw ASCII Text (pixel coordinates)
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param s text string
|
||||
/// @param color 16-bit color, default=white
|
||||
/// @return x-position behind text
|
||||
uint16_t drawText(uint16_t x, uint16_t y, STRING s, uint16_t color = COLOR_WHITE);
|
||||
|
||||
/// width of an ASCII Text (pixel )
|
||||
/// @param s text string
|
||||
uint16_t getTextWidth( STRING s ) ;
|
||||
|
||||
/// Calculate 16-bit color from 8-bit Red-Green-Blue components
|
||||
/// @param red red component, 0x00..0xff
|
||||
/// @param green green component, 0x00..0xff
|
||||
/// @param blue blue component, 0x00..0xff
|
||||
/// @return 16-bit color
|
||||
uint16_t setColor(uint8_t red, uint8_t green, uint8_t blue);
|
||||
|
||||
/// Calculate 8-bit Red-Green-Blue components from 16-bit color
|
||||
/// @param rgb 16-bit color
|
||||
/// @param red red component, 0x00..0xff
|
||||
/// @param green green component, 0x00..0xff
|
||||
/// @param blue blue component, 0x00..0xff
|
||||
void splitColor(uint16_t rgb, uint8_t &red, uint8_t &green, uint8_t &blue);
|
||||
|
||||
/// Draw triangle, triangle coordinates
|
||||
/// @param x1 corner 1 coordinate, x-axis
|
||||
/// @param y1 corner 1 coordinate, y-axis
|
||||
/// @param x2 corner 2 coordinate, x-axis
|
||||
/// @param y2 corner 2 coordinate, y-axis
|
||||
/// @param x3 corner 3 coordinate, x-axis
|
||||
/// @param y3 corner 3 coordinate, y-axis
|
||||
/// @param color 16-bit color
|
||||
void drawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color);
|
||||
|
||||
/// Draw solid triangle, triangle coordinates
|
||||
/// @param x1 corner 1 coordinate, x-axis
|
||||
/// @param y1 corner 1 coordinate, y-axis
|
||||
/// @param x2 corner 2 coordinate, x-axis
|
||||
/// @param y2 corner 2 coordinate, y-axis
|
||||
/// @param x3 corner 3 coordinate, x-axis
|
||||
/// @param y3 corner 3 coordinate, y-axis
|
||||
/// @param color 16-bit color
|
||||
void fillTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, uint16_t color);
|
||||
|
||||
/// Set current font
|
||||
/// @param font Font name
|
||||
void setFont(uint8_t* font, bool monoSp=false ); // default = proportional
|
||||
|
||||
/// Get current font
|
||||
_currentFont getFont();
|
||||
|
||||
/// Draw single character (pixel coordinates)
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param ch ASCII character
|
||||
/// @param color 16-bit color, default=white
|
||||
/// @return width of character in display pixels
|
||||
uint16_t drawChar(uint16_t x, uint16_t y, uint16_t ch, uint16_t color = COLOR_WHITE);
|
||||
|
||||
/// width of an ASCII character (pixel )
|
||||
/// @param ch ASCII character
|
||||
uint16_t getCharWidth( uint16_t ch ) ;
|
||||
|
||||
/// Draw bitmap
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param bitmap
|
||||
/// @param w width
|
||||
/// @param h height
|
||||
/// @param color 16-bit color, default=white
|
||||
/// @param bg 16-bit color, background
|
||||
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
|
||||
void drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
|
||||
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
|
||||
void drawBitmap(int16_t x, int16_t y, uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
|
||||
|
||||
void drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color);
|
||||
void drawXBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg);
|
||||
|
||||
/// Draw bitmap
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param bitmap, 2D 16bit color bitmap
|
||||
/// @param w width
|
||||
/// @param h height
|
||||
void drawBitmap(uint16_t x, uint16_t y, const uint16_t** bitmap, int16_t w, int16_t h);
|
||||
void drawBitmap(uint16_t x, uint16_t y, uint16_t** bitmap, int16_t w, int16_t h);
|
||||
|
||||
/// Draw bitmap
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param bitmap, 1D 16bit color bitmap
|
||||
/// @param w width
|
||||
/// @param h height
|
||||
void drawBitmap(uint16_t x, uint16_t y, const uint16_t* bitmap, int16_t w, int16_t h);
|
||||
void drawBitmap(uint16_t x, uint16_t y, uint16_t* bitmap, int16_t w, int16_t h);
|
||||
|
||||
/// Set current GFX font
|
||||
/// @param f GFX font name defined in include file
|
||||
void setGFXFont(const GFXfont *f = NULL);
|
||||
|
||||
/// Draw a string with the current GFX font
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param s string to print
|
||||
/// @param color 16-bit color
|
||||
void drawGFXText(int16_t x, int16_t y, STRING s, uint16_t color);
|
||||
|
||||
/// Get the width & height of a text string with the current GFX font
|
||||
/// @param str string to analyze
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param w width in pixels of string
|
||||
/// @param h height in pixels of string
|
||||
void getGFXTextExtent(STRING str, int16_t x, int16_t y, int16_t *w, int16_t *h);
|
||||
|
||||
/// Draw a single character with the current GFX font
|
||||
/// @param x point coordinate, x-axis
|
||||
/// @param y point coordinate, y-axis
|
||||
/// @param c character to draw
|
||||
/// @param color 16-bit color
|
||||
/// @return width of character in display pixels
|
||||
uint16_t drawGFXChar(int16_t x, int16_t y, unsigned char c, uint16_t color);
|
||||
|
||||
uint16_t drawGFXcharBM(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t *bm, int bmwidth, int bmheight);
|
||||
|
||||
void getGFXCharExtent(uint8_t c, int16_t *gw, int16_t *gh, int16_t *xa);
|
||||
|
||||
void setModeFlip(uint16_t m);
|
||||
|
||||
private:
|
||||
|
||||
void _spiWrite(uint8_t v);
|
||||
void _spiWrite16(uint16_t v);
|
||||
void _spiWriteCommand(uint8_t c);
|
||||
void _spiWriteData(uint8_t d);
|
||||
|
||||
void _swap(uint16_t &a, uint16_t &b);
|
||||
void _setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
|
||||
void _setWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, autoIncMode_t mode);
|
||||
void _resetWindow();
|
||||
void _drawBitmap(int16_t x, int16_t y, const uint8_t *bitmap, int16_t w, int16_t h,
|
||||
uint16_t color, uint16_t bg, bool transparent, bool progmem, bool Xbit );
|
||||
void _orientCoordinates(uint16_t &x1, uint16_t &y1);
|
||||
void _writeRegister(uint16_t reg, uint16_t data);
|
||||
void _writeData(uint8_t HI, uint8_t LO);
|
||||
void _writeData16(uint16_t HILO);
|
||||
void _writeCommand(uint8_t HI, uint8_t LO);
|
||||
void _writeCommand16(uint16_t HILO);
|
||||
uint16_t _maxX, _maxY, _bgColor;
|
||||
|
||||
#if defined (__AVR__) || defined(TEENSYDUINO)
|
||||
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
|
||||
#ifdef USE_FAST_PINIO
|
||||
volatile uint8_t *mosiport, *clkport, *dcport, *rsport, *csport;
|
||||
uint8_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
|
||||
#endif
|
||||
#elif defined (__arm__)
|
||||
int32_t _rst, _rs, _cs, _sdi, _clk, _led;
|
||||
#ifdef USE_FAST_PINIO
|
||||
volatile RwReg *mosiport, *clkport, *dcport, *rsport, *csport;
|
||||
uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
|
||||
#endif
|
||||
#elif defined (ESP8266) || defined (ESP32)
|
||||
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
|
||||
#ifdef USE_FAST_PINIO
|
||||
volatile uint32_t *mosiport, *clkport, *dcport, *rsport, *csport;
|
||||
uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask;
|
||||
#endif
|
||||
#else
|
||||
int8_t _rst, _rs, _cs, _sdi, _clk, _led;
|
||||
#endif
|
||||
|
||||
uint8_t _orientation, _brightness;
|
||||
uint16_t _modeFlip;
|
||||
|
||||
// correspondig modes if orientation changed:
|
||||
const autoIncMode_t modeTab [3][8] = {
|
||||
// { R2L_BottomUp, BottomUp_R2L, L2R_BottomUp, BottomUp_L2R, R2L_TopDown, TopDown_R2L, L2R_TopDown, TopDown_L2R }//
|
||||
/* 90° */ { BottomUp_L2R, L2R_BottomUp, TopDown_L2R, L2R_TopDown, BottomUp_R2L, R2L_BottomUp, TopDown_R2L, R2L_TopDown },
|
||||
/*180° */ { L2R_TopDown , TopDown_L2R, R2L_TopDown, TopDown_R2L, L2R_BottomUp, BottomUp_L2R, R2L_BottomUp, BottomUp_R2L},
|
||||
/*270° */ { TopDown_R2L , R2L_TopDown, BottomUp_R2L, R2L_BottomUp, TopDown_L2R, L2R_TopDown, BottomUp_L2R, L2R_BottomUp}
|
||||
};
|
||||
|
||||
|
||||
bool hwSPI, blState;
|
||||
|
||||
_currentFont cfont;
|
||||
|
||||
#ifdef ESP32
|
||||
SPIClass _spi;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
|
||||
uint32_t writeFunctionLevel;
|
||||
void startWrite(void);
|
||||
void endWrite(void);
|
||||
|
||||
|
||||
GFXfont *gfxFont;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,6 +16,7 @@
|
|||
#include <unistd.h>
|
||||
#include <inttypes.h>
|
||||
#include "aprs.h"
|
||||
#include "RS41.h"
|
||||
|
||||
extern const char *version_name;
|
||||
extern const char *version_id;
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
|
||||
#ifndef _aprs_h
|
||||
#define _aprs_h
|
||||
|
||||
#include "Sonde.h"
|
||||
#include "RS41.h"
|
||||
|
||||
|
||||
#define APRS_MAXLEN 201
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
#include "../features.h"
|
||||
|
||||
#if FEATURE_APRS
|
||||
|
||||
#include "conn-aprs.h"
|
||||
#include "aprs.h"
|
||||
#include "posinfo.h"
|
||||
#include <ESPmDNS.h>
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <lwip/dns.h>
|
||||
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
// KISS over TCP for communicating with APRSdroid
|
||||
static WiFiServer tncserver(14580);
|
||||
static WiFiClient tncclient;
|
||||
|
||||
// APRS over TCP for radiosondy.info etc
|
||||
static int tcpclient = 0;
|
||||
enum { TCS_DISCONNECTED, TCS_DNSLOOKUP, TCS_DNSRESOLVED, TCS_CONNECTING, TCS_LOGIN, TCS_CONNECTED };
|
||||
static uint8_t tcpclient_state = TCS_DISCONNECTED;
|
||||
ip_addr_t tcpclient_ipaddr;
|
||||
|
||||
extern const char *version_name;
|
||||
extern const char *version_id;
|
||||
|
||||
extern WiFiUDP udp;
|
||||
|
||||
static unsigned long last_in = 0;
|
||||
|
||||
void tcpclient_fsm();
|
||||
|
||||
|
||||
void ConnAPRS::init() {
|
||||
aprs_gencrctab();
|
||||
}
|
||||
|
||||
void ConnAPRS::netsetup() {
|
||||
// Setup for KISS TCP server
|
||||
if(sonde.config.kisstnc.active) {
|
||||
MDNS.addService("kiss-tnc", "tcp", 14580);
|
||||
tncserver.begin();
|
||||
}
|
||||
|
||||
if(sonde.config.tcpfeed.active) {
|
||||
// start the FSM
|
||||
tcpclient_fsm();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnAPRS::updateSonde( SondeInfo *si ) {
|
||||
// prepare data (for UDP and TCP output)
|
||||
char *str = aprs_senddata(si, sonde.config.call, sonde.config.objcall, sonde.config.udpfeed.symbol);
|
||||
|
||||
// Output via AXUDP
|
||||
if(sonde.config.udpfeed.active) {
|
||||
char raw[201];
|
||||
int rawlen = aprsstr_mon2raw(str, raw, APRS_MAXLEN);
|
||||
Serial.println("Sending AXUDP");
|
||||
//Serial.println(raw);
|
||||
udp.beginPacket(sonde.config.udpfeed.host, sonde.config.udpfeed.port);
|
||||
udp.write((const uint8_t *)raw, rawlen);
|
||||
udp.endPacket();
|
||||
}
|
||||
// KISS via TCP (incoming connection, e.g. from APRSdroid
|
||||
if (tncclient.connected()) {
|
||||
Serial.println("Sending position via TCP");
|
||||
char raw[201];
|
||||
int rawlen = aprsstr_mon2kiss(str, raw, APRS_MAXLEN);
|
||||
Serial.print("sending: "); Serial.println(raw);
|
||||
tncclient.write(raw, rawlen);
|
||||
}
|
||||
// APRS via TCP (outgoing connection to aprs-is, e.g. radiosonde.info or wettersonde.net
|
||||
if (sonde.config.tcpfeed.active) {
|
||||
static unsigned long lasttcp = 0;
|
||||
tcpclient_fsm();
|
||||
if(tcpclient_state == TCS_CONNECTED) {
|
||||
unsigned long now = millis();
|
||||
long tts = sonde.config.tcpfeed.highrate * 1000L - (now-lasttcp);
|
||||
Serial.printf("aprs: now-last = %ld\n", (now - lasttcp));
|
||||
if ( tts < 0 ) {
|
||||
strcat(str, "\r\n");
|
||||
Serial.printf("Sending APRS: %s",str);
|
||||
write(tcpclient, str, strlen(str));
|
||||
lasttcp = now;
|
||||
} else {
|
||||
Serial.printf("Sending APRS in %d s\n", (int)(tts/1000));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define APRS_TIMEOUT 25000
|
||||
|
||||
void ConnAPRS::updateStation( PosInfo *pi ) {
|
||||
// This funciton is called peridocally.
|
||||
|
||||
// We check for stalled connection and possibly close it
|
||||
Serial.printf("last_in - now: %ld\n", millis() - last_in);
|
||||
if ( sonde.config.tcpfeed.timeout > 0) {
|
||||
if ( last_in && ( (millis() - last_in) > sonde.config.tcpfeed.timeout*1000 ) ) {
|
||||
Serial.println("APRS timeout - closing connection");
|
||||
close(tcpclient);
|
||||
tcpclient_state = TCS_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
// If available, read data from tcpclient; then send update (if its time for that)
|
||||
tcpclient_fsm();
|
||||
if(sonde.config.tcpfeed.active) {
|
||||
aprs_station_update();
|
||||
}
|
||||
|
||||
// We check for new connections or new data (tnc port)
|
||||
if (!tncclient.connected()) {
|
||||
tncclient = tncserver.available();
|
||||
if (tncclient.connected()) {
|
||||
Serial.println("new TCP KISS connection");
|
||||
}
|
||||
}
|
||||
if (tncclient.available()) {
|
||||
Serial.print("TCP KISS socket: recevied ");
|
||||
while (tncclient.available()) {
|
||||
Serial.print(tncclient.read()); // Check if we receive anything from from APRSdroid
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
}
|
||||
|
||||
void ConnAPRS::aprs_station_update() {
|
||||
int chase = sonde.config.chase;
|
||||
// automatically decided if CHASE or FIXED mode is used (for config AUTO)
|
||||
if (chase == SH_LOC_AUTO) {
|
||||
if (posInfo.chase) chase = SH_LOC_CHASE; else chase = SH_LOC_FIXED;
|
||||
}
|
||||
unsigned long time_now = millis();
|
||||
unsigned long time_delta = time_now - time_last_aprs_update;
|
||||
unsigned long update_time = (chase == SH_LOC_CHASE) ? APRS_MOBILE_STATION_UPDATE_TIME : APRS_STATION_UPDATE_TIME;
|
||||
long tts = update_time - time_delta;
|
||||
Serial.printf("aprs_statio_update due in %d s", (int)(tts/1000));
|
||||
if (tts>0) return;
|
||||
|
||||
float lat, lon;
|
||||
if (chase == SH_LOC_FIXED) {
|
||||
// fixed location
|
||||
lat = sonde.config.rxlat;
|
||||
lon = sonde.config.rxlon;
|
||||
if (isnan(lat) || isnan(lon)) return;
|
||||
} else {
|
||||
if (gpsPos.valid) {
|
||||
lat = gpsPos.lat;
|
||||
lon = gpsPos.lon;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
char *bcn = aprs_send_beacon(sonde.config.call, lat, lon, sonde.config.beaconsym + ((chase == SH_LOC_CHASE) ? 2 : 0), sonde.config.comment);
|
||||
tcpclient_fsm();
|
||||
if(tcpclient_state == TCS_CONNECTED) {
|
||||
strcat(bcn, "\r\n");
|
||||
Serial.printf("APRS TCP BEACON: %s", bcn);
|
||||
write(tcpclient, bcn, strlen(bcn));
|
||||
time_last_aprs_update = time_now;
|
||||
}
|
||||
}
|
||||
|
||||
static void _tcp_dns_found(const char * name, const ip_addr_t *ipaddr, void * arg) {
|
||||
if (ipaddr) {
|
||||
tcpclient_ipaddr = *ipaddr;
|
||||
tcpclient_state = TCS_DNSRESOLVED; // DNS lookup success
|
||||
} else {
|
||||
memset(&tcpclient_ipaddr, 0, sizeof(tcpclient_ipaddr));
|
||||
tcpclient_state = TCS_DISCONNECTED; // DNS lookup failed
|
||||
}
|
||||
}
|
||||
|
||||
void tcpclient_sendlogin() {
|
||||
char buf[128];
|
||||
snprintf(buf, 128, "user %s pass %d vers %s %s\r\n", sonde.config.call, sonde.config.passcode, version_name, version_id);
|
||||
int res = write(tcpclient, buf, strlen(buf));
|
||||
Serial.printf("APRS login: %s, res=%d\n", buf, res);
|
||||
last_in = millis();
|
||||
if(res<=0) {
|
||||
close(tcpclient);
|
||||
tcpclient_state = TCS_DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
void tcpclient_fsm() {
|
||||
if(!sonde.config.tcpfeed.active)
|
||||
return;
|
||||
Serial.printf("TCS: %d\n", tcpclient_state);
|
||||
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(tcpclient, &fdset);
|
||||
fd_set fdeset;
|
||||
FD_ZERO(&fdeset);
|
||||
FD_SET(tcpclient, &fdeset);
|
||||
|
||||
struct timeval selto = {0};
|
||||
int res;
|
||||
|
||||
switch(tcpclient_state) {
|
||||
case TCS_DISCONNECTED:
|
||||
/* We are disconnected. Try to connect, starting with a DNS lookup */
|
||||
{
|
||||
// Restart timeout
|
||||
last_in = millis();
|
||||
err_t res = dns_gethostbyname( sonde.config.tcpfeed.host, &tcpclient_ipaddr, /*(dns_found_callback)*/_tcp_dns_found, NULL );
|
||||
|
||||
if(res == ERR_OK) { // Returns immediately of host is IP or in cache
|
||||
tcpclient_state = TCS_DNSRESOLVED;
|
||||
/* fall through */
|
||||
} else if(res == ERR_INPROGRESS) {
|
||||
tcpclient_state = TCS_DNSLOOKUP;
|
||||
break;
|
||||
} else { // failed
|
||||
tcpclient_state = TCS_DISCONNECTED;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
case TCS_DNSRESOLVED:
|
||||
{
|
||||
/* We have got the IP address, start the connection */
|
||||
tcpclient = socket(AF_INET, SOCK_STREAM, 0);
|
||||
int flags = fcntl(tcpclient, F_GETFL);
|
||||
if (fcntl(tcpclient, F_SETFL, flags | O_NONBLOCK) == -1) {
|
||||
Serial.println("Setting O_NONBLOCK failed");
|
||||
}
|
||||
|
||||
struct sockaddr_in sock_info;
|
||||
memset(&sock_info, 0, sizeof(struct sockaddr_in));
|
||||
sock_info.sin_family = AF_INET;
|
||||
sock_info.sin_addr.s_addr = tcpclient_ipaddr.u_addr.ip4.addr;
|
||||
sock_info.sin_port = htons( sonde.config.tcpfeed.port );
|
||||
err_t res = connect(tcpclient, (struct sockaddr *)&sock_info, sizeof(sock_info));
|
||||
if(res) {
|
||||
if (errno == EINPROGRESS) { // Should be the usual case, go to connecting state
|
||||
tcpclient_state = TCS_CONNECTING;
|
||||
} else {
|
||||
close(tcpclient);
|
||||
tcpclient_state = TCS_DISCONNECTED;
|
||||
}
|
||||
} else {
|
||||
tcpclient_state = TCS_CONNECTED;
|
||||
tcpclient_sendlogin();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TCS_CONNECTING:
|
||||
{
|
||||
// Poll to see if we are now connected
|
||||
res = select(tcpclient+1, NULL, &fdset, &fdeset, &selto);
|
||||
if(res<0) {
|
||||
Serial.println("TNS_CONNECTING: select error");
|
||||
goto error;
|
||||
} else if (res==0) { // still pending
|
||||
break;
|
||||
}
|
||||
// Socket has become ready (or something went wrong, check for error first)
|
||||
|
||||
int sockerr;
|
||||
socklen_t len = (socklen_t)sizeof(int);
|
||||
if (getsockopt(tcpclient, SOL_SOCKET, SO_ERROR, (void*)(&sockerr), &len) < 0) {
|
||||
goto error;
|
||||
}
|
||||
Serial.printf("select returing %d. isset:%d iseset:%d sockerr:%d\n", res, FD_ISSET(tcpclient, &fdset), FD_ISSET(tcpclient, &fdeset), sockerr);
|
||||
if(sockerr) {
|
||||
Serial.printf("APRS connect error: %s\n", strerror(sockerr));
|
||||
goto error;
|
||||
}
|
||||
tcpclient_state = TCS_CONNECTED;
|
||||
tcpclient_sendlogin();
|
||||
}
|
||||
break;
|
||||
|
||||
case TCS_CONNECTED:
|
||||
{
|
||||
res = select(tcpclient+1, &fdset, NULL, NULL, &selto);
|
||||
if(res<0) {
|
||||
Serial.println("TCS_CONNECTING: select error");
|
||||
goto error;
|
||||
} else if (res==0) { // still pending
|
||||
break;
|
||||
}
|
||||
// Read data
|
||||
char buf[512+1];
|
||||
res = read(tcpclient, buf, 512);
|
||||
if(res<=0) {
|
||||
close(tcpclient);
|
||||
tcpclient_state = TCS_DISCONNECTED;
|
||||
} else {
|
||||
buf[res] = 0;
|
||||
Serial.printf("tcpclient data (len=%d):", res);
|
||||
Serial.write( (uint8_t *)buf, res );
|
||||
last_in = millis();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TCS_DNSLOOKUP:
|
||||
Serial.println("DNS lookup in progress");
|
||||
break; // DNS lookup in progress, do not do anything until callback is called, updating the state
|
||||
}
|
||||
return;
|
||||
|
||||
error:
|
||||
close(tcpclient);
|
||||
tcpclient_state = TCS_DISCONNECTED;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
ConnAPRS connAPRS;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,39 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_APRS
|
||||
|
||||
#ifndef conn_aprs_h
|
||||
#define conn_aprs_h
|
||||
|
||||
#include "conn.h"
|
||||
#include "aprs.h"
|
||||
|
||||
// Times in ms, i.e. station: 10 minutes, mobile: 20 seconds
|
||||
#define APRS_STATION_UPDATE_TIME (10*60*1000)
|
||||
#define APRS_MOBILE_STATION_UPDATE_TIME (20*1000)
|
||||
|
||||
static unsigned long time_last_aprs_update = -APRS_STATION_UPDATE_TIME;
|
||||
|
||||
|
||||
class ConnAPRS : public Conn
|
||||
{
|
||||
public:
|
||||
/* Called once on startup */
|
||||
void init();
|
||||
|
||||
/* Called whenever the network becomes available */
|
||||
void netsetup();
|
||||
|
||||
/* Called approx 1x / second (maybe only if good data is available) */
|
||||
void updateSonde( SondeInfo *si );
|
||||
|
||||
/* Called approx 1x / second* */
|
||||
void updateStation( PosInfo *pi );
|
||||
|
||||
private:
|
||||
void aprs_station_update();
|
||||
};
|
||||
|
||||
extern ConnAPRS connAPRS;
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,8 +1,20 @@
|
|||
#include "Chasemapper.h"
|
||||
#include "../features.h"
|
||||
#if FEATURE_CHASEMAPPER
|
||||
|
||||
#include "conn-chasemapper.h"
|
||||
#include <WiFiUdp.h>
|
||||
|
||||
extern const char *sondeTypeStrSH[];
|
||||
extern WiFiUDP udp;
|
||||
|
||||
int Chasemapper::send(WiFiUDP &udp, SondeInfo *si) {
|
||||
void ConnChasemapper::init() {
|
||||
}
|
||||
|
||||
void ConnChasemapper::netsetup() {
|
||||
}
|
||||
|
||||
|
||||
void ConnChasemapper::updateSonde(SondeInfo *si) {
|
||||
char buf[1024];
|
||||
struct tm tim;
|
||||
time_t t = si->d.time;
|
||||
|
@ -11,8 +23,16 @@ int Chasemapper::send(WiFiUDP &udp, SondeInfo *si) {
|
|||
if (TYPE_IS_METEO(realtype)) {
|
||||
realtype = si->d.subtype == 1 ? STYPE_M10 : STYPE_M20;
|
||||
}
|
||||
char prefix[10];
|
||||
if(realtype == STYPE_RS41) {
|
||||
prefix[0] = 0;
|
||||
}
|
||||
else {
|
||||
strncpy(prefix, sondeTypeStrSH[realtype], 10);
|
||||
strcat(prefix, "-");
|
||||
}
|
||||
sprintf(buf, "{ \"type\": \"PAYLOAD_SUMMARY\","
|
||||
"\"callsign\": \"%s\","
|
||||
"\"callsign\": \"%s%s\","
|
||||
"\"latitude\": %.5f,"
|
||||
"\"longitude\": %.5f,"
|
||||
"\"altitude\": %d,"
|
||||
|
@ -21,6 +41,7 @@ int Chasemapper::send(WiFiUDP &udp, SondeInfo *si) {
|
|||
"\"time\": \"%02d:%02d:%02d\","
|
||||
"\"model\": \"%s\","
|
||||
"\"freq\": \"%.3f MHz\"",
|
||||
prefix,
|
||||
si->d.ser,
|
||||
si->d.lat,
|
||||
si->d.lon,
|
||||
|
@ -38,6 +59,10 @@ int Chasemapper::send(WiFiUDP &udp, SondeInfo *si) {
|
|||
udp.beginPacket(sonde.config.cm.host, sonde.config.cm.port);
|
||||
udp.write((const uint8_t *)buf, strlen(buf));
|
||||
udp.endPacket();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ConnChasemapper::updateStation(PosInfo *pi) {
|
||||
}
|
||||
|
||||
ConnChasemapper connChasemapper;
|
||||
#endif
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef _CHASEMAPPER_H
|
||||
#define _CHASEMAPPER_H
|
||||
|
||||
#include "Sonde.h"
|
||||
#include "conn.h"
|
||||
|
||||
class ConnChasemapper : public Conn {
|
||||
public:
|
||||
void init();
|
||||
void netsetup();
|
||||
void updateSonde( SondeInfo *si );
|
||||
void updateStation( PosInfo *pi );
|
||||
};
|
||||
|
||||
extern ConnChasemapper connChasemapper;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,131 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_MQTT
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "conn-mqtt.h"
|
||||
#include <WiFi.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "json.h"
|
||||
|
||||
extern const char *version_name;
|
||||
extern const char *version_id;
|
||||
|
||||
/* configuration paramters are in the config, no need to duplicate :-)
|
||||
{"mqtt.active", 0, &sonde.config.mqtt.active},
|
||||
{"mqtt.id", 63, &sonde.config.mqtt.id},
|
||||
{"mqtt.host", 63, &sonde.config.mqtt.host},
|
||||
{"mqtt.port", 0, &sonde.config.mqtt.port},
|
||||
{"mqtt.username", 63, &sonde.config.mqtt.username},
|
||||
{"mqtt.password", 63, &sonde.config.mqtt.password},
|
||||
{"mqtt.prefix", 63, &sonde.config.mqtt.prefix},
|
||||
*/
|
||||
|
||||
TimerHandle_t mqttReconnectTimer;
|
||||
|
||||
/* Global initalization (on TTGO startup) */
|
||||
void MQTT::init() {
|
||||
}
|
||||
|
||||
|
||||
// Internal helper function for netsetup
|
||||
void mqttCallback(char* topic, byte* payload, unsigned int length) {
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (int i=0;i<length;i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
/* Network initialization (as soon as network becomes available) */
|
||||
void MQTT::netsetup() {
|
||||
if (!sonde.config.mqtt.active)
|
||||
return;
|
||||
if (strlen(sonde.config.mqtt.host)==0)
|
||||
return;
|
||||
|
||||
WiFi.hostByName(sonde.config.mqtt.host, this->ip);
|
||||
|
||||
Serial.println("[MQTT] pubsub client");
|
||||
mqttClient.setServer(ip, sonde.config.mqtt.port);
|
||||
snprintf(clientID, 20, "%s%04d", sonde.config.mqtt.id, (int)random(0, 1000));
|
||||
clientID[20] = 0;
|
||||
Serial.print(clientID);
|
||||
mqttClient.setClientId(clientID);
|
||||
if (strlen(sonde.config.mqtt.password) > 0) {
|
||||
mqttClient.setCredentials(sonde.config.mqtt.username, sonde.config.mqtt.password);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MQTT::updateSonde( SondeInfo *si ) {
|
||||
if(!sonde.config.mqtt.active)
|
||||
return;
|
||||
|
||||
if(1 /*connected*/) {
|
||||
Serial.println("Sending sonde info via MQTT");
|
||||
// TODO: Check if si is good / fresh
|
||||
publishPacket(si);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::updateStation( PosInfo *pi ) {
|
||||
if(!sonde.config.mqtt.active)
|
||||
return;
|
||||
|
||||
int now = millis();
|
||||
if ( (lastMqttUptime == 0 || (lastMqttUptime + 60000 < now) || (lastMqttUptime > now))) {
|
||||
publishUptime();
|
||||
lastMqttUptime = now;
|
||||
}
|
||||
}
|
||||
|
||||
// Internal (private) functions
|
||||
//void MQTT::connectToMqtt() {
|
||||
// Serial.println("Connecting to MQTT...");
|
||||
// mqttClient.connect();
|
||||
//}
|
||||
|
||||
void MQTT::publishUptime()
|
||||
{
|
||||
mqttClient.connect(); // ensure we've got connection
|
||||
|
||||
Serial.println("[MQTT] writing");
|
||||
char payload[256];
|
||||
// maybe TODO: Use dynamic position if GPS is available?
|
||||
// rxlat, rxlon only if not empty
|
||||
snprintf(payload, 256, "{\"uptime\": %lu, \"user\": \"%s\", ", millis(), sonde.config.mqtt.username);
|
||||
if( !isnan(sonde.config.rxlat) && !isnan(sonde.config.rxlon) ) {
|
||||
snprintf(payload, 256, "%s\"rxlat\": %.5f, \"rxlon\": %.5f, ", payload, sonde.config.rxlat, sonde.config.rxlon);
|
||||
}
|
||||
snprintf(payload, 256, "%s\"SW\": \"%s\", \"VER\": \"%s\"}", payload, version_name, version_id);
|
||||
Serial.println(payload);
|
||||
char topic[128];
|
||||
snprintf(topic, 128, "%s%s", sonde.config.mqtt.prefix, "uptime");
|
||||
mqttClient.publish(topic, 1, 1, payload);
|
||||
}
|
||||
|
||||
void MQTT::publishPacket(SondeInfo *si)
|
||||
{
|
||||
SondeData *s = &(si->d);
|
||||
mqttClient.connect(); // ensure we've got connection
|
||||
|
||||
char payload[1024];
|
||||
payload[0] = '{';
|
||||
int n = sonde2json(payload+1, 1023, si);
|
||||
if(n<0) {
|
||||
// ERROR
|
||||
Serial.println("publishPacket: sonde2json failed, string too long");
|
||||
}
|
||||
strcat(payload, "}"); // terminate payload string
|
||||
|
||||
char topic[128];
|
||||
snprintf(topic, 128, "%s%s", sonde.config.mqtt.prefix, "packet");
|
||||
Serial.print(payload);
|
||||
mqttClient.publish(topic, 1, 1, payload);
|
||||
}
|
||||
|
||||
MQTT connMQTT;
|
||||
#endif
|
|
@ -0,0 +1,52 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_MQTT
|
||||
|
||||
#ifndef MQTT_h
|
||||
#define MQTT_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include "Sonde.h"
|
||||
//#include "RS41.h"
|
||||
#include "conn.h"
|
||||
|
||||
class MQTT : public Conn
|
||||
{
|
||||
public:
|
||||
/* Called once on startup */
|
||||
void init();
|
||||
|
||||
/* Called whenever the network becomes available */
|
||||
void netsetup();
|
||||
|
||||
/* Called approx 1x / second (maybe only if good data is available) */
|
||||
virtual void updateSonde( SondeInfo *si );
|
||||
|
||||
/* Called approx 1x / second* */
|
||||
virtual void updateStation( PosInfo *pi );
|
||||
|
||||
|
||||
private:
|
||||
WiFiClient mqttWifiClient;
|
||||
AsyncMqttClient mqttClient;
|
||||
TimerHandle_t mqttReconnectTimer;
|
||||
IPAddress ip;
|
||||
//uint16_t port;
|
||||
//const char *username;
|
||||
//const char *password;
|
||||
//const char *prefix;
|
||||
char clientID[21];
|
||||
|
||||
//void init(const char *host, uint16_t port, const char *id, const char *username, const char *password, const char *prefix);
|
||||
void publishPacket(SondeInfo *s);
|
||||
void publishUptime();
|
||||
//void connectToMqtt();
|
||||
|
||||
unsigned long lastMqttUptime = 0;
|
||||
boolean mqttEnabled;
|
||||
};
|
||||
|
||||
extern MQTT connMQTT;
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,51 @@
|
|||
#include "../features.h"
|
||||
|
||||
#if FEATURE_SDCARD
|
||||
|
||||
#include "conn-sdcard.h"
|
||||
|
||||
// TODO: Move into config
|
||||
#define CS 13
|
||||
#define SYNC_INTERVAL 10
|
||||
|
||||
void ConnSDCard::init() {
|
||||
/* Initialize SD card */
|
||||
initok = SD.begin(CS);
|
||||
Serial.printf("SD card init: %s\n", initok?"OK":"Failed");
|
||||
}
|
||||
|
||||
void ConnSDCard::netsetup() {
|
||||
/* empty function, we don't use any network here */
|
||||
}
|
||||
|
||||
void ConnSDCard::updateSonde( SondeInfo *si ) {
|
||||
if (!initok) return;
|
||||
if (!file) {
|
||||
file = SD.open("/data.csv", FILE_APPEND);
|
||||
}
|
||||
if (!file) {
|
||||
Serial.println("Error opening file");
|
||||
return;
|
||||
}
|
||||
SondeData *sd = &si->d;
|
||||
file.printf("%d,%s,%s,%d,"
|
||||
"%f,%f,%f,%f,%f,%f,%d,%d,"
|
||||
"%d,%d,%d,%d\n",
|
||||
sd->validID, sd->ser, sd->typestr, sd->subtype,
|
||||
sd->lat, sd->lon, sd->alt, sd->vs, sd->hs, sd->dir, sd->sats, sd->validPos,
|
||||
sd->time, sd->frame, sd->vframe, sd->validTime);
|
||||
wcount++;
|
||||
if(wcount >= SYNC_INTERVAL) {
|
||||
file.flush();
|
||||
wcount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ConnSDCard::updateStation( PosInfo *pi ) {
|
||||
}
|
||||
|
||||
|
||||
ConnSDCard connSDCard;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* conn-sdcard.h
|
||||
* Data exporter to SD card
|
||||
* Copyright (c) 2023 Hansi Reiser, dl9rdz
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0+
|
||||
*/
|
||||
|
||||
#ifndef conn_sdcard_h
|
||||
#define conn_sdcard_h
|
||||
|
||||
#include "conn.h"
|
||||
|
||||
//#include "FS.h"
|
||||
#include "SD.h"
|
||||
|
||||
class ConnSDCard : public Conn
|
||||
{
|
||||
public:
|
||||
/* Called once on startup */
|
||||
void init();
|
||||
|
||||
/* Called whenever the network becomes available */
|
||||
void netsetup();
|
||||
|
||||
|
||||
/* Called approx 1x / second (maybe only if good data is available) */
|
||||
virtual void updateSonde( SondeInfo *si );
|
||||
|
||||
/* Called approx 1x / second* */
|
||||
virtual void updateStation( PosInfo *pi );
|
||||
|
||||
|
||||
private:
|
||||
File file;
|
||||
uint8_t initok = 0;
|
||||
uint16_t wcount = 0;
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* conn.h
|
||||
* Interface for external data exporters
|
||||
* Copyright (c) 2023 Hansi Reiser, dl9rdz
|
||||
*/
|
||||
|
||||
#ifndef conn_h
|
||||
#define conn_h
|
||||
|
||||
#include "Sonde.h"
|
||||
|
||||
|
||||
// to be moved elsewhere
|
||||
struct PosInfo {
|
||||
public:
|
||||
float lat;
|
||||
float lon;
|
||||
};
|
||||
|
||||
|
||||
/* Interface for all data exporters */
|
||||
class Conn
|
||||
{
|
||||
public:
|
||||
/* Called once on startup */
|
||||
virtual void init();
|
||||
|
||||
/* Called whenever the network becomes available */
|
||||
virtual void netsetup();
|
||||
|
||||
/* Called approx 1x / second (maybe only if good data is available) */
|
||||
virtual void updateSonde( SondeInfo *si );
|
||||
|
||||
/* Called approx 1x / second* */
|
||||
virtual void updateStation( PosInfo *pi );
|
||||
|
||||
};
|
||||
#endif
|
|
@ -1,3 +1,6 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_RS92
|
||||
|
||||
#include "time.h"
|
||||
#include "geteph.h"
|
||||
#include <SPIFFS.h>
|
||||
|
@ -14,7 +17,7 @@ extern WiFiClient client;
|
|||
char outbuf[128];
|
||||
uint8_t ephstate = EPH_NOTUSED;
|
||||
//enum EPHSTATE { EPH_NOTUSED, EPH_PENDING, EPH_TIMEERR, EPH_ERROR, EPH_EPHERROR, EPH_GOOD };
|
||||
const char *ephtxt[] = { "Disabled", "Pending", "Time error", "Fetch error", "Read error", "Good" };
|
||||
const char *ephtxt[] = { "Disabled (no RS92 in QRG list or Wifi mode not 3)", "Pending", "Time error", "Fetch error", "Read error", "Good" };
|
||||
|
||||
uint8_t getreply() {
|
||||
String s = client.readStringUntil('\n');
|
||||
|
@ -83,22 +86,25 @@ void geteph() {
|
|||
Serial.println("cannot open file\n");
|
||||
return;
|
||||
}
|
||||
char host[252];
|
||||
strcpy(host, sonde.config.ephftp);
|
||||
char *buf = strchr(host, '/');
|
||||
if(!buf) { Serial.println("Invalid FTP host config"); return; }
|
||||
*buf = 0;
|
||||
buf++;
|
||||
char host[100];
|
||||
char buf[200];
|
||||
char *ptr = strchr(sonde.config.ephftp, '/');
|
||||
if(!ptr) { Serial.println("Invalid FTP host config"); return; }
|
||||
int hlen = ptr - sonde.config.ephftp;
|
||||
strncpy(host, sonde.config.ephftp, hlen);
|
||||
host[hlen] = 0;
|
||||
snprintf(buf, 200, ptr+1, year, day, year-2000);
|
||||
uint8_t dispw, disph, dispxs, dispys;
|
||||
disp.rdis->getDispSize(&disph, &dispw, &dispxs, &dispys);
|
||||
disp.rdis->clear();
|
||||
disp.rdis->setFont(FONT_SMALL);
|
||||
disp.rdis->drawString(0, 0, host);
|
||||
// fetch rinex from server
|
||||
char *ptr = buf + strlen(buf);
|
||||
snprintf(ptr, 128, "%04d/%03d/brdc%03d0.%02dn.gz", year, day, day, year-2000);
|
||||
// char *ptr = buf + strlen(buf);
|
||||
// snprintf(ptr, 128, "%04d/%03d/brdc%03d0.%02dn.gz", year, day, day, year-2000);
|
||||
// snprintf(ptr, 128, "%04d/brdc/brdc%03d0.%02dn.gz", year, /*day,*/ day, year-2000);
|
||||
Serial.println("running geteph\n");
|
||||
disp.rdis->drawString(0, 1*dispys, ptr+9);
|
||||
disp.rdis->drawString(0, 1*dispys, buf+9);
|
||||
|
||||
if(!client.connect(host, 21)) {
|
||||
Serial.printf("FTP connection to %s failed\n", host);
|
||||
|
@ -236,3 +242,4 @@ void geteph() {
|
|||
file.close();
|
||||
ofile.close();
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
#include "json.h"
|
||||
#include "RS41.h"
|
||||
#include "DFM.h"
|
||||
|
||||
extern const char *sondeTypeStrSH[];
|
||||
extern const char *dfmSubtypeStrSH[];
|
||||
//extern const char *dfmSubtypeStrSH[];
|
||||
|
||||
static char typestr[11];
|
||||
|
||||
const char *getType(SondeInfo *si) {
|
||||
if( si->type == STYPE_RS41 ) {
|
||||
if ( RS41::getSubtype(typestr, 11, si) == 0 ) return typestr;
|
||||
} else if ( TYPE_IS_DFM(si->type) && si->d.subtype > 0 && si->d.subtype < 16 ) {
|
||||
const char *t = dfmSubtypeStrSH[si->d.subtype];
|
||||
if(t) return t;
|
||||
sprintf(typestr, "DFMx%X", si->d.subtype);
|
||||
return typestr;
|
||||
} else if ( TYPE_IS_DFM(si->type) && si->d.subtype > 0 ) {
|
||||
const char *t = dfmSubtypeLong[si->d.subtype & 0xf];
|
||||
if( (si->d.subtype & 0xf) == DFM_UNK) {
|
||||
sprintf(typestr, "DFMx%X", si->d.subtype>>4);
|
||||
return typestr;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
return sondeTypeStrSH[sonde.realType(si)];
|
||||
}
|
||||
|
@ -96,7 +99,7 @@ int sonde2json(char *buf, int maxlen, SondeInfo *si)
|
|||
|
||||
// add only if available
|
||||
if(s->batteryVoltage > 0) {
|
||||
n = snprintf(buf, maxlen, ",\"bat\": %.1f", s->batteryVoltage);
|
||||
n = snprintf(buf, maxlen, ",\"batt\": %.1f", s->batteryVoltage);
|
||||
if(n>=maxlen) return -1;
|
||||
buf += n; maxlen -= n;
|
||||
}
|
||||
|
|
|
@ -1,162 +0,0 @@
|
|||
#include <Arduino.h>
|
||||
#include "mqtt.h"
|
||||
#include <WiFi.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPmDNS.h>
|
||||
#include "RS41.h"
|
||||
#include "json.h"
|
||||
|
||||
TimerHandle_t mqttReconnectTimer;
|
||||
|
||||
extern const char *version_name;
|
||||
extern const char *version_id;
|
||||
|
||||
void mqttCallback(char* topic, byte* payload, unsigned int length) {
|
||||
Serial.print("Message arrived [");
|
||||
Serial.print(topic);
|
||||
Serial.print("] ");
|
||||
for (int i=0;i<length;i++) {
|
||||
Serial.print((char)payload[i]);
|
||||
}
|
||||
Serial.println();
|
||||
}
|
||||
|
||||
static char buffer[21];
|
||||
void MQTT::init(const char* host, uint16_t port, const char* id, const char *username, const char *password, const char *prefix)
|
||||
{
|
||||
WiFi.hostByName(host, this->ip);
|
||||
this->port = port;
|
||||
this->username = username;
|
||||
this->password = password;
|
||||
this->prefix = prefix;
|
||||
|
||||
Serial.println("[MQTT] pubsub client");
|
||||
mqttClient.setServer(ip, port);
|
||||
snprintf(buffer, 20, "%s%04d", id, (int)random(0, 1000));
|
||||
buffer[20] = 0;
|
||||
Serial.print(buffer);
|
||||
mqttClient.setClientId(buffer);
|
||||
if (strlen(password) > 0) {
|
||||
mqttClient.setCredentials(username, password);
|
||||
}
|
||||
}
|
||||
|
||||
void MQTT::connectToMqtt() {
|
||||
Serial.println("Connecting to MQTT...");
|
||||
mqttClient.connect();
|
||||
}
|
||||
|
||||
void MQTT::publishUptime()
|
||||
{
|
||||
mqttClient.connect(); // ensure we've got connection
|
||||
|
||||
Serial.println("[MQTT] writing");
|
||||
//char payload[128];
|
||||
//snprintf(payload, 12, "%lu", millis());
|
||||
//snprintf(payload, 124, "{\"uptime\": %lu," "\"user\": \"%s\"", millis(), username );
|
||||
char payload[128];
|
||||
snprintf(payload, 128, "{\"uptime\": %ld, \"user\": \"%s\", \"rxlat\": %.5f, \"rxlon\": %.5f, \"ver\": \"%s\", \"sub\": \"%s\"}",
|
||||
millis(), username, sonde.config.rxlat, sonde.config.rxlon, version_name, version_id);
|
||||
Serial.println(payload);
|
||||
char topic[128];
|
||||
snprintf(topic, 128, "%s%s", this->prefix, "uptime");
|
||||
mqttClient.publish(topic, 1, 1, payload);
|
||||
}
|
||||
|
||||
void MQTT::publishPacket(SondeInfo *si)
|
||||
{
|
||||
SondeData *s = &(si->d);
|
||||
mqttClient.connect(); // ensure we've got connection
|
||||
|
||||
char payload[1024];
|
||||
payload[0] = '{';
|
||||
int n = sonde2json(payload+1, 1023, si);
|
||||
if(n<0) {
|
||||
// ERROR
|
||||
Serial.println("publishPacket: sonde2json failed, string too long");
|
||||
}
|
||||
|
||||
#if 0
|
||||
snprintf(payload, 1024, "{"
|
||||
"\"active\": %d,"
|
||||
"\"freq\": %.2f,"
|
||||
"\"id\": \"%s\","
|
||||
"\"ser\": \"%s\","
|
||||
"\"validId\": %d,"
|
||||
"\"launchsite\": \"%s\","
|
||||
"\"lat\": %.5f,"
|
||||
"\"lon\": %.5f,"
|
||||
"\"alt\": %.1f,"
|
||||
"\"vs\": %.1f,"
|
||||
"\"hs\": %.1f,"
|
||||
"\"dir\": %.1f,"
|
||||
"\"sats\": %d,"
|
||||
"\"validPos\": %d,"
|
||||
"\"time\": %u,"
|
||||
"\"frame\": %u,"
|
||||
"\"validTime\": %d,"
|
||||
"\"rssi\": %d,"
|
||||
"\"afc\": %d,"
|
||||
"\"rxStat\": \"%s\","
|
||||
"\"rxStart\": %u,"
|
||||
"\"norxStart\": %u,"
|
||||
"\"viewStart\": %u,"
|
||||
"\"lastState\": %d,"
|
||||
"\"launchKT\": %d,"
|
||||
"\"burstKT\": %d,"
|
||||
"\"countKT\": %d,"
|
||||
"\"crefKT\": %d",
|
||||
(int)si->active,
|
||||
si->freq,
|
||||
s->id,
|
||||
s->ser,
|
||||
(int)s->validID,
|
||||
si->launchsite,
|
||||
s->lat,
|
||||
s->lon,
|
||||
s->alt,
|
||||
s->vs,
|
||||
s->hs,
|
||||
s->dir,
|
||||
s->sats,
|
||||
s->validPos,
|
||||
s->time,
|
||||
s->frame,
|
||||
(int)s->validTime,
|
||||
si->rssi,
|
||||
si->afc,
|
||||
si->rxStat,
|
||||
si->rxStart,
|
||||
si->norxStart,
|
||||
si->viewStart,
|
||||
si->lastState,
|
||||
s->launchKT,
|
||||
s->burstKT,
|
||||
s->countKT,
|
||||
s->crefKT
|
||||
);
|
||||
if ( !isnan( s->temperature ) ) {
|
||||
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"temp\": ", s->temperature );
|
||||
}
|
||||
if ( !isnan( s->relativeHumidity ) ) {
|
||||
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"humidity\": ", s->relativeHumidity );
|
||||
}
|
||||
if ( !isnan( s->pressure ) ) {
|
||||
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"pressure\": ", s->pressure );
|
||||
}
|
||||
if ( !isnan( s->batteryVoltage && s->batteryVoltage > 0 ) ) {
|
||||
snprintf(payload, 1024, "%s%s%.1f", payload, ",\"batt\": ", s->batteryVoltage );
|
||||
}
|
||||
char subtype[11];
|
||||
if ( RS41::getSubtype( subtype, 11, si) == 0 ) {
|
||||
snprintf(payload, 1024, "%s%s%s%s", payload, ",\"subtype\": \"", subtype, "\"" );
|
||||
}
|
||||
snprintf(payload, 1024, "%s%s", payload, "}" ); // terminate payload string
|
||||
#endif
|
||||
strcat(payload, "}"); // terminate payload string
|
||||
|
||||
char topic[128];
|
||||
snprintf(topic, 128, "%s%s", this->prefix, "packet");
|
||||
Serial.print(payload);
|
||||
mqttClient.publish(topic, 1, 1, payload);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
#ifndef MQTT_h
|
||||
#define MQTT_h
|
||||
|
||||
#include <WiFi.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include "Sonde.h"
|
||||
#include "RS41.h"
|
||||
|
||||
class MQTT
|
||||
{
|
||||
public:
|
||||
WiFiClient mqttWifiClient;
|
||||
AsyncMqttClient mqttClient;
|
||||
TimerHandle_t mqttReconnectTimer;
|
||||
IPAddress ip;
|
||||
uint16_t port;
|
||||
const char *username;
|
||||
const char *password;
|
||||
const char *prefix;
|
||||
|
||||
void init(const char *host, uint16_t port, const char *id, const char *username, const char *password, const char *prefix);
|
||||
void publishPacket(SondeInfo *s);
|
||||
void publishUptime();
|
||||
private:
|
||||
void connectToMqtt();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,3 +1,6 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_RS92
|
||||
|
||||
/* SPDX-License-Identifier: GPL-3.0
|
||||
* based on https://github.com/rs1729/RS/blob/master/rs92/nav_gps_vel.c
|
||||
*
|
||||
|
@ -1713,3 +1716,4 @@ int NAV_LinV(int N, SAT_t satv[], double pos_ecef[3],
|
|||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,545 @@
|
|||
#include <stdio.h>
|
||||
#include <Wire.h>
|
||||
#include "pmu.h"
|
||||
#include "Sonde.h"
|
||||
|
||||
// 0: cleared; 1: set; 2: do not check, also query state of axp via i2c on each loop
|
||||
uint8_t pmu_irq = 0;
|
||||
#define PMU_IRQ 35
|
||||
|
||||
#define AXP192_VMIN 1800
|
||||
#define AXP192_VSTEP 100
|
||||
|
||||
|
||||
#define AXP192_IC_TYPE (0x03)
|
||||
|
||||
#define AXP192_DC_MIN 700
|
||||
#define AXP192_DC_STEPS 25
|
||||
|
||||
#define AXP192_LDO_MIN (1800)
|
||||
#define AXP192_LDO_STEPS (100)
|
||||
|
||||
#define AXP192_VOLTREG_DC1
|
||||
|
||||
// some registers:
|
||||
#define AXP192_STATUS (0x00)
|
||||
#define AXP192_MODE_CHGSTATUS (0x01)
|
||||
|
||||
// Power voltage control register
|
||||
#define AXP192_DC2OUT_VOL (0x23)
|
||||
#define AXP192_DC1OUT_VOL (0x26)
|
||||
#define AXP192_DC3OUT_VOL (0x27)
|
||||
#define AXP192_LDO23OUT_VOL (0x28)
|
||||
#define AXP192_GPIO0_VOL (0x91)
|
||||
|
||||
// Power enable registers
|
||||
#define AXP192_LDO23_DC123_EXT_CTL (0x12)
|
||||
|
||||
// ADC control
|
||||
#define AXP192_ADC_EN1 (0x82)
|
||||
|
||||
// ADC results
|
||||
#define AXP192_BAT_AVERVOL_H8 (0x78)
|
||||
#define AXP192_BAT_AVERVOL_L4 (0x79)
|
||||
#define AXP192_BAT_AVERCHGCUR_H8 (0x7A)
|
||||
#define AXP192_BAT_AVERCHGCUR_L4 (0x7B)
|
||||
#define AXP192_BAT_AVERCHGCUR_L5 (0x7B)
|
||||
#define AXP192_ACIN_VOL_H8 (0x56)
|
||||
#define AXP192_ACIN_VOL_L4 (0x57)
|
||||
#define AXP192_ACIN_CUR_H8 (0x58)
|
||||
#define AXP192_ACIN_CUR_L4 (0x59)
|
||||
#define AXP192_VBUS_VOL_H8 (0x5A)
|
||||
#define AXP192_VBUS_VOL_L4 (0x5B)
|
||||
#define AXP192_VBUS_CUR_H8 (0x5C)
|
||||
#define AXP192_VBUS_CUR_L4 (0x5D)
|
||||
#define AXP192_INTERNAL_TEMP_H8 (0x5E)
|
||||
#define AXP192_INTERNAL_TEMP_L4 (0x5F)
|
||||
#define AXP192_TS_IN_H8 (0x62)
|
||||
#define AXP192_TS_IN_L4 (0x63)
|
||||
#define AXP192_GPIO0_VOL_ADC_H8 (0x64)
|
||||
#define AXP192_GPIO0_VOL_ADC_L4 (0x65)
|
||||
#define AXP192_GPIO1_VOL_ADC_H8 (0x66)
|
||||
#define AXP192_GPIO1_VOL_ADC_L4 (0x67)
|
||||
#define AXP192_BAT_AVERDISCHGCUR_H8 (0x7C)
|
||||
#define AXP192_BAT_AVERDISCHGCUR_L5 (0x7D)
|
||||
|
||||
|
||||
// Interrupt enable
|
||||
#define AXP192_INTEN1 (0x40)
|
||||
#define AXP192_INTEN2 (0x41)
|
||||
#define AXP192_INTEN3 (0x42)
|
||||
#define AXP192_INTEN4 (0x43)
|
||||
#define AXP192_INTEN5 (0x4A)
|
||||
|
||||
// Int clear.
|
||||
#define AXP192_INTSTS1 (0x44)
|
||||
#define AXP192_INTSTS2 (0x45)
|
||||
#define AXP192_INTSTS3 (0x46)
|
||||
#define AXP192_INTSTS4 (0x47)
|
||||
#define AXP192_INTSTS5 (0x4D)
|
||||
|
||||
extern SemaphoreHandle_t axpSemaphore;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
/// High-level functions
|
||||
PMU *PMU::getInstance(TwoWire &wire) {
|
||||
PMU *pmu = NULL;
|
||||
// Check if there is some AXP192 or AXP2101 present
|
||||
uint8_t chipid = readRegisterWire(wire, AXP192_IC_TYPE);
|
||||
// AXP192: 0x03 AXP2101: 0x4A
|
||||
if(chipid==0x03) {
|
||||
pmu = new AXP192PMU(wire);
|
||||
}
|
||||
else if (chipid==0x4A) {
|
||||
pmu = new AXP2101PMU(wire);
|
||||
}
|
||||
return pmu;
|
||||
}
|
||||
|
||||
|
||||
int PMU::readRegisterWire(TwoWire &wire, uint8_t reg) {
|
||||
wire.beginTransmission(AXP192_SLAVE_ADDRESS);
|
||||
wire.write(reg);
|
||||
if (wire.endTransmission() != 0) {
|
||||
return -1;
|
||||
}
|
||||
wire.requestFrom(AXP192_SLAVE_ADDRESS, 1U);
|
||||
return wire.read();
|
||||
}
|
||||
int PMU::readRegister(uint8_t reg) {
|
||||
return readRegisterWire(_wire, reg);
|
||||
}
|
||||
uint16_t PMU::readRegisters_8_4(uint8_t regh, uint8_t regl)
|
||||
{
|
||||
uint8_t hi = readRegister(regh);
|
||||
uint8_t lo = readRegister(regl);
|
||||
return (hi << 4) | (lo & 0x0F);
|
||||
}
|
||||
|
||||
uint16_t PMU::readRegisters_8_5(uint8_t regh, uint8_t regl)
|
||||
{
|
||||
uint8_t hi = readRegister(regh);
|
||||
uint8_t lo = readRegister(regl);
|
||||
return (hi << 5) | (lo & 0x1F);
|
||||
}
|
||||
|
||||
int PMU::writeRegister(uint8_t reg, uint8_t val) {
|
||||
_wire.beginTransmission(AXP192_SLAVE_ADDRESS);
|
||||
_wire.write(reg);
|
||||
_wire.write(val);
|
||||
return (_wire.endTransmission() == 0) ? 0 : -1;
|
||||
}
|
||||
int PMU::getRegisterBit(uint8_t reg, uint8_t bit) {
|
||||
int val = readRegister(reg);
|
||||
if (val == -1) { return -1; }
|
||||
return (val >> bit) & 0x01;
|
||||
}
|
||||
int PMU::setRegisterBit(uint8_t reg, uint8_t bit) {
|
||||
int val = readRegister(reg);
|
||||
if (val == -1) { return -1; }
|
||||
return writeRegister(reg, (val | (1<<bit)));
|
||||
}
|
||||
int PMU::clearRegisterBit(uint8_t reg, uint8_t bit) {
|
||||
int val = readRegister(reg);
|
||||
if (val == -1) { return -1; }
|
||||
return writeRegister(reg, (val & ( ~(1<<bit))));
|
||||
}
|
||||
|
||||
// Returns if there was a keypress, using the following enum defined in RX_FSK.ini:
|
||||
enum KeyPress { KP_NONE = 0, KP_SHORT, KP_DOUBLE, KP_MID, KP_LONG };
|
||||
|
||||
int PMU::handleIRQ() {
|
||||
if (pmu_irq) {
|
||||
Serial.println("PMU_IRQ is set\n");
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
int keypress = -1;
|
||||
xSemaphoreTake( axpSemaphore, portMAX_DELAY );
|
||||
keypress = getIrqKeyStatus();
|
||||
if(keypress) { Serial.printf("Keypress: %d (%s)", keypress, keypress==KP_SHORT?"short":"mid"); }
|
||||
if (pmu_irq != 2) {
|
||||
pmu_irq = 0;
|
||||
}
|
||||
xSemaphoreGive( axpSemaphore );
|
||||
return keypress;
|
||||
}
|
||||
|
||||
int AXP192PMU::init() {
|
||||
// Initialize AXP192, for T-BEAM v1.1 or M5Stack
|
||||
|
||||
// LDO2: LoRa VCC on T-BEAM, PERI_VDD on M5Core2 (LCD)
|
||||
setLDO2(3300);
|
||||
enableLDO2();
|
||||
if(sonde.config.type == TYPE_M5_CORE2) {
|
||||
// Display backlight (LCD_BL) on M5 Core2
|
||||
setDC3(3300);
|
||||
enableDC3();
|
||||
pmu_irq = 2; // IRQ pin not connected on Core2
|
||||
// Set GPIO0 VDO to 3.3V (as is done by original M5Stack software)
|
||||
// (default value 2.8V did not have the expected effect :))
|
||||
setLDOio(3300);
|
||||
// ADC configuration: Enable monitoring of AC [bits 4,5 in enable register]
|
||||
uint8_t val = readRegister(AXP192_ADC_EN1);
|
||||
writeRegister(AXP192_ADC_EN1, val | (1 << 4) | (1 << 5) );
|
||||
} else {
|
||||
// T-Beam specific
|
||||
// GPS power on T-Beam (its the buzzer on M5 Core2, so only enable for T-Beam)
|
||||
enableLDO3();
|
||||
// ADC configuration: Enable monitoring of USB [bits 2,3 in enable register]
|
||||
uint8_t val = readRegister(AXP192_ADC_EN1);
|
||||
writeRegister(AXP192_ADC_EN1, val | (1 << 4) | (1 << 5) );
|
||||
}
|
||||
// Common configuration for T-Beam and M5 Core2
|
||||
// DCDC2: M5Core: Unused, T-Beam: Unused, so set to disabled!! (was enabled in previous versions)
|
||||
enableDC2(false);
|
||||
|
||||
// EXTEN: M5Core2: 5V Boost enable; T-Beam EXTEN
|
||||
enableEXTEN();
|
||||
|
||||
// DCDC1: M5Core: MCU_VDD, T-Beam 1.1: "VCC_2.5V" == 3V3-Pin on pin header on board
|
||||
setDC1(3300);
|
||||
enableDC1();
|
||||
|
||||
// ADC configuration: Enable monitor batt current [bit 6 in eable register]
|
||||
uint8_t val = readRegister(AXP192_ADC_EN1);
|
||||
writeRegister(AXP192_ADC_EN1, val | (1 << 6) );
|
||||
|
||||
if (pmu_irq != 2) {
|
||||
pinMode(PMU_IRQ, INPUT_PULLUP);
|
||||
attachInterrupt(PMU_IRQ, [] {
|
||||
pmu_irq = 1;
|
||||
}, FALLING);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/// Helper functions
|
||||
|
||||
int AXP192PMU::getIrqKeyStatus() {
|
||||
int status = readRegister(AXP192_INTSTS3);
|
||||
|
||||
// Also clear IRQ status
|
||||
writeRegister(AXP192_INTSTS1, 0xFF);
|
||||
writeRegister(AXP192_INTSTS2, 0xFF);
|
||||
writeRegister(AXP192_INTSTS3, 0xFF);
|
||||
writeRegister(AXP192_INTSTS4, 0xFF);
|
||||
writeRegister(AXP192_INTSTS5, 0xFF);
|
||||
|
||||
//
|
||||
if ( status & 0x01 ) return KP_MID;
|
||||
if ( status & 0x02 ) return KP_SHORT;
|
||||
return KP_NONE;
|
||||
}
|
||||
|
||||
|
||||
void AXP192PMU::disableAllIRQ() {
|
||||
writeRegister(AXP192_INTEN1, 0);
|
||||
writeRegister(AXP192_INTEN2, 0);
|
||||
writeRegister(AXP192_INTEN3, 0);
|
||||
writeRegister(AXP192_INTEN4, 0);
|
||||
writeRegister(AXP192_INTEN5, 0);
|
||||
}
|
||||
|
||||
void AXP192PMU::_enableIRQ(uint8_t addr, uint8_t mask) {
|
||||
int data = readRegister(addr);
|
||||
writeRegister(addr, data | mask);
|
||||
}
|
||||
|
||||
// we want KP_SHORT and KP_LONG interrupts...
|
||||
// IRQ4, in reg 0x42h, Bit 16+17
|
||||
void AXP192PMU::enableIRQ() {
|
||||
//_enableIRQ( AXP192_INTEN1, mask&0xFF );
|
||||
//_enableIRQ( AXP192_INTEN2, mask>>8 );
|
||||
_enableIRQ( AXP192_INTEN3, 0x03 );
|
||||
//_enableIRQ( AXP192_INTEN4, mask>>24 );
|
||||
//_enableIRQ( AXP192_INTEN5, mask>>32 );
|
||||
}
|
||||
|
||||
// Functions for setting voltage output levels
|
||||
int AXP192PMU::setVoltageReg(uint8_t reg, uint8_t regval) {
|
||||
int val = readRegister(reg);
|
||||
if (val==-1) return -1;
|
||||
val &= 0x80;
|
||||
val |= regval;
|
||||
return writeRegister(reg, val);
|
||||
}
|
||||
|
||||
int AXP192PMU::setDC1(uint16_t millivolt) {
|
||||
return setVoltageReg(AXP192_DC1OUT_VOL, (millivolt-AXP192_DC_MIN)/AXP192_DC_STEPS );
|
||||
}
|
||||
int AXP192PMU::setDC2(uint16_t millivolt) {
|
||||
return setVoltageReg(AXP192_DC2OUT_VOL, (millivolt-AXP192_DC_MIN)/AXP192_DC_STEPS );
|
||||
}
|
||||
int AXP192PMU::setDC3(uint16_t millivolt) {
|
||||
return setVoltageReg(AXP192_DC3OUT_VOL , (millivolt-AXP192_DC_MIN)/AXP192_DC_STEPS );
|
||||
}
|
||||
int AXP192PMU::setLDO2(uint16_t millivolt) {
|
||||
return setVoltageReg(AXP192_LDO23OUT_VOL, (millivolt-AXP192_LDO_MIN)/AXP192_LDO_STEPS);
|
||||
}
|
||||
int AXP192PMU::setLDOio(uint16_t millivolt) {
|
||||
return setVoltageReg(AXP192_GPIO0_VOL, (millivolt-AXP192_LDO_MIN)/AXP192_LDO_STEPS);
|
||||
}
|
||||
|
||||
|
||||
// LDO23_DC123_EXT_CTL
|
||||
// 0:DC-DC1, 1:DC-DC3, 2:LDO2, 3:LDO3, 4:DC-DC2, 6:EXTEN
|
||||
int AXP192PMU::enableDC1(bool onoff) {
|
||||
return onoff ? setRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 0) : clearRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 0);
|
||||
}
|
||||
int AXP192PMU::enableDC3(bool onoff) {
|
||||
return onoff ? setRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 1) : clearRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 1);
|
||||
}
|
||||
int AXP192PMU::enableLDO2(bool onoff) {
|
||||
return onoff ? setRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 2) : clearRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 2);
|
||||
}
|
||||
int AXP192PMU::enableLDO3(bool onoff) {
|
||||
return onoff ? setRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 3) : clearRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 3);
|
||||
}
|
||||
int AXP192PMU::enableDC2(bool onoff) {
|
||||
return onoff ? setRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 4) : clearRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 4);
|
||||
}
|
||||
int AXP192PMU::enableEXTEN(bool onoff) {
|
||||
return onoff ? setRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 6) : clearRegisterBit(AXP192_LDO23_DC123_EXT_CTL, 6);
|
||||
}
|
||||
|
||||
int AXP192PMU::enableADC(uint8_t channels) {
|
||||
uint8_t val = readRegister(AXP192_ADC_EN1);
|
||||
return writeRegister(AXP192_ADC_EN1, val | channels );
|
||||
}
|
||||
|
||||
int AXP192PMU::isBatteryConnected() {
|
||||
return getRegisterBit(AXP192_MODE_CHGSTATUS, 5);
|
||||
}
|
||||
int AXP192PMU::isVbusIn() {
|
||||
return getRegisterBit(AXP192_STATUS, 5);
|
||||
}
|
||||
int AXP192PMU::isCharging() {
|
||||
return getRegisterBit(AXP192_MODE_CHGSTATUS, 6);
|
||||
}
|
||||
|
||||
#define AXP192_BATT_VOLTAGE_STEP (1.1F)
|
||||
float AXP192PMU::getBattVoltage() {
|
||||
return readRegisters_8_4(AXP192_BAT_AVERVOL_H8, AXP192_BAT_AVERVOL_L4) * AXP192_BATT_VOLTAGE_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_BATT_DISCHARGE_CUR_STEP (0.5F)
|
||||
float AXP192PMU::getBattDischargeCurrent() {
|
||||
return readRegisters_8_5(AXP192_BAT_AVERDISCHGCUR_H8, AXP192_BAT_AVERDISCHGCUR_L5) * AXP192_BATT_DISCHARGE_CUR_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_BATT_CHARGE_CUR_STEP (0.5F)
|
||||
float AXP192PMU::getBattChargeCurrent() {
|
||||
return readRegisters_8_5(AXP192_BAT_AVERCHGCUR_H8, AXP192_BAT_AVERCHGCUR_L5) * AXP192_BATT_CHARGE_CUR_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_ACIN_VOLTAGE_STEP (1.7F)
|
||||
float AXP192PMU::getAcinVoltage() {
|
||||
return readRegisters_8_4(AXP192_ACIN_VOL_H8, AXP192_ACIN_VOL_L4) * AXP192_ACIN_VOLTAGE_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_ACIN_CUR_STEP (0.625F)
|
||||
float AXP192PMU::getAcinCurrent() {
|
||||
return readRegisters_8_4(AXP192_ACIN_CUR_H8, AXP192_ACIN_CUR_L4) * AXP192_ACIN_CUR_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_VBUS_VOLTAGE_STEP (1.7F)
|
||||
float AXP192PMU::getVbusVoltage() {
|
||||
return readRegisters_8_4(AXP192_VBUS_VOL_H8, AXP192_VBUS_VOL_L4) * AXP192_VBUS_VOLTAGE_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_VBUS_CUR_STEP (0.375F)
|
||||
float AXP192PMU::getVbusCurrent() {
|
||||
return readRegisters_8_4(AXP192_VBUS_CUR_H8, AXP192_VBUS_CUR_L4) * AXP192_VBUS_CUR_STEP;
|
||||
}
|
||||
|
||||
#define AXP192_INTERNAL_TEMP_STEP (0.1F)
|
||||
float AXP192PMU::getTemperature() {
|
||||
return readRegisters_8_4(AXP192_INTERNAL_TEMP_H8, AXP192_INTERNAL_TEMP_L4) * AXP192_INTERNAL_TEMP_STEP - 144.7;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
/////// Functions for AXP2101
|
||||
|
||||
// Registers
|
||||
#define AXP2101_CHARGE_GAUGE_WDT_CTRL (0x18)
|
||||
#define AXP2101_BTN_BAT_CHG_VOL_SET (0x6A)
|
||||
#define AXP2101_DC_ONOFF_DVM_CTRL (0x80)
|
||||
#define AXP2101_LDO_ONOFF_CTRL0 (0x90)
|
||||
#define AXP2101_LDO_ONOFF_CTRL1 (0x91)
|
||||
|
||||
#define AXP2101_LDO_VOL1_CTRL (0x93)
|
||||
#define AXP2101_LDO_VOL2_CTRL (0x94)
|
||||
|
||||
#define AXP2101_ADC_CHANNEL_CTRL (0x30)
|
||||
|
||||
// vterm_cfg: Bit 2:0, 4.2V = 011 (3)
|
||||
#define AXP2101_CHG_V_CFG (0x64)
|
||||
// ICC_CFG: Bit 4:0: constant current charge current limit, 25*N mA (N<=8), 200+100*(N-8) (N>8)
|
||||
#define AXP2101_ICC_CFG (0x62)
|
||||
|
||||
// Interrupt enable
|
||||
#define AXP2101_INTEN1 (0x40)
|
||||
#define AXP2101_INTEN2 (0x41)
|
||||
#define AXP2101_INTEN3 (0x42)
|
||||
|
||||
// Interrupt status
|
||||
#define AXP2101_INTSTS1 (0x48)
|
||||
#define AXP2101_INTSTS2 (0x49)
|
||||
#define AXP2101_INTSTS3 (0x4A)
|
||||
|
||||
// Constants
|
||||
#define AXP2101_ALDO_VOL_MIN (500)
|
||||
#define AXP2101_ALDO_VOL_STEPS (100)
|
||||
|
||||
#define AXP2101_BTN_VOL_MIN (2600)
|
||||
#define AXP2101_BTN_VOL_STEPS (100)
|
||||
|
||||
// 200 + 100*(11-8) = 500
|
||||
#define AXP2101_CHG_CUR_500MA (0x0B)
|
||||
#define AXP2101_CHG_VOL_4V2 (3)
|
||||
|
||||
|
||||
// return 0: ok, -1: error
|
||||
int AXP2101PMU::init() {
|
||||
// Initialize AXP2101, for T-BEAM v1.2
|
||||
|
||||
// Hard-coded for now, disable DC2/3/4/5 ALDO1,4 BLDO1/2 DLDO1/2
|
||||
int val = readRegister(AXP2101_DC_ONOFF_DVM_CTRL);
|
||||
writeRegister(AXP2101_DC_ONOFF_DVM_CTRL, val & (~0x1E)); // clear Bit 1,2,3,4 (DC2/3/4/5)
|
||||
|
||||
// clear bit 0 (aldo1), 3 (aldo4), 4,5(bldo1/2), 7 (dldo1)
|
||||
val = readRegister(AXP2101_LDO_ONOFF_CTRL0);
|
||||
writeRegister(AXP2101_LDO_ONOFF_CTRL0, val & (~0xB9));
|
||||
|
||||
// clear bit 0 (dldo2)
|
||||
val = readRegister(AXP2101_LDO_ONOFF_CTRL1);
|
||||
writeRegister(AXP2101_LDO_ONOFF_CTRL1, val & (~0x01));
|
||||
|
||||
// Set PowerVDD to 3100mV (GNSS RTC) -- reg 6A [MS412FE data sheet: charge volt 2.8-3.3; standard value 3.1]
|
||||
val = readRegister(AXP2101_BTN_BAT_CHG_VOL_SET);
|
||||
if (val == -1) return -1;
|
||||
val &= 0xF8;
|
||||
val |= (3100 - AXP2101_BTN_VOL_MIN) / AXP2101_BTN_VOL_STEPS;
|
||||
writeRegister(AXP2101_BTN_BAT_CHG_VOL_SET, val);
|
||||
|
||||
setRegisterBit(AXP2101_CHARGE_GAUGE_WDT_CTRL, 2);
|
||||
|
||||
// ESP32 VDD 3300mV
|
||||
// No need to set, automatically open , Don't close it
|
||||
|
||||
// LoRa VDD 3300mV on ALDO2
|
||||
val = readRegister(AXP2101_LDO_VOL1_CTRL);
|
||||
if (val == -1) return -1;
|
||||
val &= 0xE0;
|
||||
val |= (3300 - AXP2101_ALDO_VOL_MIN) / AXP2101_ALDO_VOL_STEPS;
|
||||
writeRegister(AXP2101_LDO_VOL1_CTRL, val);
|
||||
setRegisterBit(AXP2101_LDO_ONOFF_CTRL0, 1);
|
||||
|
||||
// GNSS VDD 3300mV on ALDO3
|
||||
val = readRegister(AXP2101_LDO_VOL2_CTRL);
|
||||
if (val == -1) return -1;
|
||||
val &= 0xE0;
|
||||
val |= (3300 - AXP2101_ALDO_VOL_MIN) / AXP2101_ALDO_VOL_STEPS;
|
||||
writeRegister(AXP2101_LDO_VOL2_CTRL, val);
|
||||
setRegisterBit(AXP2101_LDO_ONOFF_CTRL0, 2);
|
||||
|
||||
if (pmu_irq != 2) {
|
||||
pinMode(PMU_IRQ, INPUT_PULLUP);
|
||||
attachInterrupt(PMU_IRQ, [] {
|
||||
pmu_irq = 1;
|
||||
}, FALLING);
|
||||
}
|
||||
|
||||
// Set charging configuration: 500mA, 4.2V cut off
|
||||
|
||||
// Set constant current charge limit to 500mA (reguster 0x62)
|
||||
// Data sheep (7.3.3.) tells that default value is 1.024 A (which should be fine??)
|
||||
// Data sheet (register table) tells that default value is "{EFUSE,0b,EFUSE}", whatever that is
|
||||
// Let's set this to 500 mA manually to be sure
|
||||
val = readRegister(AXP2101_ICC_CFG);
|
||||
if (val == -1) return -1;
|
||||
val &= 0xE0;
|
||||
writeRegister(AXP2101_ICC_CFG, val | AXP2101_CHG_CUR_500MA);
|
||||
|
||||
#if 0
|
||||
// Set cut-off voltage to 4.2V (register 0x63)
|
||||
// This is the default value, so setting it should not be needed.
|
||||
val = readRegister(AXP2101_CHG_V_CFG);
|
||||
if (val == -1) return -1;
|
||||
val &= 0xFC;
|
||||
writeRegister(AXP2101_CHG_V_CFG, val | AXP2101_CHG_VOL_4V2);
|
||||
#endif
|
||||
|
||||
// Disable TS measurement, enable vsys, vbus, vbat measurement
|
||||
// Disable TS is important for T-Beam 1.2 (no TS thermistor), otherwise it will not charge.
|
||||
writeRegister(AXP2101_ADC_CHANNEL_CTRL, 0x0d);
|
||||
|
||||
// Clear all IRQ
|
||||
getIrqKeyStatus();
|
||||
|
||||
// precharge current (reg 0x61): default value (0101b) should be fine
|
||||
// Termination current (125mA default value) should be fine as well
|
||||
#if 0
|
||||
// Just some debug code
|
||||
Serial.println("All good \n");
|
||||
|
||||
for(int i=0; i<0x80; i++) {
|
||||
val = readRegister(i);
|
||||
Serial.printf("Reg %x: %x (%d)\n", i, val, val);
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AXP2101PMU::disableAllIRQ() {
|
||||
writeRegister(AXP2101_INTEN1, 0);
|
||||
writeRegister(AXP2101_INTEN2, 0);
|
||||
writeRegister(AXP2101_INTEN3, 0);
|
||||
}
|
||||
|
||||
void AXP2101PMU::_enableIRQ(uint8_t addr, uint8_t mask) {
|
||||
int data = readRegister(addr);
|
||||
writeRegister(addr, data | mask);
|
||||
}
|
||||
|
||||
// we want KP_SHORT and KP_LONG interrupts...
|
||||
// IRQen1, in req 0x41h, Bit 2(long)+3(short) (10+11 global)
|
||||
void AXP2101PMU::enableIRQ() {
|
||||
//_enableIRQ( AXP2101_INTEN1, mask&0xFF );
|
||||
_enableIRQ( AXP2101_INTEN2, 0x0C );
|
||||
//_enableIRQ( AXP2101_INTEN3, 0x03 );
|
||||
}
|
||||
|
||||
int AXP2101PMU::getIrqKeyStatus() {
|
||||
int status = readRegister(AXP2101_INTSTS2);
|
||||
|
||||
// Also clear IRQ status
|
||||
writeRegister(AXP2101_INTSTS1, 0xFF);
|
||||
writeRegister(AXP2101_INTSTS2, 0xFF);
|
||||
writeRegister(AXP2101_INTSTS3, 0xFF);
|
||||
|
||||
//
|
||||
if ( status & 0x04 ) return KP_MID;
|
||||
if ( status & 0x08 ) return KP_SHORT;
|
||||
return KP_NONE;
|
||||
}
|
||||
|
||||
int AXP2101PMU::isBatteryConnected() { return -1; }
|
||||
int AXP2101PMU::isVbusIn() { return -1; }
|
||||
int AXP2101PMU::isCharging() { return -1; }
|
||||
float AXP2101PMU::getBattVoltage() { return -1; }
|
||||
float AXP2101PMU::getBattDischargeCurrent() { return -1; }
|
||||
float AXP2101PMU::getBattChargeCurrent() { return -1; }
|
||||
float AXP2101PMU::getAcinVoltage() { return -1; }
|
||||
float AXP2101PMU::getAcinCurrent() { return -1; }
|
||||
float AXP2101PMU::getVbusVoltage() { return -1; }
|
||||
float AXP2101PMU::getVbusCurrent() { return -1; }
|
||||
float AXP2101PMU::getTemperature() { return -1; }
|
||||
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
#include <inttypes.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#define AXP192_SLAVE_ADDRESS 0x34
|
||||
|
||||
enum { TYPE_NONE=-1, TYPE_UNKNOWN=0, TYPE_AXP192, TYPE_AXP2101 };
|
||||
|
||||
class PMU {
|
||||
protected:
|
||||
PMU(TwoWire &wire) : _wire(wire) { };
|
||||
|
||||
public:
|
||||
TwoWire &_wire;
|
||||
static PMU *getInstance(TwoWire &wire);
|
||||
int type;
|
||||
|
||||
static int readRegisterWire(TwoWire &wire, uint8_t reg);
|
||||
int readRegister(uint8_t reg);
|
||||
uint16_t readRegisters_8_4(uint8_t reghi, uint8_t reglo);
|
||||
uint16_t readRegisters_8_5(uint8_t reghi, uint8_t reglo);
|
||||
int writeRegister(uint8_t reg, uint8_t val);
|
||||
int getRegisterBit(uint8_t register, uint8_t bit);
|
||||
int setRegisterBit(uint8_t register, uint8_t bit);
|
||||
int clearRegisterBit(uint8_t register, uint8_t bit);
|
||||
|
||||
int handleIRQ();
|
||||
|
||||
virtual int init();
|
||||
virtual void disableAllIRQ();
|
||||
virtual void enableIRQ();
|
||||
virtual int getIrqKeyStatus();
|
||||
|
||||
virtual int isBatteryConnected();
|
||||
virtual int isVbusIn();
|
||||
virtual int isCharging();
|
||||
virtual float getBattVoltage();
|
||||
virtual float getBattDischargeCurrent();
|
||||
virtual float getBattChargeCurrent();
|
||||
virtual float getAcinVoltage();
|
||||
virtual float getAcinCurrent();
|
||||
virtual float getVbusVoltage();
|
||||
virtual float getVbusCurrent();
|
||||
virtual float getTemperature();
|
||||
};
|
||||
|
||||
/* Interface */
|
||||
class AXP192PMU : public PMU {
|
||||
public:
|
||||
AXP192PMU(TwoWire &wire) : PMU(wire) { type = TYPE_AXP192; };
|
||||
int init();
|
||||
void disableAllIRQ();
|
||||
void enableIRQ();
|
||||
int getIrqKeyStatus();
|
||||
|
||||
int isBatteryConnected();
|
||||
int isVbusIn();
|
||||
int isCharging();
|
||||
float getBattVoltage();
|
||||
float getBattDischargeCurrent();
|
||||
float getBattChargeCurrent();
|
||||
float getAcinVoltage();
|
||||
float getAcinCurrent();
|
||||
float getVbusVoltage();
|
||||
float getVbusCurrent();
|
||||
float getTemperature();
|
||||
|
||||
protected:
|
||||
void _enableIRQ(uint8_t addr, uint8_t mask);
|
||||
|
||||
int setVoltageReg(uint8_t reg, uint8_t regval);
|
||||
int setDC1(uint16_t millivolt);
|
||||
int setDC2(uint16_t millivolt);
|
||||
int setDC3(uint16_t millivolt);
|
||||
int setLDO2(uint16_t millivolt);
|
||||
int setLDOio(uint16_t millivolt);
|
||||
|
||||
int enableDC1(bool onff = true);
|
||||
int enableDC3(bool onoff = true);
|
||||
int enableLDO2(bool onoff = true);
|
||||
int enableLDO3(bool onoff = true);
|
||||
int enableDC2(bool onoff = true);
|
||||
int enableEXTEN(bool onoff = true);
|
||||
|
||||
int enableADC(uint8_t channels);
|
||||
};
|
||||
|
||||
class AXP2101PMU : public PMU {
|
||||
public:
|
||||
AXP2101PMU(TwoWire &wire) : PMU(wire) { };
|
||||
int init();
|
||||
void disableAllIRQ();
|
||||
void enableIRQ();
|
||||
int getIrqKeyStatus();
|
||||
|
||||
int isBatteryConnected();
|
||||
int isVbusIn();
|
||||
int isCharging();
|
||||
float getBattVoltage();
|
||||
float getBattDischargeCurrent();
|
||||
float getBattChargeCurrent();
|
||||
float getAcinVoltage();
|
||||
float getAcinCurrent();
|
||||
float getVbusVoltage();
|
||||
float getVbusCurrent();
|
||||
float getTemperature();
|
||||
|
||||
protected:
|
||||
void _enableIRQ(uint8_t addr, uint8_t mask);
|
||||
|
||||
int setVBACKUP(uint16_t millivolt);
|
||||
int setDCDC1(uint16_t millivolt);
|
||||
int setALDO2(uint16_t millivolt);
|
||||
int setALDO3(uint16_t millivolt);
|
||||
|
||||
};
|
|
@ -0,0 +1,271 @@
|
|||
#include "posinfo.h"
|
||||
|
||||
#include <MicroNMEA.h>
|
||||
|
||||
|
||||
// Sation position obtained from GPS (if available)
|
||||
struct StationPos gpsPos;
|
||||
|
||||
// Station position to use (from GPS or fixed)
|
||||
struct StationPos posInfo;
|
||||
|
||||
|
||||
/* SH_LOC_OFF: never send position information to SondeHub
|
||||
SH_LOC_FIXED: send fixed position (if specified in config) to sondehub
|
||||
SH_LOC_CHASE: always activate chase mode and send GPS position (if available)
|
||||
SH_LOC_AUTO: if there is no valid GPS position, or GPS position < MIN_LOC_AUTO_DIST away from known fixed position: use FIXED mode
|
||||
otherwise, i.e. if there is a valid GPS position and (either no fixed position in config, or GPS position is far away from fixed position), use CHASE mode.
|
||||
*/
|
||||
// same constants used for SondeHub and APRS
|
||||
|
||||
/* auto mode is chase if valid GPS position and (no fixed location entered OR valid GPS position and distance in lat/lon deg to fixed location > threshold) */
|
||||
//#define MIN_LOC_AUTO_DIST 200 /* meter */
|
||||
//#define SH_LOC_AUTO_IS_CHASE ( gpsPos.valid && ( (isnan(sonde.config.rxlat) || isnan(sonde.config.rxlon) ) || \
|
||||
// calcLatLonDist( gpsPos.lat, gpsPos.lon, sonde.config.rxlat, sonde.config.rxlon ) > MIN_LOC_AUTO_DIST ) )
|
||||
//extern float calcLatLonDist(float lat1, float lon1, float lat2, float lon2);
|
||||
|
||||
/////
|
||||
// set fixed position based on config
|
||||
void fixedToPosInfo() {
|
||||
memset(&posInfo, 0, sizeof(posInfo));
|
||||
if( isnan(sonde.config.rxlat) || isnan(sonde.config.rxlon) )
|
||||
return;
|
||||
posInfo.lat = sonde.config.rxlat;
|
||||
posInfo.lon = sonde.config.rxlon;
|
||||
posInfo.alt = sonde.config.rxalt;
|
||||
posInfo.valid = 1;
|
||||
}
|
||||
|
||||
|
||||
///// GPS handling functions
|
||||
|
||||
static char buffer[85];
|
||||
static MicroNMEA nmea(buffer, sizeof(buffer));
|
||||
|
||||
|
||||
/// Arrg. MicroNMEA changes type definition... so lets auto-infer type
|
||||
template<typename T>
|
||||
//void unkHandler(const MicroNMEA& nmea) {
|
||||
void unkHandler(T nmea) {
|
||||
if (strcmp(nmea.getMessageID(), "VTG") == 0) {
|
||||
const char *s = nmea.getSentence();
|
||||
while (*s && *s != ',') s++;
|
||||
if (*s == ',') s++; else return;
|
||||
if (*s == ',') return; /// no new course data
|
||||
int lastCourse = nmea.parseFloat(s, 0, NULL);
|
||||
Serial.printf("Course update: %d\n", lastCourse);
|
||||
} else if (strcmp(nmea.getMessageID(), "GST") == 0) {
|
||||
// get horizontal accuracy for android app on devices without gps
|
||||
// GPGST,time,rms,-,-,-,stdlat,stdlon,stdalt,cs
|
||||
const char *s = nmea.getSentence();
|
||||
while (*s && *s != ',') s++; // #0: GST
|
||||
if (*s == ',') s++; else return;
|
||||
while (*s && *s != ',') s++; // #1: time: skip
|
||||
if (*s == ',') s++; else return;
|
||||
while (*s && *s != ',') s++; // #1: rms: skip
|
||||
if (*s == ',') s++; else return;
|
||||
while (*s && *s != ',') s++; // #1: (-): skip
|
||||
if (*s == ',') s++; else return;
|
||||
while (*s && *s != ',') s++; // #1: (-): skip
|
||||
if (*s == ',') s++; else return;
|
||||
while (*s && *s != ',') s++; // #1: (-): skip
|
||||
if (*s == ',') s++; else return;
|
||||
// stdlat
|
||||
int stdlat = nmea.parseFloat(s, 1, NULL);
|
||||
while (*s && *s != ',') s++;
|
||||
if (*s == ',') s++; else return;
|
||||
// stdlong
|
||||
int stdlon = nmea.parseFloat(s, 1, NULL);
|
||||
// calculate position error as 1-signma horizontal RMS
|
||||
// I guess that is equivalent to Androids getAccurac()?
|
||||
int poserr = 0;
|
||||
if (stdlat < 10000 && stdlon < 10000) { // larger errors: no GPS fix, avoid overflow in *
|
||||
poserr = (int)(sqrt(0.5 * (stdlat * stdlat + stdlon * stdlon)));
|
||||
}
|
||||
//Serial.printf("\nHorizontal accuracy: %d, %d => %.1fm\n", stdlat, stdlon, 0.1*poserr);
|
||||
gpsPos.accuracy = poserr;
|
||||
}
|
||||
}
|
||||
|
||||
// 1 deg = aprox. 100 km ==> approx. 200m
|
||||
#define AUTO_CHASE_THRESHOLD 0.002
|
||||
|
||||
//#define DEBUG_GPS
|
||||
static bool gpsCourseOld;
|
||||
static int lastCourse;
|
||||
void gpsTask(void *parameter) {
|
||||
nmea.setUnknownSentenceHandler(unkHandler);
|
||||
|
||||
while (1) {
|
||||
while (Serial2.available()) {
|
||||
char c = Serial2.read();
|
||||
//Serial.print(c);
|
||||
if (nmea.process(c)) {
|
||||
gpsPos.valid = nmea.isValid();
|
||||
if (gpsPos.valid) {
|
||||
gpsPos.lon = nmea.getLongitude() * 0.000001;
|
||||
gpsPos.lat = nmea.getLatitude() * 0.000001;
|
||||
long alt = 0;
|
||||
nmea.getAltitude(alt);
|
||||
gpsPos.alt = (int)(alt / 1000);
|
||||
gpsPos.course = (int)(nmea.getCourse() / 1000);
|
||||
gpsCourseOld = false;
|
||||
if (gpsPos.course == 0) {
|
||||
// either north or not new
|
||||
if (lastCourse != 0) // use old value...
|
||||
{
|
||||
gpsCourseOld = true;
|
||||
gpsPos.course = lastCourse;
|
||||
}
|
||||
}
|
||||
if (gpsPos.lon == 0 && gpsPos.lat == 0) gpsPos.valid = false;
|
||||
}
|
||||
/* Check if home */
|
||||
if(gpsPos.valid) {
|
||||
float d = fabs(gpsPos.lon - sonde.config.rxlon);
|
||||
d += fabs(gpsPos.lat - sonde.config.rxlat);
|
||||
if(!posInfo.chase && d > AUTO_CHASE_THRESHOLD) {
|
||||
posInfo = gpsPos;
|
||||
posInfo.chase = 1;
|
||||
} else if ( posInfo.chase && d < AUTO_CHASE_THRESHOLD/2 ) {
|
||||
fixedToPosInfo();
|
||||
}
|
||||
}
|
||||
|
||||
gpsPos.hdop = nmea.getHDOP();
|
||||
gpsPos.sat = nmea.getNumSatellites();
|
||||
gpsPos.speed = nmea.getSpeed() / 1000.0 * 0.514444; // speed is in m/s nmea.getSpeed is in 0.001 knots
|
||||
#ifdef DEBUG_GPS
|
||||
uint8_t hdop = nmea.getHDOP();
|
||||
Serial.printf(" =>: valid: %d N %f E %f alt %d course:%d dop:%d\n", gpsPos.valid ? 1 : 0, gpsPos.lat, gpsPos.lon, gpsPos.alt, gpsPos.course, hdop);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
delay(50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#define UBX_SYNCH_1 0xB5
|
||||
#define UBX_SYNCH_2 0x62
|
||||
uint8_t ubx_set9k6[] = {UBX_SYNCH_1, UBX_SYNCH_2, 0x06, 0x00, 0x14, 0x00, 0x01, 0x00, 0x00, 0x00, 0xC0, 0x08, 0x00, 0x00, 0x80, 0x25, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8D, 0x8F};
|
||||
uint8_t ubx_factorydef[] = {UBX_SYNCH_1, UBX_SYNCH_2, 0x06, 0x09, 13, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0x13, 0x7c };
|
||||
uint8_t ubx_hardreset[] = {UBX_SYNCH_1, UBX_SYNCH_2, 0x06, 0x04, 4, 0, 0xff, 0xff, 0, 0, 0x0C, 0x5D };
|
||||
// GPGST: Class 0xF0 Id 0x07
|
||||
uint8_t ubx_enable_gpgst[] = {UBX_SYNCH_1, UBX_SYNCH_2, 0x06, 0x01, 3, 0, 0xF0, 0x07, 2, 0x03, 0x1F};
|
||||
|
||||
void dumpGPS() {
|
||||
while (Serial2.available()) {
|
||||
char c = Serial2.read();
|
||||
Serial.printf("%02x ", (uint8_t)c);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void initGPS() {
|
||||
if (sonde.config.gps_rxd < 0) return; // GPS disabled
|
||||
if (sonde.config.gps_txd >= 0) { // TX enable, thus try setting baud to 9600 and do a factory reset
|
||||
File testfile = SPIFFS.open("/GPSRESET", FILE_READ);
|
||||
if (testfile && !testfile.isDirectory()) {
|
||||
testfile.close();
|
||||
Serial.println("GPS resetting baud to 9k6...");
|
||||
/* TODO: debug:
|
||||
Sometimes I have seen the Serial2.begin to cause a reset
|
||||
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)
|
||||
Backtrace: 0x40081d2f:0x3ffc11b0 0x40087969:0x3ffc11e0 0x4000bfed:0x3ffb1db0 0x4008b7dd:0x3ffb1dc0 0x4017afee:0x3ffb1de0 0x4017b04b:0x3ffb1e20 0x4010722b:0x3ffb1e50 0x40107303:0x3ffb1e70 0x4010782d:0x3ffb1e90 0x40103814:0x3ffb1ed0 0x400d8772:0x3ffb1f10 0x400d9057:0x3ffb1f60 0x40107aca:0x3ffb1fb0 0x4008a63e:0x3ffb1fd0
|
||||
#0 0x40081d2f:0x3ffc11b0 in _uart_isr at /Users/hansi/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-uart.c:464
|
||||
#1 0x40087969:0x3ffc11e0 in _xt_lowint1 at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/xtensa_vectors.S:1154
|
||||
#2 0x4000bfed:0x3ffb1db0 in ?? ??:0
|
||||
#3 0x4008b7dd:0x3ffb1dc0 in vTaskExitCritical at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/tasks.c:3507
|
||||
#4 0x4017afee:0x3ffb1de0 in esp_intr_alloc_intrstatus at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp32/intr_alloc.c:784
|
||||
#5 0x4017b04b:0x3ffb1e20 in esp_intr_alloc at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp32/intr_alloc.c:784
|
||||
#6 0x4010722b:0x3ffb1e50 in uartEnableInterrupt at /Users/hansi/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-uart.c:464
|
||||
#7 0x40107303:0x3ffb1e70 in uartAttachRx at /Users/hansi/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-uart.c:464
|
||||
#8 0x4010782d:0x3ffb1e90 in uartBegin at /Users/hansi/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-uart.c:464
|
||||
#9 0x40103814:0x3ffb1ed0 in HardwareSerial::begin(unsigned long, unsigned int, signed char, signed char, bool, unsigned long) at /Users/hansi/.platformio/packages/framework-arduinoespressif32/cores/esp32/HardwareSerial.cpp:190
|
||||
*/
|
||||
Serial2.begin(115200, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
|
||||
Serial2.write(ubx_set9k6, sizeof(ubx_set9k6));
|
||||
delay(200);
|
||||
Serial2.begin(38400, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
|
||||
Serial2.write(ubx_set9k6, sizeof(ubx_set9k6));
|
||||
delay(200);
|
||||
Serial2.begin(19200, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
|
||||
Serial2.write(ubx_set9k6, sizeof(ubx_set9k6));
|
||||
Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
|
||||
delay(1000);
|
||||
dumpGPS();
|
||||
Serial.println("GPS factory reset...");
|
||||
Serial2.write(ubx_factorydef, sizeof(ubx_factorydef));
|
||||
delay(1000);
|
||||
dumpGPS();
|
||||
delay(1000);
|
||||
dumpGPS();
|
||||
delay(1000);
|
||||
dumpGPS();
|
||||
SPIFFS.remove("/GPSRESET");
|
||||
} else if (testfile) {
|
||||
Serial.println("GPS reset file: not found/isdir");
|
||||
testfile.close();
|
||||
Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
|
||||
}
|
||||
// Enable GPGST messages
|
||||
Serial2.write(ubx_enable_gpgst, sizeof(ubx_enable_gpgst));
|
||||
} else {
|
||||
Serial2.begin(9600, SERIAL_8N1, sonde.config.gps_rxd, sonde.config.gps_txd);
|
||||
}
|
||||
xTaskCreate( gpsTask, "gpsTask",
|
||||
5000, /* stack size */
|
||||
NULL, /* paramter */
|
||||
1, /* priority */
|
||||
NULL); /* task handle*/
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Getting GPS data from App (phone)
|
||||
|
||||
void parseGpsJson(char *data, int len) {
|
||||
char *key = NULL;
|
||||
char *value = NULL;
|
||||
// very simple json parser: look for ", then key, then ", then :, then number, then , or } or \0
|
||||
for (int i = 0; i < len; i++) {
|
||||
if (key == NULL) {
|
||||
if (data[i] != '"') continue;
|
||||
key = data + i + 1;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (value == NULL) {
|
||||
if (data[i] != ':') continue;
|
||||
value = data + i + 1;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (data[i] == ',' || data[i] == '}' || data[i] == 0) {
|
||||
// get value
|
||||
double val = strtod(value, NULL);
|
||||
// get data
|
||||
if (strncmp(key, "lat", 3) == 0) {
|
||||
gpsPos.lat = val;
|
||||
}
|
||||
else if (strncmp(key, "lon", 3) == 0) {
|
||||
gpsPos.lon = val;
|
||||
}
|
||||
else if (strncmp(key, "alt", 3) == 0) {
|
||||
gpsPos.alt = (int)val;
|
||||
}
|
||||
else if (strncmp(key, "course", 6) == 0) {
|
||||
gpsPos.course = (int)val;
|
||||
}
|
||||
gpsPos.valid = true;
|
||||
|
||||
// next item:
|
||||
if (data[i] != ',') break;
|
||||
key = NULL;
|
||||
value = NULL;
|
||||
}
|
||||
}
|
||||
if (gpsPos.lat == 0 && gpsPos.lon == 0) gpsPos.valid = false;
|
||||
Serial.printf("Parse result: lat=%f, lon=%f, alt=%d, valid=%d\n", gpsPos.lat, gpsPos.lon, gpsPos.alt, gpsPos.valid);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
#ifndef _posinfo_h
|
||||
#define _posinfo_h
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "Sonde.h"
|
||||
#include <SPIFFS.h>
|
||||
|
||||
enum { SH_LOC_OFF, SH_LOC_FIXED, SH_LOC_CHASE, SH_LOC_AUTO };
|
||||
|
||||
|
||||
// Handling of station position (GPS, fixed location)
|
||||
|
||||
struct StationPos {
|
||||
double lat;
|
||||
double lon;
|
||||
int alt;
|
||||
float speed;
|
||||
int16_t course;
|
||||
int16_t accuracy;
|
||||
int16_t hdop;
|
||||
int8_t sat;
|
||||
int8_t valid;
|
||||
int8_t chase;
|
||||
};
|
||||
|
||||
extern struct StationPos gpsPos, posInfo;
|
||||
|
||||
|
||||
// Initialize GPS chip
|
||||
void initGPS();
|
||||
|
||||
// Update position from app (if not local GPS chip)
|
||||
void parseGpsJson(char *data, int len);
|
||||
|
||||
// Update position from static config
|
||||
void fixedToPosInfo();
|
||||
|
||||
#endif
|
|
@ -1,3 +1,6 @@
|
|||
#include "../features.h"
|
||||
#if FEATURE_RS92
|
||||
|
||||
/* SPDX-License-Identifier: GPL-3.0
|
||||
* based on https://github.com/rs1729/RS/blob/master/rs92/rs92gps.c
|
||||
*
|
||||
|
@ -1202,3 +1205,4 @@ void get_eph(const char *file) {
|
|||
if (!option_der) d_err = 1000;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const char *version_name = "rdzTTGOsonde";
|
||||
const char *version_id = "master_v0.9.2";
|
||||
const char *version_id = "devel20240107";
|
||||
const int SPIFFS_MAJOR=2;
|
||||
const int SPIFFS_MINOR=16;
|
||||
const int SPIFFS_MINOR=17;
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
We currently use https://github.com/platformio/platform-espressif32.git#v6.3.1 for platformIO builds
|
||||
|
||||
When upgrading to v6.4.0, there is a problem:
|
||||
|
||||
If a web browser sends concurrent requests (mainly reproducible with the "config" tab, where the config.html loads two files, rdz.js and cfg.js from SPIFFS),
|
||||
one of the two often gets garbled (TCP response is missing its first 4 frames)
|
||||
bad: two TCP packets with 1490 and 1094 bytes (data: 1436 and 1040 bytes)
|
||||
good: TCP packets with 1490 (5x) and 1094 bytes (data: 1436 and 1040 bytes)
|
||||
|
||||
Somehow something seems to go wrong internally in the TCP stack. Not reproducible with a minimalistic example, but happens almost always with the full code base.
|
||||
|
||||
|
||||
Two mitigations:
|
||||
- Do not upgrade to v6.4.0
|
||||
- Avoid double request by browser. For this, cache control has been added (useful anyway). Implication: After an upgrade, browser might still use old rdz.js and cfg.js for 15 Minutes.
|
||||
|
||||
|
||||
|
||||
|
||||
**
|
||||
|
||||
In v6.5.0 there seems to be another issue: In AP mode the client does no longer get an IP address -- needs to be checked
|
||||
|
||||
|
||||
**
|
||||
|
||||
Tested with 6.3.1:
|
||||
|
||||
Sometimes, when a AP is turned off (for < 1s) and turned on again, the WiFi produces a Disconnect event (#5) but no Clients cleared event (#3)
|
||||
In that case, the WiFi state remains connected (as in WiFi.isConnected()), so network is not working, but never reestablished.
|
||||
|
||||
fix: added WiFi.mode(0) when event #5 is received.
|
||||
|
||||
Still, for each reconnect, there apparently is a memory leak. Heap goes down by 40 bytes for each reconnect.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
# Connect the socket to the port where the server is listening
|
||||
server_address = ('192.168.4.1', 80)
|
||||
|
||||
# Create a TCP/IP socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.connect(server_address)
|
||||
|
||||
# Create a TCP/IP socket
|
||||
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock2.connect(server_address)
|
||||
|
||||
# send http request
|
||||
req = '''GET /{} HTTP/1.1
|
||||
Host: 192.168.4.1
|
||||
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:121.0) Gecko/20100101 Firefox/121.0
|
||||
Accept: */*
|
||||
Accept-Language: en-US,en;q=0.5
|
||||
Accept-Encoding: gzip, deflate
|
||||
DNT: 1
|
||||
Connection: keep-alive
|
||||
Referer: http://192.168.4.1/qrg.html
|
||||
|
||||
'''
|
||||
|
||||
sock.sendall(req.format("rdz.js").encode())
|
||||
sock2.sendall(req.format("cfg.js").encode())
|
||||
|
||||
time.sleep(0.5)
|
||||
sock.close()
|
||||
sock2.close()
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x140000,
|
||||
app1, app, ota_1, 0x150000,0x140000,
|
||||
spiffs, data, spiffs, 0x290000,0x160000,
|
||||
coredump, data, coredump,0x3F0000,0x10000,
|
|
|
@ -10,22 +10,21 @@
|
|||
|
||||
[platformio]
|
||||
src_dir = RX_FSK
|
||||
;# lib_dir = RX_FSK
|
||||
data_dir = RX_FSK/data
|
||||
|
||||
[extra]
|
||||
lib_deps_builtin =
|
||||
; src
|
||||
lib_deps_external =
|
||||
olikraus/U8g2 @ ^2.28.8
|
||||
AXP202X_Library
|
||||
olikraus/U8g2 @ ^2.35.8
|
||||
stevemarple/MicroNMEA @ ^2.0.5
|
||||
me-no-dev/ESP Async WebServer @ ^1.2.3
|
||||
https://github.com/moononournation/Arduino_GFX#v1.1.5
|
||||
https://github.com/me-no-dev/ESPAsyncWebServer/archive/refs/heads/master.zip
|
||||
https://github.com/moononournation/Arduino_GFX#v1.2.9
|
||||
https://github.com/dx168b/async-mqtt-client
|
||||
|
||||
[env:ttgo-lora32]
|
||||
platform = https://github.com/platformio/platform-espressif32.git#v3.3.2
|
||||
# Issues with 6.4.0 (TCP corruption) and 6.5.0 (no DHCP response from AP) need to be investigated before upgrading further.
|
||||
platform = https://github.com/platformio/platform-espressif32.git#v6.3.0
|
||||
board = ttgo-lora32-v1
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
|
@ -34,6 +33,8 @@ lib_deps =
|
|||
${extra.lib_deps_external}
|
||||
paulstoffregen/Time@^1.6.0
|
||||
lib_ignore = Time
|
||||
; Same as with ArduinoIDE. Saves around 27k code
|
||||
build_flags = -DCORE_DEBUG_LEVEL=0
|
||||
|
||||
; Add / remove the following two lines for separate fonts partition in flash
|
||||
; after changes:
|
||||
|
@ -45,3 +46,7 @@ lib_ignore = Time
|
|||
;
|
||||
extra_scripts = post:scripts/makefontpartition.py
|
||||
;board_build.partitions = partition-fonts.csv
|
||||
|
||||
; Uncomment the following if you want to have the partition scheme used in the ESP32 board version 2.0.x of ArduinoIDE
|
||||
; board_build.partitions = partitions-esp32v2.csv
|
||||
|
||||
|
|
|
@ -0,0 +1,577 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# ESP32 partition table generation tool
|
||||
#
|
||||
# Converts partition tables to/from CSV and binary formats.
|
||||
#
|
||||
# See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html
|
||||
# for explanation of partition table structure and uses.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016-2021 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import binascii
|
||||
import errno
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
|
||||
MD5_PARTITION_BEGIN = b'\xEB\xEB' + b'\xFF' * 14 # The first 2 bytes are like magic numbers for MD5 sum
|
||||
PARTITION_TABLE_SIZE = 0x1000 # Size of partition table
|
||||
|
||||
MIN_PARTITION_SUBTYPE_APP_OTA = 0x10
|
||||
NUM_PARTITION_SUBTYPE_APP_OTA = 16
|
||||
|
||||
__version__ = '1.2'
|
||||
|
||||
APP_TYPE = 0x00
|
||||
DATA_TYPE = 0x01
|
||||
|
||||
TYPES = {
|
||||
'app': APP_TYPE,
|
||||
'data': DATA_TYPE,
|
||||
}
|
||||
|
||||
|
||||
def get_ptype_as_int(ptype):
|
||||
""" Convert a string which might be numeric or the name of a partition type to an integer """
|
||||
try:
|
||||
return TYPES[ptype]
|
||||
except KeyError:
|
||||
try:
|
||||
return int(ptype, 0)
|
||||
except TypeError:
|
||||
return ptype
|
||||
|
||||
|
||||
# Keep this map in sync with esp_partition_subtype_t enum in esp_partition.h
|
||||
SUBTYPES = {
|
||||
APP_TYPE: {
|
||||
'factory': 0x00,
|
||||
'test': 0x20,
|
||||
},
|
||||
DATA_TYPE: {
|
||||
'ota': 0x00,
|
||||
'phy': 0x01,
|
||||
'nvs': 0x02,
|
||||
'coredump': 0x03,
|
||||
'nvs_keys': 0x04,
|
||||
'efuse': 0x05,
|
||||
'undefined': 0x06,
|
||||
'esphttpd': 0x80,
|
||||
'fat': 0x81,
|
||||
'spiffs': 0x82,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_subtype_as_int(ptype, subtype):
|
||||
""" Convert a string which might be numeric or the name of a partition subtype to an integer """
|
||||
try:
|
||||
return SUBTYPES[get_ptype_as_int(ptype)][subtype]
|
||||
except KeyError:
|
||||
try:
|
||||
return int(subtype, 0)
|
||||
except TypeError:
|
||||
return subtype
|
||||
|
||||
|
||||
ALIGNMENT = {
|
||||
APP_TYPE: 0x10000,
|
||||
DATA_TYPE: 0x4,
|
||||
}
|
||||
|
||||
|
||||
STRICT_DATA_ALIGNMENT = 0x1000
|
||||
|
||||
|
||||
def get_alignment_for_type(ptype):
|
||||
return ALIGNMENT.get(ptype, ALIGNMENT[DATA_TYPE])
|
||||
|
||||
|
||||
quiet = False
|
||||
md5sum = True
|
||||
secure = False
|
||||
offset_part_table = 0
|
||||
|
||||
|
||||
def status(msg):
|
||||
""" Print status message to stderr """
|
||||
if not quiet:
|
||||
critical(msg)
|
||||
|
||||
|
||||
def critical(msg):
|
||||
""" Print critical message to stderr """
|
||||
sys.stderr.write(msg)
|
||||
sys.stderr.write('\n')
|
||||
|
||||
|
||||
class PartitionTable(list):
|
||||
def __init__(self):
|
||||
super(PartitionTable, self).__init__(self)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, f):
|
||||
data = f.read()
|
||||
data_is_binary = data[0:2] == PartitionDefinition.MAGIC_BYTES
|
||||
if data_is_binary:
|
||||
status('Parsing binary partition input...')
|
||||
return cls.from_binary(data), True
|
||||
|
||||
data = data.decode()
|
||||
status('Parsing CSV input...')
|
||||
return cls.from_csv(data), False
|
||||
|
||||
@classmethod
|
||||
def from_csv(cls, csv_contents):
|
||||
res = PartitionTable()
|
||||
lines = csv_contents.splitlines()
|
||||
|
||||
def expand_vars(f):
|
||||
f = os.path.expandvars(f)
|
||||
m = re.match(r'(?<!\\)\$([A-Za-z_][A-Za-z0-9_]*)', f)
|
||||
if m:
|
||||
raise InputError("unknown variable '%s'" % m.group(1))
|
||||
return f
|
||||
|
||||
for line_no in range(len(lines)):
|
||||
line = expand_vars(lines[line_no]).strip()
|
||||
if line.startswith('#') or len(line) == 0:
|
||||
continue
|
||||
try:
|
||||
res.append(PartitionDefinition.from_csv(line, line_no + 1))
|
||||
except InputError as err:
|
||||
raise InputError('Error at line %d: %s' % (line_no + 1, err))
|
||||
except Exception:
|
||||
critical('Unexpected error parsing CSV line %d: %s' % (line_no + 1, line))
|
||||
raise
|
||||
|
||||
# fix up missing offsets & negative sizes
|
||||
last_end = offset_part_table + PARTITION_TABLE_SIZE # first offset after partition table
|
||||
for e in res:
|
||||
if e.offset is not None and e.offset < last_end:
|
||||
if e == res[0]:
|
||||
raise InputError('CSV Error: First partition offset 0x%x overlaps end of partition table 0x%x'
|
||||
% (e.offset, last_end))
|
||||
else:
|
||||
raise InputError('CSV Error: Partitions overlap. Partition at line %d sets offset 0x%x. Previous partition ends 0x%x'
|
||||
% (e.line_no, e.offset, last_end))
|
||||
if e.offset is None:
|
||||
pad_to = get_alignment_for_type(e.type)
|
||||
if last_end % pad_to != 0:
|
||||
last_end += pad_to - (last_end % pad_to)
|
||||
e.offset = last_end
|
||||
if e.size < 0:
|
||||
e.size = -e.size - e.offset
|
||||
last_end = e.offset + e.size
|
||||
|
||||
return res
|
||||
|
||||
def __getitem__(self, item):
|
||||
""" Allow partition table access via name as well as by
|
||||
numeric index. """
|
||||
if isinstance(item, str):
|
||||
for x in self:
|
||||
if x.name == item:
|
||||
return x
|
||||
raise ValueError("No partition entry named '%s'" % item)
|
||||
else:
|
||||
return super(PartitionTable, self).__getitem__(item)
|
||||
|
||||
def find_by_type(self, ptype, subtype):
|
||||
""" Return a partition by type & subtype, returns
|
||||
None if not found """
|
||||
# convert ptype & subtypes names (if supplied this way) to integer values
|
||||
ptype = get_ptype_as_int(ptype)
|
||||
subtype = get_subtype_as_int(ptype, subtype)
|
||||
|
||||
for p in self:
|
||||
if p.type == ptype and p.subtype == subtype:
|
||||
yield p
|
||||
return
|
||||
|
||||
def find_by_name(self, name):
|
||||
for p in self:
|
||||
if p.name == name:
|
||||
return p
|
||||
return None
|
||||
|
||||
def verify(self):
|
||||
# verify each partition individually
|
||||
for p in self:
|
||||
p.verify()
|
||||
|
||||
# check on duplicate name
|
||||
names = [p.name for p in self]
|
||||
duplicates = set(n for n in names if names.count(n) > 1)
|
||||
|
||||
# print sorted duplicate partitions by name
|
||||
if len(duplicates) != 0:
|
||||
critical('A list of partitions that have the same name:')
|
||||
for p in sorted(self, key=lambda x:x.name):
|
||||
if len(duplicates.intersection([p.name])) != 0:
|
||||
critical('%s' % (p.to_csv()))
|
||||
raise InputError('Partition names must be unique')
|
||||
|
||||
# check for overlaps
|
||||
last = None
|
||||
for p in sorted(self, key=lambda x:x.offset):
|
||||
if p.offset < offset_part_table + PARTITION_TABLE_SIZE:
|
||||
raise InputError('Partition offset 0x%x is below 0x%x' % (p.offset, offset_part_table + PARTITION_TABLE_SIZE))
|
||||
if last is not None and p.offset < last.offset + last.size:
|
||||
raise InputError('Partition at 0x%x overlaps 0x%x-0x%x' % (p.offset, last.offset, last.offset + last.size - 1))
|
||||
last = p
|
||||
|
||||
# check that otadata should be unique
|
||||
otadata_duplicates = [p for p in self if p.type == TYPES['data'] and p.subtype == SUBTYPES[DATA_TYPE]['ota']]
|
||||
if len(otadata_duplicates) > 1:
|
||||
for p in otadata_duplicates:
|
||||
critical('%s' % (p.to_csv()))
|
||||
raise InputError('Found multiple otadata partitions. Only one partition can be defined with type="data"(1) and subtype="ota"(0).')
|
||||
|
||||
if len(otadata_duplicates) == 1 and otadata_duplicates[0].size != 0x2000:
|
||||
p = otadata_duplicates[0]
|
||||
critical('%s' % (p.to_csv()))
|
||||
raise InputError('otadata partition must have size = 0x2000')
|
||||
|
||||
def flash_size(self):
|
||||
""" Return the size that partitions will occupy in flash
|
||||
(ie the offset the last partition ends at)
|
||||
"""
|
||||
try:
|
||||
last = sorted(self, reverse=True)[0]
|
||||
except IndexError:
|
||||
return 0 # empty table!
|
||||
return last.offset + last.size
|
||||
|
||||
def verify_size_fits(self, flash_size_bytes: int) -> None:
|
||||
""" Check that partition table fits into the given flash size.
|
||||
Raises InputError otherwise.
|
||||
"""
|
||||
table_size = self.flash_size()
|
||||
if flash_size_bytes < table_size:
|
||||
mb = 1024 * 1024
|
||||
raise InputError('Partitions tables occupies %.1fMB of flash (%d bytes) which does not fit in configured '
|
||||
"flash size %dMB. Change the flash size in menuconfig under the 'Serial Flasher Config' menu." %
|
||||
(table_size / mb, table_size, flash_size_bytes / mb))
|
||||
|
||||
@classmethod
|
||||
def from_binary(cls, b):
|
||||
md5 = hashlib.md5()
|
||||
result = cls()
|
||||
for o in range(0,len(b),32):
|
||||
data = b[o:o + 32]
|
||||
if len(data) != 32:
|
||||
raise InputError('Partition table length must be a multiple of 32 bytes')
|
||||
if data == b'\xFF' * 32:
|
||||
return result # got end marker
|
||||
if md5sum and data[:2] == MD5_PARTITION_BEGIN[:2]: # check only the magic number part
|
||||
if data[16:] == md5.digest():
|
||||
continue # the next iteration will check for the end marker
|
||||
else:
|
||||
raise InputError("MD5 checksums don't match! (computed: 0x%s, parsed: 0x%s)" % (md5.hexdigest(), binascii.hexlify(data[16:])))
|
||||
else:
|
||||
md5.update(data)
|
||||
result.append(PartitionDefinition.from_binary(data))
|
||||
raise InputError('Partition table is missing an end-of-table marker')
|
||||
|
||||
def to_binary(self):
|
||||
result = b''.join(e.to_binary() for e in self)
|
||||
if md5sum:
|
||||
result += MD5_PARTITION_BEGIN + hashlib.md5(result).digest()
|
||||
if len(result) >= MAX_PARTITION_LENGTH:
|
||||
raise InputError('Binary partition table length (%d) longer than max' % len(result))
|
||||
result += b'\xFF' * (MAX_PARTITION_LENGTH - len(result)) # pad the sector, for signing
|
||||
return result
|
||||
|
||||
def to_csv(self, simple_formatting=False):
|
||||
rows = ['# ESP-IDF Partition Table',
|
||||
'# Name, Type, SubType, Offset, Size, Flags']
|
||||
rows += [x.to_csv(simple_formatting) for x in self]
|
||||
return '\n'.join(rows) + '\n'
|
||||
|
||||
|
||||
class PartitionDefinition(object):
|
||||
MAGIC_BYTES = b'\xAA\x50'
|
||||
|
||||
# dictionary maps flag name (as used in CSV flags list, property name)
|
||||
# to bit set in flags words in binary format
|
||||
FLAGS = {
|
||||
'encrypted': 0
|
||||
}
|
||||
|
||||
# add subtypes for the 16 OTA slot values ("ota_XX, etc.")
|
||||
for ota_slot in range(NUM_PARTITION_SUBTYPE_APP_OTA):
|
||||
SUBTYPES[TYPES['app']]['ota_%d' % ota_slot] = MIN_PARTITION_SUBTYPE_APP_OTA + ota_slot
|
||||
|
||||
def __init__(self):
|
||||
self.name = ''
|
||||
self.type = None
|
||||
self.subtype = None
|
||||
self.offset = None
|
||||
self.size = None
|
||||
self.encrypted = False
|
||||
|
||||
@classmethod
|
||||
def from_csv(cls, line, line_no):
|
||||
""" Parse a line from the CSV """
|
||||
line_w_defaults = line + ',,,,' # lazy way to support default fields
|
||||
fields = [f.strip() for f in line_w_defaults.split(',')]
|
||||
|
||||
res = PartitionDefinition()
|
||||
res.line_no = line_no
|
||||
res.name = fields[0]
|
||||
res.type = res.parse_type(fields[1])
|
||||
res.subtype = res.parse_subtype(fields[2])
|
||||
res.offset = res.parse_address(fields[3])
|
||||
res.size = res.parse_address(fields[4])
|
||||
if res.size is None:
|
||||
raise InputError("Size field can't be empty")
|
||||
|
||||
flags = fields[5].split(':')
|
||||
for flag in flags:
|
||||
if flag in cls.FLAGS:
|
||||
setattr(res, flag, True)
|
||||
elif len(flag) > 0:
|
||||
raise InputError("CSV flag column contains unknown flag '%s'" % (flag))
|
||||
|
||||
return res
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name and self.type == other.type \
|
||||
and self.subtype == other.subtype and self.offset == other.offset \
|
||||
and self.size == other.size
|
||||
|
||||
def __repr__(self):
|
||||
def maybe_hex(x):
|
||||
return '0x%x' % x if x is not None else 'None'
|
||||
return "PartitionDefinition('%s', 0x%x, 0x%x, %s, %s)" % (self.name, self.type, self.subtype or 0,
|
||||
maybe_hex(self.offset), maybe_hex(self.size))
|
||||
|
||||
def __str__(self):
|
||||
return "Part '%s' %d/%d @ 0x%x size 0x%x" % (self.name, self.type, self.subtype, self.offset or -1, self.size or -1)
|
||||
|
||||
def __cmp__(self, other):
|
||||
return self.offset - other.offset
|
||||
|
||||
def __lt__(self, other):
|
||||
return self.offset < other.offset
|
||||
|
||||
def __gt__(self, other):
|
||||
return self.offset > other.offset
|
||||
|
||||
def __le__(self, other):
|
||||
return self.offset <= other.offset
|
||||
|
||||
def __ge__(self, other):
|
||||
return self.offset >= other.offset
|
||||
|
||||
def parse_type(self, strval):
|
||||
if strval == '':
|
||||
raise InputError("Field 'type' can't be left empty.")
|
||||
return parse_int(strval, TYPES)
|
||||
|
||||
def parse_subtype(self, strval):
|
||||
if strval == '':
|
||||
if self.type == TYPES['app']:
|
||||
raise InputError('App partition cannot have an empty subtype')
|
||||
return SUBTYPES[DATA_TYPE]['undefined']
|
||||
return parse_int(strval, SUBTYPES.get(self.type, {}))
|
||||
|
||||
def parse_address(self, strval):
|
||||
if strval == '':
|
||||
return None # PartitionTable will fill in default
|
||||
return parse_int(strval)
|
||||
|
||||
def verify(self):
|
||||
if self.type is None:
|
||||
raise ValidationError(self, 'Type field is not set')
|
||||
if self.subtype is None:
|
||||
raise ValidationError(self, 'Subtype field is not set')
|
||||
if self.offset is None:
|
||||
raise ValidationError(self, 'Offset field is not set')
|
||||
align = get_alignment_for_type(self.type)
|
||||
if self.offset % align:
|
||||
raise ValidationError(self, 'Offset 0x%x is not aligned to 0x%x' % (self.offset, align))
|
||||
# The alignment requirement for non-app partition is 4 bytes, but it should be 4 kB.
|
||||
# Print a warning for now, make it an error in IDF 5.0 (IDF-3742).
|
||||
if self.type != APP_TYPE and self.offset % STRICT_DATA_ALIGNMENT:
|
||||
critical('WARNING: Partition %s not aligned to 0x%x.'
|
||||
'This is deprecated and will be considered an error in the future release.' % (self.name, STRICT_DATA_ALIGNMENT))
|
||||
if self.size % align and secure and self.type == APP_TYPE:
|
||||
raise ValidationError(self, 'Size 0x%x is not aligned to 0x%x' % (self.size, align))
|
||||
if self.size is None:
|
||||
raise ValidationError(self, 'Size field is not set')
|
||||
|
||||
if self.name in TYPES and TYPES.get(self.name, '') != self.type:
|
||||
critical("WARNING: Partition has name '%s' which is a partition type, but does not match this partition's "
|
||||
'type (0x%x). Mistake in partition table?' % (self.name, self.type))
|
||||
all_subtype_names = []
|
||||
for names in (t.keys() for t in SUBTYPES.values()):
|
||||
all_subtype_names += names
|
||||
if self.name in all_subtype_names and SUBTYPES.get(self.type, {}).get(self.name, '') != self.subtype:
|
||||
critical("WARNING: Partition has name '%s' which is a partition subtype, but this partition has "
|
||||
'non-matching type 0x%x and subtype 0x%x. Mistake in partition table?' % (self.name, self.type, self.subtype))
|
||||
|
||||
STRUCT_FORMAT = b'<2sBBLL16sL'
|
||||
|
||||
@classmethod
|
||||
def from_binary(cls, b):
|
||||
if len(b) != 32:
|
||||
raise InputError('Partition definition length must be exactly 32 bytes. Got %d bytes.' % len(b))
|
||||
res = cls()
|
||||
(magic, res.type, res.subtype, res.offset,
|
||||
res.size, res.name, flags) = struct.unpack(cls.STRUCT_FORMAT, b)
|
||||
if b'\x00' in res.name: # strip null byte padding from name string
|
||||
res.name = res.name[:res.name.index(b'\x00')]
|
||||
res.name = res.name.decode()
|
||||
if magic != cls.MAGIC_BYTES:
|
||||
raise InputError('Invalid magic bytes (%r) for partition definition' % magic)
|
||||
for flag,bit in cls.FLAGS.items():
|
||||
if flags & (1 << bit):
|
||||
setattr(res, flag, True)
|
||||
flags &= ~(1 << bit)
|
||||
if flags != 0:
|
||||
critical('WARNING: Partition definition had unknown flag(s) 0x%08x. Newer binary format?' % flags)
|
||||
return res
|
||||
|
||||
def get_flags_list(self):
|
||||
return [flag for flag in self.FLAGS.keys() if getattr(self, flag)]
|
||||
|
||||
def to_binary(self):
|
||||
flags = sum((1 << self.FLAGS[flag]) for flag in self.get_flags_list())
|
||||
return struct.pack(self.STRUCT_FORMAT,
|
||||
self.MAGIC_BYTES,
|
||||
self.type, self.subtype,
|
||||
self.offset, self.size,
|
||||
self.name.encode(),
|
||||
flags)
|
||||
|
||||
def to_csv(self, simple_formatting=False):
|
||||
def addr_format(a, include_sizes):
|
||||
if not simple_formatting and include_sizes:
|
||||
for (val, suffix) in [(0x100000, 'M'), (0x400, 'K')]:
|
||||
if a % val == 0:
|
||||
return '%d%s' % (a // val, suffix)
|
||||
return '0x%x' % a
|
||||
|
||||
def lookup_keyword(t, keywords):
|
||||
for k,v in keywords.items():
|
||||
if simple_formatting is False and t == v:
|
||||
return k
|
||||
return '%d' % t
|
||||
|
||||
def generate_text_flags():
|
||||
""" colon-delimited list of flags """
|
||||
return ':'.join(self.get_flags_list())
|
||||
|
||||
return ','.join([self.name,
|
||||
lookup_keyword(self.type, TYPES),
|
||||
lookup_keyword(self.subtype, SUBTYPES.get(self.type, {})),
|
||||
addr_format(self.offset, False),
|
||||
addr_format(self.size, True),
|
||||
generate_text_flags()])
|
||||
|
||||
|
||||
def parse_int(v, keywords={}):
|
||||
"""Generic parser for integer fields - int(x,0) with provision for
|
||||
k/m/K/M suffixes and 'keyword' value lookup.
|
||||
"""
|
||||
try:
|
||||
for letter, multiplier in [('k', 1024), ('m', 1024 * 1024)]:
|
||||
if v.lower().endswith(letter):
|
||||
return parse_int(v[:-1], keywords) * multiplier
|
||||
return int(v, 0)
|
||||
except ValueError:
|
||||
if len(keywords) == 0:
|
||||
raise InputError('Invalid field value %s' % v)
|
||||
try:
|
||||
return keywords[v.lower()]
|
||||
except KeyError:
|
||||
raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ', '.join(keywords)))
|
||||
|
||||
|
||||
def main():
|
||||
global quiet
|
||||
global md5sum
|
||||
global offset_part_table
|
||||
global secure
|
||||
parser = argparse.ArgumentParser(description='ESP32 partition table utility')
|
||||
|
||||
parser.add_argument('--flash-size', help='Optional flash size limit, checks partition table fits in flash',
|
||||
nargs='?', choices=['1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB'])
|
||||
parser.add_argument('--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true')
|
||||
parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true')
|
||||
parser.add_argument('--verify', '-v', help='Verify partition table fields (deprecated, this behaviour is '
|
||||
'enabled by default and this flag does nothing.', action='store_true')
|
||||
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
|
||||
parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000')
|
||||
parser.add_argument('--secure', help='Require app partitions to be suitable for secure boot', action='store_true')
|
||||
parser.add_argument('input', help='Path to CSV or binary file to parse.', type=argparse.FileType('rb'))
|
||||
parser.add_argument('output', help='Path to output converted binary or CSV file. Will use stdout if omitted.',
|
||||
nargs='?', default='-')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
quiet = args.quiet
|
||||
md5sum = not args.disable_md5sum
|
||||
secure = args.secure
|
||||
offset_part_table = int(args.offset, 0)
|
||||
table, input_is_binary = PartitionTable.from_file(args.input)
|
||||
|
||||
if not args.no_verify:
|
||||
status('Verifying table...')
|
||||
table.verify()
|
||||
|
||||
if args.flash_size:
|
||||
size_mb = int(args.flash_size.replace('MB', ''))
|
||||
table.verify_size_fits(size_mb * 1024 * 1024)
|
||||
|
||||
# Make sure that the output directory is created
|
||||
output_dir = os.path.abspath(os.path.dirname(args.output))
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
try:
|
||||
os.makedirs(output_dir)
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.EEXIST:
|
||||
raise
|
||||
|
||||
if input_is_binary:
|
||||
output = table.to_csv()
|
||||
with sys.stdout if args.output == '-' else open(args.output, 'w') as f:
|
||||
f.write(output)
|
||||
else:
|
||||
output = table.to_binary()
|
||||
try:
|
||||
stdout_binary = sys.stdout.buffer # Python 3
|
||||
except AttributeError:
|
||||
stdout_binary = sys.stdout
|
||||
with stdout_binary if args.output == '-' else open(args.output, 'wb') as f:
|
||||
f.write(output)
|
||||
|
||||
|
||||
class InputError(RuntimeError):
|
||||
def __init__(self, e):
|
||||
super(InputError, self).__init__(e)
|
||||
|
||||
|
||||
class ValidationError(InputError):
|
||||
def __init__(self, partition, message):
|
||||
super(ValidationError, self).__init__(
|
||||
'Partition %s invalid: %s' % (partition.name, message))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except InputError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(2)
|
|
@ -13,7 +13,7 @@ import subprocess
|
|||
#spiffs, data, spiffs, 0x290000,0x170000,
|
||||
|
||||
MKSPIFFS = os.environ['MKSPIFFS']
|
||||
print "mkspiffs is "+MKSPIFFS
|
||||
print("mkspiffs is "+MKSPIFFS)
|
||||
|
||||
OFFSET_BOOTLOADER = 0x1000
|
||||
OFFSET_PARTITIONS = 0x8000
|
||||
|
@ -33,7 +33,7 @@ partition = esp32tools + "/partitions/default.csv"
|
|||
if os.path.isfile("RX_FSK/partitions.csv"):
|
||||
partition = "RX_FSK/partitions.csv"
|
||||
|
||||
with open(partition, 'rb') as csvfile:
|
||||
with open(partition, 'rt') as csvfile:
|
||||
partreader = csv.reader(csvfile, delimiter=',')
|
||||
for row in partreader:
|
||||
if row[0] == "otadata":
|
||||
|
@ -44,9 +44,9 @@ with open(partition, 'rb') as csvfile:
|
|||
OFFSET_SPIFFS = int(row[3],16)
|
||||
SIZE_SPIFFS = int(row[4],16)
|
||||
|
||||
print "bootapp0: "+hex(OFFSET_BOOTAPP0)
|
||||
print "app0: "+hex(OFFSET_APPLICATION)
|
||||
print "spiffs: "+hex(OFFSET_SPIFFS)+" size "+hex(SIZE_SPIFFS)
|
||||
print("bootapp0: "+hex(OFFSET_BOOTAPP0))
|
||||
print("app0: "+hex(OFFSET_APPLICATION))
|
||||
print("spiffs: "+hex(OFFSET_SPIFFS)+" size "+hex(SIZE_SPIFFS))
|
||||
|
||||
# create binary partition
|
||||
file_part = "/tmp/partition.bin"
|
||||
|
|
|
@ -3,6 +3,7 @@ import requests
|
|||
import sys
|
||||
import os
|
||||
import socket
|
||||
import tempfile
|
||||
import esptool
|
||||
|
||||
ttgohost = "rdzsonde.local"
|
||||
|
@ -56,6 +57,7 @@ if len(sys.argv)<=2:
|
|||
print("or: ",sys.argv[0]," <get|put> file {filename}");
|
||||
print("or: ",sys.argv[0]," update <devel-xxx|master-yyy>");
|
||||
print("or: ",sys.argv[0]," <backup|restore> file.bin");
|
||||
print("or: ",sys.argv[0]," uploadfs directory");
|
||||
print("\n",
|
||||
" screens is screens1.txt, screens2.txt, screens3.txt");
|
||||
print(" networks is networks.txt (Wifi ssid and password)")
|
||||
|
@ -94,6 +96,52 @@ if sys.argv[1]=="update":
|
|||
esptool.main()
|
||||
exit(0)
|
||||
|
||||
|
||||
def getpartinfo(partname):
|
||||
import gen_esp32part as pt
|
||||
# flash complete file system
|
||||
# automatically get file system parameters from ESP (i.e. you need to program the partition table first)
|
||||
if False:
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
partbin = os.path.join(tmpdir, "partition.bin")
|
||||
sys._argv = sys.argv[:]
|
||||
sys.argv=[sys._argv[0], "--chip", "esp32", "--baud", "921600", "--before", "default_reset",
|
||||
"--after", "no_reset", "read_flash", "0x8000", "0x1000", partbin]
|
||||
esptool.main()
|
||||
else:
|
||||
# test only
|
||||
partbin="partitions-esp32v2.csv"
|
||||
with open(partbin,"rb") as f:
|
||||
table, input_is_binary = pt.PartitionTable.from_file(f)
|
||||
print("Partition table:")
|
||||
tab = table.to_csv()
|
||||
print(tab)
|
||||
OFFSET = -1
|
||||
SIZE = -1
|
||||
for line in tab.split("\n"):
|
||||
if line.startswith(partname):
|
||||
l = line.split(",")
|
||||
OFFSET = int(l[3],0)
|
||||
SIZE = l[4]
|
||||
mult = 1
|
||||
if SIZE[-1].upper() == 'K':
|
||||
SIZE = SIZE[:-1]
|
||||
mult = 1024
|
||||
print("SIZE is:"+SIZE+"!")
|
||||
SIZE = int(SIZE,0) * mult
|
||||
|
||||
print("File system at ",hex(OFFSET)," size=",hex(SIZE))
|
||||
return [OFFSET, SIZE]
|
||||
|
||||
#OFFSET="0x3F0000"
|
||||
#SIZE="0x10000"
|
||||
|
||||
if sys.argv[1]=="uploadfs":
|
||||
(offset, size) = getpartinfo("spiffs")
|
||||
print("Using offset ",offset,"; size is ",size)
|
||||
exit(0)
|
||||
|
||||
|
||||
addrinfo = socket.gethostbyname(ttgohost)
|
||||
url = "http://"+addrinfo+"/"
|
||||
print("Using URL ",url)
|
||||
|
|
Ładowanie…
Reference in New Issue