mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-19 17:22:59 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9075501917 | ||
|
|
a3b70e7538 | ||
|
|
b4b8abe6ec | ||
|
|
d647be73df | ||
|
|
42d7966858 | ||
|
|
f4d368e1f4 | ||
|
|
3a756b0e08 | ||
|
|
34ead2d68e | ||
|
|
3f1161b68b | ||
|
|
f108e24bc1 | ||
|
|
1f83f7d9df | ||
|
|
756c7ca7b3 | ||
|
|
79f7bf77c9 | ||
|
|
dee3e530de | ||
|
|
3e44c2c3e1 | ||
|
|
3886665041 | ||
|
|
c9b269c3c0 | ||
|
|
eb51c92d08 | ||
|
|
992b525588 | ||
|
|
25288d8ed6 | ||
|
|
32ac5ac9ae | ||
|
|
ef5cdefca6 | ||
|
|
f6f9dfa463 | ||
|
|
2161ce21df | ||
|
|
534691f0c2 | ||
|
|
6bc8e1b10a | ||
|
|
c8b95f7691 | ||
|
|
daf8594b99 | ||
|
|
5b54fd6359 | ||
|
|
53765298e1 | ||
|
|
0d94458c4e | ||
|
|
5e55695862 | ||
|
|
c9e2e6c386 | ||
|
|
dbbb62f63e | ||
|
|
79ce7d929c | ||
|
|
33437b5246 | ||
|
|
f4bacb9d87 | ||
|
|
0ac218b06d | ||
|
|
2ce1b4bb2c | ||
|
|
f002bee4f4 | ||
|
|
9c69326ed9 | ||
|
|
7a617d5378 | ||
|
|
90edae1ce0 | ||
|
|
2134b4db9b | ||
|
|
7b1ffb5c09 | ||
|
|
535f07d927 | ||
|
|
bfad136137 | ||
|
|
36bee8fa53 | ||
|
|
99cb0b3855 | ||
|
|
41c95eaff7 | ||
|
|
ca8a25f585 | ||
|
|
51a8700391 | ||
|
|
f099a31a29 | ||
|
|
52dbc4e15d | ||
|
|
8cabb3ea3d | ||
|
|
7a4a1af332 | ||
|
|
90ecdf229e | ||
|
|
24ac907780 | ||
|
|
5037fb830e | ||
|
|
79f1346359 | ||
|
|
35dada683a | ||
|
|
30a431788d | ||
|
|
d0b8adab75 | ||
|
|
74f7b7b622 | ||
|
|
9ec8562ce7 | ||
|
|
0cbcb7a9bd | ||
|
|
2874b22d6c | ||
|
|
3c9be48445 | ||
|
|
b5201f928b | ||
|
|
2591859df5 | ||
|
|
c411db111b |
6
.clang-format
Normal file
6
.clang-format
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Language: Cpp
|
||||||
|
IndentWidth: 4
|
||||||
|
ColumnLimit: 130
|
||||||
|
PointerAlignment: Right
|
||||||
|
BreakBeforeBraces: Linux
|
||||||
|
AllowShortFunctionsOnASingleLine: Inline
|
||||||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -1,5 +1,7 @@
|
|||||||
name: Continuous Integration
|
name: Continuous Integration
|
||||||
on: push
|
on:
|
||||||
|
- push
|
||||||
|
- pull_request
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
|
|||||||
64
README.md
64
README.md
@@ -51,31 +51,77 @@ Warning: ESP32 has no Chip ID. Reading MAC instead.
|
|||||||
MAC: 24:6f:28:b5:36:71
|
MAC: 24:6f:28:b5:36:71
|
||||||
Hard resetting via RTS pin...
|
Hard resetting via RTS pin...
|
||||||
```
|
```
|
||||||
6. Install the correct firmware for your board with "esptool.py write_flash 0x10000 firmware-_board_-_country_.bin". For instance "esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin". You should see something like this:
|
6. cd into the directory where the release zip file was expanded.
|
||||||
|
7. Install the correct firmware for your board with "device-install.sh firmware-_board_-_country_.bin". For instance "./device-install.sh firmware-HELTEC-US-0.0.3.bin".
|
||||||
|
|
||||||
|
Note: If you have previously installed meshtastic, you don't need to run this full script instead just run "esptool.py --baud 921600 write_flash 0x10000 firmware-_board_-_country_.bin". This will be faster, also all of your current preferences will be preserved.
|
||||||
|
|
||||||
|
You should see something like this:
|
||||||
```
|
```
|
||||||
~/development/meshtastic/meshtastic-esp32$ esptool.py write_flash 0x10000 release/firmware-HELTEC-US-0.0.3.bin
|
kevinh@kevin-server:~/development/meshtastic/meshtastic-esp32/release/latest$ ./device-install.sh firmware-TBEAM-US-0.1.8.bin
|
||||||
|
Trying to flash firmware-TBEAM-US-0.1.8.bin, but first erasing and writing system information
|
||||||
esptool.py v2.6
|
esptool.py v2.6
|
||||||
Found 2 serial ports
|
Found 2 serial ports
|
||||||
Serial port /dev/ttyUSB0
|
Serial port /dev/ttyUSB0
|
||||||
Connecting......
|
Connecting........____
|
||||||
Detecting chip type... ESP32
|
Detecting chip type... ESP32
|
||||||
Chip is ESP32D0WDQ6 (revision 1)
|
Chip is ESP32D0WDQ6 (revision 1)
|
||||||
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
||||||
MAC: 24:6f:28:b5:36:71
|
MAC: 24:6f:28:b2:01:6c
|
||||||
Uploading stub...
|
Uploading stub...
|
||||||
Running stub...
|
Running stub...
|
||||||
Stub running...
|
Stub running...
|
||||||
|
Changing baud rate to 921600
|
||||||
|
Changed.
|
||||||
|
Erasing flash (this may take a while)...
|
||||||
|
Chip erase completed successfully in 6.1s
|
||||||
|
Hard resetting via RTS pin...
|
||||||
|
esptool.py v2.6
|
||||||
|
Found 2 serial ports
|
||||||
|
Serial port /dev/ttyUSB0
|
||||||
|
Connecting.......
|
||||||
|
Detecting chip type... ESP32
|
||||||
|
Chip is ESP32D0WDQ6 (revision 1)
|
||||||
|
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
||||||
|
MAC: 24:6f:28:b2:01:6c
|
||||||
|
Uploading stub...
|
||||||
|
Running stub...
|
||||||
|
Stub running...
|
||||||
|
Changing baud rate to 921600
|
||||||
|
Changed.
|
||||||
Configuring flash size...
|
Configuring flash size...
|
||||||
Auto-detected Flash size: 8MB
|
Auto-detected Flash size: 4MB
|
||||||
Compressed 1184800 bytes to 652635...
|
Flash params set to 0x0220
|
||||||
Wrote 1184800 bytes (652635 compressed) at 0x00010000 in 57.6 seconds (effective 164.5 kbit/s)...
|
Compressed 61440 bytes to 11950...
|
||||||
|
Wrote 61440 bytes (11950 compressed) at 0x00001000 in 0.2 seconds (effective 3092.4 kbit/s)...
|
||||||
|
Hash of data verified.
|
||||||
|
|
||||||
|
Leaving...
|
||||||
|
Hard resetting via RTS pin...
|
||||||
|
esptool.py v2.6
|
||||||
|
Found 2 serial ports
|
||||||
|
Serial port /dev/ttyUSB0
|
||||||
|
Connecting.....
|
||||||
|
Detecting chip type... ESP32
|
||||||
|
Chip is ESP32D0WDQ6 (revision 1)
|
||||||
|
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
|
||||||
|
MAC: 24:6f:28:b2:01:6c
|
||||||
|
Uploading stub...
|
||||||
|
Running stub...
|
||||||
|
Stub running...
|
||||||
|
Changing baud rate to 921600
|
||||||
|
Changed.
|
||||||
|
Configuring flash size...
|
||||||
|
Auto-detected Flash size: 4MB
|
||||||
|
Compressed 1223568 bytes to 678412...
|
||||||
|
Wrote 1223568 bytes (678412 compressed) at 0x00010000 in 10.7 seconds (effective 912.0 kbit/s)...
|
||||||
Hash of data verified.
|
Hash of data verified.
|
||||||
|
|
||||||
Leaving...
|
Leaving...
|
||||||
Hard resetting via RTS pin...
|
Hard resetting via RTS pin...
|
||||||
```
|
```
|
||||||
7. The board will boot and show the Meshtastic logo.
|
8. The board will boot and show the Meshtastic logo.
|
||||||
8. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
|
9. Please post a comment on our chat so we know if these instructions worked for you ;-). If you find bugs/have-questions post there also - we will be rapidly iterating over the next few weeks.
|
||||||
|
|
||||||
## Meshtastic Android app
|
## Meshtastic Android app
|
||||||
The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android).
|
The source code for the (optional) Meshtastic Android app is [here](https://github.com/meshtastic/Meshtastic-Android).
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ set -e
|
|||||||
source bin/version.sh
|
source bin/version.sh
|
||||||
|
|
||||||
COUNTRIES="US EU433 EU865 CN JP"
|
COUNTRIES="US EU433 EU865 CN JP"
|
||||||
# COUNTRIES=US
|
#COUNTRIES=US
|
||||||
|
|
||||||
SRCMAP=.pio/build/esp32/output.map
|
SRCMAP=.pio/build/esp32/output.map
|
||||||
SRCBIN=.pio/build/esp32/firmware.bin
|
SRCBIN=.pio/build/esp32/firmware.bin
|
||||||
|
SRCELF=.pio/build/esp32/firmware.elf
|
||||||
OUTDIR=release/latest
|
OUTDIR=release/latest
|
||||||
|
|
||||||
# We keep all old builds (and their map files in the archive dir)
|
# We keep all old builds (and their map files in the archive dir)
|
||||||
@@ -26,12 +27,14 @@ for COUNTRY in $COUNTRIES; do
|
|||||||
rm -f $SRCBIN $SRCMAP
|
rm -f $SRCBIN $SRCMAP
|
||||||
pio run # -v
|
pio run # -v
|
||||||
cp $SRCBIN $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin
|
cp $SRCBIN $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.bin
|
||||||
|
cp $SRCELF $OUTDIR/firmware-TBEAM-$COUNTRY-$VERSION.elf
|
||||||
#cp $SRCMAP $ARCHIVEDIR/firmware-TBEAM-$COUNTRY-$VERSION.map
|
#cp $SRCMAP $ARCHIVEDIR/firmware-TBEAM-$COUNTRY-$VERSION.map
|
||||||
|
|
||||||
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
|
export PLATFORMIO_BUILD_FLAGS="-DHELTEC_LORA32 $COMMONOPTS"
|
||||||
rm -f $SRCBIN $SRCMAP
|
rm -f $SRCBIN $SRCMAP
|
||||||
pio run # -v
|
pio run # -v
|
||||||
cp $SRCBIN $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin
|
cp $SRCBIN $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.bin
|
||||||
|
cp $SRCELF $OUTDIR/firmware-HELTEC-$COUNTRY-$VERSION.elf
|
||||||
#cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map
|
#cp $SRCMAP $ARCHIVEDIR/firmware-HELTEC-$COUNTRY-$VERSION.map
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -51,6 +54,6 @@ Generated by bin/buildall.sh -->
|
|||||||
XML
|
XML
|
||||||
|
|
||||||
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
rm -f $ARCHIVEDIR/firmware-$VERSION.zip
|
||||||
zip $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.bin
|
zip --junk-paths $ARCHIVEDIR/firmware-$VERSION.zip $OUTDIR/firmware-*-$VERSION.* images/system-info.bin bin/device-install.sh
|
||||||
|
|
||||||
echo BUILT ALL
|
echo BUILT ALL
|
||||||
11
bin/device-install.sh
Executable file
11
bin/device-install.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
FILENAME=$1
|
||||||
|
|
||||||
|
echo "Trying to flash $FILENAME, but first erasing and writing system information"
|
||||||
|
esptool.py --baud 921600 erase_flash
|
||||||
|
esptool.py --baud 921600 write_flash 0x1000 system-info.bin
|
||||||
|
esptool.py --baud 921600 write_flash 0x10000 $FILENAME
|
||||||
|
|
||||||
329
bin/exception_decoder.py
Executable file
329
bin/exception_decoder.py
Executable file
@@ -0,0 +1,329 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""ESP Exception Decoder
|
||||||
|
|
||||||
|
github: https://github.com/janLo/EspArduinoExceptionDecoder
|
||||||
|
license: GPL v3
|
||||||
|
author: Jan Losinski
|
||||||
|
|
||||||
|
Meshtastic notes:
|
||||||
|
* original version is at: https://github.com/janLo/EspArduinoExceptionDecoder
|
||||||
|
* version that's checked into meshtastic repo is based on: https://github.com/me21/EspArduinoExceptionDecoder
|
||||||
|
which adds in ESP32 Backtrace decoding.
|
||||||
|
* this also updates the defaults to use ESP32, instead of ESP8266 and defaults to the built firmware.bin
|
||||||
|
|
||||||
|
To use, copy the "Backtrace: 0x...." line to a file, e.g., backtrace.txt, then run:
|
||||||
|
$ bin/exception_decoder.py backtrace.txt
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
EXCEPTIONS = [
|
||||||
|
"Illegal instruction",
|
||||||
|
"SYSCALL instruction",
|
||||||
|
"InstructionFetchError: Processor internal physical address or data error during instruction fetch",
|
||||||
|
"LoadStoreError: Processor internal physical address or data error during load or store",
|
||||||
|
"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register",
|
||||||
|
"Alloca: MOVSP instruction, if caller's registers are not in the register file",
|
||||||
|
"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero",
|
||||||
|
"reserved",
|
||||||
|
"Privileged: Attempt to execute a privileged operation when CRING ? 0",
|
||||||
|
"LoadStoreAlignmentCause: Load or store to an unaligned address",
|
||||||
|
"reserved",
|
||||||
|
"reserved",
|
||||||
|
"InstrPIFDataError: PIF data error during instruction fetch",
|
||||||
|
"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access",
|
||||||
|
"InstrPIFAddrError: PIF address error during instruction fetch",
|
||||||
|
"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access",
|
||||||
|
"InstTLBMiss: Error during Instruction TLB refill",
|
||||||
|
"InstTLBMultiHit: Multiple instruction TLB entries matched",
|
||||||
|
"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING",
|
||||||
|
"reserved",
|
||||||
|
"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch",
|
||||||
|
"reserved",
|
||||||
|
"reserved",
|
||||||
|
"reserved",
|
||||||
|
"LoadStoreTLBMiss: Error during TLB refill for a load or store",
|
||||||
|
"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store",
|
||||||
|
"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING",
|
||||||
|
"reserved",
|
||||||
|
"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads",
|
||||||
|
"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores"
|
||||||
|
]
|
||||||
|
|
||||||
|
PLATFORMS = {
|
||||||
|
"ESP8266": "lx106",
|
||||||
|
"ESP32": "esp32"
|
||||||
|
}
|
||||||
|
|
||||||
|
BACKTRACE_REGEX = re.compile(r"(?:\s+(0x40[0-2](?:\d|[a-f]|[A-F]){5}):0x(?:\d|[a-f]|[A-F]){8})\b")
|
||||||
|
EXCEPTION_REGEX = re.compile("^Exception \\((?P<exc>[0-9]*)\\):$")
|
||||||
|
COUNTER_REGEX = re.compile('^epc1=(?P<epc1>0x[0-9a-f]+) epc2=(?P<epc2>0x[0-9a-f]+) epc3=(?P<epc3>0x[0-9a-f]+) '
|
||||||
|
'excvaddr=(?P<excvaddr>0x[0-9a-f]+) depc=(?P<depc>0x[0-9a-f]+)$')
|
||||||
|
CTX_REGEX = re.compile("^ctx: (?P<ctx>.+)$")
|
||||||
|
POINTER_REGEX = re.compile('^sp: (?P<sp>[0-9a-f]+) end: (?P<end>[0-9a-f]+) offset: (?P<offset>[0-9a-f]+)$')
|
||||||
|
STACK_BEGIN = '>>>stack>>>'
|
||||||
|
STACK_END = '<<<stack<<<'
|
||||||
|
STACK_REGEX = re.compile(
|
||||||
|
'^(?P<off>[0-9a-f]+):\W+(?P<c1>[0-9a-f]+) (?P<c2>[0-9a-f]+) (?P<c3>[0-9a-f]+) (?P<c4>[0-9a-f]+)(\W.*)?$')
|
||||||
|
|
||||||
|
StackLine = namedtuple("StackLine", ["offset", "content"])
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionDataParser(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.exception = None
|
||||||
|
|
||||||
|
self.epc1 = None
|
||||||
|
self.epc2 = None
|
||||||
|
self.epc3 = None
|
||||||
|
self.excvaddr = None
|
||||||
|
self.depc = None
|
||||||
|
|
||||||
|
self.ctx = None
|
||||||
|
|
||||||
|
self.sp = None
|
||||||
|
self.end = None
|
||||||
|
self.offset = None
|
||||||
|
|
||||||
|
self.stack = []
|
||||||
|
|
||||||
|
def _parse_backtrace(self, line):
|
||||||
|
if line.startswith('Backtrace:'):
|
||||||
|
self.stack = [StackLine(offset=0, content=(addr,)) for addr in BACKTRACE_REGEX.findall(line)]
|
||||||
|
return None
|
||||||
|
return self._parse_backtrace
|
||||||
|
|
||||||
|
def _parse_exception(self, line):
|
||||||
|
match = EXCEPTION_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.exception = int(match.group('exc'))
|
||||||
|
return self._parse_counters
|
||||||
|
return self._parse_exception
|
||||||
|
|
||||||
|
def _parse_counters(self, line):
|
||||||
|
match = COUNTER_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.epc1 = match.group("epc1")
|
||||||
|
self.epc2 = match.group("epc2")
|
||||||
|
self.epc3 = match.group("epc3")
|
||||||
|
self.excvaddr = match.group("excvaddr")
|
||||||
|
self.depc = match.group("depc")
|
||||||
|
return self._parse_ctx
|
||||||
|
return self._parse_counters
|
||||||
|
|
||||||
|
def _parse_ctx(self, line):
|
||||||
|
match = CTX_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.ctx = match.group("ctx")
|
||||||
|
return self._parse_pointers
|
||||||
|
return self._parse_ctx
|
||||||
|
|
||||||
|
def _parse_pointers(self, line):
|
||||||
|
match = POINTER_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.sp = match.group("sp")
|
||||||
|
self.end = match.group("end")
|
||||||
|
self.offset = match.group("offset")
|
||||||
|
return self._parse_stack_begin
|
||||||
|
return self._parse_pointers
|
||||||
|
|
||||||
|
def _parse_stack_begin(self, line):
|
||||||
|
if line == STACK_BEGIN:
|
||||||
|
return self._parse_stack_line
|
||||||
|
return self._parse_stack_begin
|
||||||
|
|
||||||
|
def _parse_stack_line(self, line):
|
||||||
|
if line != STACK_END:
|
||||||
|
match = STACK_REGEX.match(line)
|
||||||
|
if match is not None:
|
||||||
|
self.stack.append(StackLine(offset=match.group("off"),
|
||||||
|
content=(match.group("c1"), match.group("c2"), match.group("c3"),
|
||||||
|
match.group("c4"))))
|
||||||
|
return self._parse_stack_line
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_file(self, file, platform, stack_only=False):
|
||||||
|
if platform == 'ESP32':
|
||||||
|
func = self._parse_backtrace
|
||||||
|
else:
|
||||||
|
func = self._parse_exception
|
||||||
|
if stack_only:
|
||||||
|
func = self._parse_stack_begin
|
||||||
|
|
||||||
|
for line in file:
|
||||||
|
func = func(line.strip())
|
||||||
|
if func is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if func is not None:
|
||||||
|
print("ERROR: Parser not complete!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
class AddressResolver(object):
|
||||||
|
def __init__(self, tool_path, elf_path):
|
||||||
|
self._tool = tool_path
|
||||||
|
self._elf = elf_path
|
||||||
|
self._address_map = {}
|
||||||
|
|
||||||
|
def _lookup(self, addresses):
|
||||||
|
cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None]
|
||||||
|
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
output = subprocess.check_output(cmd)
|
||||||
|
else:
|
||||||
|
output = subprocess.check_output(cmd, encoding="utf-8")
|
||||||
|
|
||||||
|
line_regex = re.compile("^(?P<addr>[0-9a-fx]+): (?P<result>.+)$")
|
||||||
|
|
||||||
|
last = None
|
||||||
|
for line in output.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
match = line_regex.match(line)
|
||||||
|
|
||||||
|
if match is None:
|
||||||
|
if last is not None and line.startswith('(inlined by)'):
|
||||||
|
line = line [12:].strip()
|
||||||
|
self._address_map[last] += ("\n \-> inlined by: " + line)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if match.group("result") == '?? ??:0':
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._address_map[match.group("addr")] = match.group("result")
|
||||||
|
last = match.group("addr")
|
||||||
|
|
||||||
|
def fill(self, parser):
|
||||||
|
addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset]
|
||||||
|
for line in parser.stack:
|
||||||
|
addresses.extend(line.content)
|
||||||
|
|
||||||
|
self._lookup(addresses)
|
||||||
|
|
||||||
|
def _sanitize_addr(self, addr):
|
||||||
|
if addr.startswith("0x"):
|
||||||
|
addr = addr[2:]
|
||||||
|
|
||||||
|
fill = "0" * (8 - len(addr))
|
||||||
|
return "0x" + fill + addr
|
||||||
|
|
||||||
|
def resolve_addr(self, addr):
|
||||||
|
out = self._sanitize_addr(addr)
|
||||||
|
|
||||||
|
if out in self._address_map:
|
||||||
|
out += ": " + self._address_map[out]
|
||||||
|
|
||||||
|
return out
|
||||||
|
|
||||||
|
def resolve_stack_addr(self, addr, full=True):
|
||||||
|
addr = self._sanitize_addr(addr)
|
||||||
|
if addr in self._address_map:
|
||||||
|
return addr + ": " + self._address_map[addr]
|
||||||
|
|
||||||
|
if full:
|
||||||
|
return "[DATA (0x" + addr + ")]"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def print_addr(name, value, resolver):
|
||||||
|
print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value)))
|
||||||
|
|
||||||
|
|
||||||
|
def print_stack_full(lines, resolver):
|
||||||
|
print("stack:")
|
||||||
|
for line in lines:
|
||||||
|
print(line.offset + ":")
|
||||||
|
for content in line.content:
|
||||||
|
print(" " + resolver.resolve_stack_addr(content))
|
||||||
|
|
||||||
|
|
||||||
|
def print_stack(lines, resolver):
|
||||||
|
print("stack:")
|
||||||
|
for line in lines:
|
||||||
|
for content in line.content:
|
||||||
|
out = resolver.resolve_stack_addr(content, full=False)
|
||||||
|
if out is None:
|
||||||
|
continue
|
||||||
|
print(out)
|
||||||
|
|
||||||
|
|
||||||
|
def print_result(parser, resolver, platform, full=True, stack_only=False):
|
||||||
|
if platform == 'ESP8266' and not stack_only:
|
||||||
|
print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception]))
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print_addr("epc1", parser.epc1, resolver)
|
||||||
|
print_addr("epc2", parser.epc2, resolver)
|
||||||
|
print_addr("epc3", parser.epc3, resolver)
|
||||||
|
print_addr("excvaddr", parser.excvaddr, resolver)
|
||||||
|
print_addr("depc", parser.depc, resolver)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("ctx: " + parser.ctx)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print_addr("sp", parser.sp, resolver)
|
||||||
|
print_addr("end", parser.end, resolver)
|
||||||
|
print_addr("offset", parser.offset, resolver)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
if full:
|
||||||
|
print_stack_full(parser.stack, resolver)
|
||||||
|
else:
|
||||||
|
print_stack(parser.stack, resolver)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="decode ESP Stacktraces.")
|
||||||
|
|
||||||
|
parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(),
|
||||||
|
default="ESP32")
|
||||||
|
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
|
||||||
|
default="~/.platformio/packages/toolchain-xtensa32/")
|
||||||
|
parser.add_argument("-e", "--elf", help="path to elf file",
|
||||||
|
default=".pio/build/esp32/firmware.elf")
|
||||||
|
parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true")
|
||||||
|
parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true")
|
||||||
|
parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
args = parse_args()
|
||||||
|
|
||||||
|
if args.file == "-":
|
||||||
|
file = sys.stdin
|
||||||
|
else:
|
||||||
|
if not os.path.exists(args.file):
|
||||||
|
print("ERROR: file " + args.file + " not found")
|
||||||
|
sys.exit(1)
|
||||||
|
file = open(args.file, "r")
|
||||||
|
|
||||||
|
addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)),
|
||||||
|
"bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line")
|
||||||
|
if os.name == 'nt':
|
||||||
|
addr2line += '.exe'
|
||||||
|
if not os.path.exists(addr2line):
|
||||||
|
print("ERROR: addr2line not found (" + addr2line + ")")
|
||||||
|
|
||||||
|
elf_file = os.path.abspath(os.path.expanduser(args.elf))
|
||||||
|
if not os.path.exists(elf_file):
|
||||||
|
print("ERROR: elf file not found (" + elf_file + ")")
|
||||||
|
|
||||||
|
parser = ExceptionDataParser()
|
||||||
|
resolver = AddressResolver(addr2line, elf_file)
|
||||||
|
|
||||||
|
parser.parse_file(file, args.platform, args.stack_only)
|
||||||
|
resolver.fill(parser)
|
||||||
|
|
||||||
|
print_result(parser, resolver, args.platform, args.full, args.stack_only)
|
||||||
6
bin/program-release-tbeam.sh
Executable file
6
bin/program-release-tbeam.sh
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source bin/version.sh
|
||||||
|
|
||||||
|
esptool.py --baud 921600 write_flash 0x10000 release/latest/firmware-TBEAM-US-$VERSION.bin
|
||||||
1
bin/read-system-info.sh
Executable file
1
bin/read-system-info.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
esptool.py --baud 921600 read_flash 0x1000 0xf000 system-info.img
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
export VERSION=0.1.6
|
export VERSION=0.1.9
|
||||||
@@ -2,202 +2,202 @@
|
|||||||
|
|
||||||
Items to complete soon (next couple of alpha releases).
|
Items to complete soon (next couple of alpha releases).
|
||||||
|
|
||||||
* lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
|
- lower wait_bluetooth_secs to 30 seconds once we have the GPS power on (but GPS in sleep mode) across light sleep. For the time
|
||||||
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
|
being I have it set at 2 minutes to ensure enough time for a GPS lock from scratch.
|
||||||
|
|
||||||
* use gps sleep mode instead of killing its power (to allow fast position when we wake)
|
- remeasure wake time power draws now that we run CPU down at 80MHz
|
||||||
* enable fast lock and low power inside the gps chip
|
|
||||||
|
|
||||||
* remeasure wake time power draws now that we run CPU down at 80MHz
|
|
||||||
* add a SF12 transmit option for _super_ long range
|
|
||||||
|
|
||||||
# AXP192 tasks
|
# AXP192 tasks
|
||||||
* "AXP192 interrupt is not firing, remove this temporary polling of battery state"
|
|
||||||
* make debug info screen show real data (including battery level & charging) - close corresponding github issue
|
- figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?"
|
||||||
|
- "AXP192 interrupt is not firing, remove this temporary polling of battery state"
|
||||||
|
- make debug info screen show real data (including battery level & charging) - close corresponding github issue
|
||||||
|
|
||||||
# Medium priority
|
# Medium priority
|
||||||
|
|
||||||
Items to complete before the first beta release.
|
Items to complete before the first beta release.
|
||||||
|
|
||||||
* Make a FAQ
|
- good source of battery/signal/gps icons https://materialdesignicons.com/
|
||||||
* good source of battery/signal/gps icons https://materialdesignicons.com/
|
- research and implement better mesh algorithm - investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ?
|
||||||
* investigate changing routing to https://github.com/sudomesh/LoRaLayer2 ?
|
- check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/
|
||||||
* check fcc rules on duty cycle. we might not need to freq hop. https://www.sunfiretesting.com/LoRa-FCC-Certification-Guide/
|
- use fuse bits to store the board type and region. So one load can be used on all boards
|
||||||
* use fuse bits to store the board type and region. So one load can be used on all boards
|
- the BLE stack is leaking about 200 bytes each time we go to light sleep
|
||||||
* research and implement better mesh algorithm
|
- rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
||||||
* the BLE stack is leaking about 200 bytes each time we go to light sleep
|
- assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
||||||
* rx signal measurements -3 marginal, -9 bad, 10 great, -10 means almost unusable. So scale this into % signal strength. preferably as a graph, with an X indicating loss of comms.
|
- Note: we do not do address filtering at the chip level, because we might need to route for the mesh
|
||||||
* assign every "channel" a random shared 8 bit sync word (per 4.2.13.6 of datasheet) - use that word to filter packets before even checking CRC. This will ensure our CPU will only wake for packets on our "channel"
|
- add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example. Possibly do this crypto at the data payload level only, so that all of the packet routing metadata
|
||||||
* Note: we do not do address filtering at the chip level, because we might need to route for the mesh
|
is in cleartext (so that nodes will route for other radios that are cryptoed with a key we don't know)
|
||||||
* add basic crypto - https://github.com/chegewara/esp32-mbedtls-aes-test/blob/master/main/main.c https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation - use ECB at first (though it is shit) because it doesn't require us to send 16 bytes of IV with each packet. Then OFB per example
|
- add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
|
||||||
* add frequency hopping, dependent on the gps time, make the switch moment far from the time anyone is going to be transmitting
|
- share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
|
||||||
* share channel settings over Signal (or qr code) by embedding an an URL which is handled by the MeshUtil app.
|
- publish update articles on the web
|
||||||
* publish update articles on the web
|
|
||||||
|
|
||||||
# Pre-beta priority
|
# Pre-beta priority
|
||||||
|
|
||||||
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
|
During the beta timeframe the following improvements 'would be nice' (and yeah - I guess some of these items count as features, but it is a hobby project ;-) )
|
||||||
|
|
||||||
* figure out why this fixme is needed: "FIXME, disable wake due to PMU because it seems to fire all the time?"
|
- If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
|
||||||
* If the phone doesn't read fromradio mailbox within X seconds, assume the phone is gone and we can stop queing location msgs
|
for it (because it will redownload the nodedb when it comes back)
|
||||||
for it (because it will redownload the nodedb when it comes back)
|
- Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
|
||||||
* Figure out why the RF95 ISR is never seeing RH_RF95_VALID_HEADER, so it is not protecting our rx packets from getting stomped on by sends
|
- fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
|
||||||
* fix the frequency error reading in the RF95 RX code (can't do floating point math in an ISR ;-)
|
- See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
|
||||||
* See CustomRF95::send and fix the problem of dropping partially received packets if we want to start sending
|
- make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
|
||||||
* make sure main cpu is not woken for packets with bad crc or not addressed to this node - do that in the radio hw
|
- triple check fcc compliance
|
||||||
* triple check fcc compliance
|
- pick channel center frequency based on channel name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? allows us to hide the concept of channel # from hte user.
|
||||||
* pick channel center frequency based on channel name? "dolphin" would hash to 900Mhz, "cat" to 905MHz etc? allows us to hide the concept of channel # from hte user.
|
- scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
|
||||||
* scan to find channels with low background noise? (Use CAD mode of the RF95 to automatically find low noise channels)
|
- make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
||||||
* make a no bluetooth configured yet screen - include this screen in the loop if the user hasn't yet paired
|
- if radio params change fundamentally, discard the nodedb
|
||||||
* if radio params change fundamentally, discard the nodedb
|
- reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
|
||||||
* reneable the bluetooth battery level service on the T-BEAM, because we can read battery level there
|
|
||||||
|
|
||||||
# Spinoff project ideas
|
# Spinoff project ideas
|
||||||
|
|
||||||
* an open source version of https://www.burnair.ch/skynet/
|
- an open source version of https://www.burnair.ch/skynet/
|
||||||
* a paragliding app like http://airwhere.co.uk/
|
- a paragliding app like http://airwhere.co.uk/
|
||||||
* a version with a solar cell for power, just mounted high to permanently provide routing for nodes in a valley. Someone just pointed me at disaster.radio
|
- a version with a solar cell for power, just mounted high to permanently provide routing for nodes in a valley. Someone just pointed me at disaster.radio
|
||||||
* How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
|
- How do avalanche beacons work? Could this do that as well? possibly by using beacon mode feature of the RF95?
|
||||||
* provide generalized (but slow) internet message forwarding servie if one of our nodes has internet connectivity
|
- provide generalized (but slow) internet message forwarding servie if one of our nodes has internet connectivity
|
||||||
|
|
||||||
# Low priority
|
# Low priority
|
||||||
|
|
||||||
Items after the first final candidate release.
|
Items after the first final candidate release.
|
||||||
|
|
||||||
* use variable length arduino Strings in protobufs (instead of current fixed buffers)
|
- use variable length arduino Strings in protobufs (instead of current fixed buffers)
|
||||||
* use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
|
- use BLEDevice::setPower to lower our BLE transmit power - extra range doesn't help us, it costs amps and it increases snoopability
|
||||||
* make an install script to let novices install software on their boards
|
- make an install script to let novices install software on their boards
|
||||||
* use std::map<NodeInfo*, std::string> in node db
|
- use std::map<NodeInfo\*, std::string> in node db
|
||||||
* make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
|
- make a HAM build: yep - that's a great idea. I'll add it to the TODO. should be pretty painless - just a new frequency list, a bool to say 'never do encryption' and use hte callsign as that node's unique id. -from Girts
|
||||||
* don't forward redundant pings or ping responses to the phone, it just wastes phone battery
|
- don't forward redundant pings or ping responses to the phone, it just wastes phone battery
|
||||||
* use https://platformio.org/lib/show/1260/OneButton if necessary
|
- use https://platformio.org/lib/show/1260/OneButton if necessary
|
||||||
* don't send location packets if we haven't moved
|
- don't send location packets if we haven't moved
|
||||||
* scrub default radio config settings for bandwidth/range/speed
|
- scrub default radio config settings for bandwidth/range/speed
|
||||||
* answer to pings (because some other user is looking at our nodeinfo) with our latest location (not a stale location)
|
- answer to pings (because some other user is looking at our nodeinfo) with our latest location (not a stale location)
|
||||||
* show radio and gps signal strength as an image
|
- show radio and gps signal strength as an image
|
||||||
* only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
|
- only BLE advertise for a short time after the screen is on and button pressed - to save power and prevent people for sniffing for our BT app.
|
||||||
* make mesh aware network timing state machine (sync wake windows to gps time)
|
- make mesh aware network timing state machine (sync wake windows to gps time)
|
||||||
* split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
- split out the software update utility so other projects can use it. Have the appload specify the URL for downloads.
|
||||||
* read the PMU battery fault indicators and blink/led/warn user on screen
|
- read the PMU battery fault indicators and blink/led/warn user on screen
|
||||||
* the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
|
- the AXP debug output says it is trying to charge at 700mA, but the max I've seen is 180mA, so AXP registers probably need to be set to tell them the circuit can only provide 300mAish max. So that the low charge rate kicks in faster and we don't wear out batteries.
|
||||||
* increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
|
- increase the max charging rate a bit for 18650s, currently it limits to 180mA (at 4V). Work backwards from the 500mA USB limit (at 5V) and let the AXP charge at that rate.
|
||||||
* discard very old nodedb records (> 1wk)
|
- discard very old nodedb records (> 1wk)
|
||||||
* using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
|
- using the genpartitions based table doesn't work on TTGO so for now I stay with my old memory map
|
||||||
* We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
|
- We let anyone BLE scan for us (FIXME, perhaps only allow that until we are paired with a phone and configured)
|
||||||
* use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
|
- use two different buildenv flags for ttgo vs lora32. https://docs.platformio.org/en/latest/ide/vscode.html#key-bindings
|
||||||
* sim gps data for testing nodes that don't have hardware
|
- sim gps data for testing nodes that don't have hardware
|
||||||
* do debug serial logging to android over bluetooth
|
- do debug serial logging to android over bluetooth
|
||||||
* break out my bluetooth OTA software as a seperate library so others can use it
|
- break out my bluetooth OTA software as a seperate library so others can use it
|
||||||
* Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
|
- Heltec LoRa32 has 8MB flash, use a bigger partition table if needed - TTGO is 4MB but has PSRAM
|
||||||
* add a watchdog timer
|
- add a watchdog timer
|
||||||
* handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
- handle millis() rollover in GPS.getTime - otherwise we will break after 50 days
|
||||||
* report esp32 device code bugs back to the mothership via android
|
- report esp32 device code bugs back to the mothership via android
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
|
|
||||||
* change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
- change the partition table to take advantage of the 4MB flash on the wroom: http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables
|
||||||
* wrap in nice MeshRadio class
|
- wrap in nice MeshRadio class
|
||||||
* add mesh send & rx
|
- add mesh send & rx
|
||||||
* make message send from android go to service, then to mesh radio
|
- make message send from android go to service, then to mesh radio
|
||||||
* make message receive from radio go through to android
|
- make message receive from radio go through to android
|
||||||
* test loopback tx/rx path code without using radio
|
- test loopback tx/rx path code without using radio
|
||||||
* notify phone when rx packets arrive, currently the phone polls at startup only
|
- notify phone when rx packets arrive, currently the phone polls at startup only
|
||||||
* figure out if we can use PA_BOOST - yes, it seems to be on both boards
|
- figure out if we can use PA_BOOST - yes, it seems to be on both boards
|
||||||
* implement new ble characteristics
|
- implement new ble characteristics
|
||||||
* have MeshService keep a node DB by sniffing user messages
|
- have MeshService keep a node DB by sniffing user messages
|
||||||
* have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets
|
- have a state machine return the correct FromRadio packet to the phone, it isn't always going to be a MeshPacket. Do a notify on fromnum to force the radio to read our state machine generated packets
|
||||||
* send my_node_num when phone sends WantsNodes
|
- send my_node_num when phone sends WantsNodes
|
||||||
* have meshservice periodically send location data on mesh (if device has a GPS)
|
- have meshservice periodically send location data on mesh (if device has a GPS)
|
||||||
* implement getCurrentTime() - set based off gps but then updated locally
|
- implement getCurrentTime() - set based off gps but then updated locally
|
||||||
* make default owner record have valid usernames
|
- make default owner record have valid usernames
|
||||||
* message loop between node 0x28 and 0x7c
|
- message loop between node 0x28 and 0x7c
|
||||||
* check in my radiolib fixes
|
- check in my radiolib fixes
|
||||||
* figure out what is busted with rx
|
- figure out what is busted with rx
|
||||||
* send our owner info at boot, reply if we see anyone send theirs
|
- send our owner info at boot, reply if we see anyone send theirs
|
||||||
* add manager layers
|
- add manager layers
|
||||||
* confirm second device receives that gps message and updates device db
|
- confirm second device receives that gps message and updates device db
|
||||||
* send correct hw vendor in the bluetooth info - needed so the android app can update different radio models
|
- send correct hw vendor in the bluetooth info - needed so the android app can update different radio models
|
||||||
* correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast
|
- correctly map nodeids to nodenums, currently we just do a proof of concept by always doing a broadcast
|
||||||
* add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing
|
- add interrupt detach/sleep mode config to lora radio so we can enable deepsleep without panicing
|
||||||
* make jtag work on second board
|
- make jtag work on second board
|
||||||
* implement regen owner and radio prefs
|
- implement regen owner and radio prefs
|
||||||
* use a better font
|
- use a better font
|
||||||
* make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network)
|
- make nice screens (boot, about to sleep, debug info (gps signal, #people), latest text, person info - one frame per person on network)
|
||||||
* turn framerate from ui->state.frameState to 1 fps (or less) unless in transition
|
- turn framerate from ui->state.frameState to 1 fps (or less) unless in transition
|
||||||
* switch to my gui layout manager
|
- switch to my gui layout manager
|
||||||
* make basic gui. different screens: debug, one page for each user in the user db, last received text message
|
- make basic gui. different screens: debug, one page for each user in the user db, last received text message
|
||||||
* make button press cycle between screens
|
- make button press cycle between screens
|
||||||
* save our node db on entry to sleep
|
- save our node db on entry to sleep
|
||||||
* fix the logo
|
- fix the logo
|
||||||
* sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
|
- sent/received packets (especially if a node was just reset) have variant of zero sometimes - I think there is a bug (race-condtion?) in the radio send/rx path.
|
||||||
* DONE dynamic nodenum assignment tasks
|
- DONE dynamic nodenum assignment tasks
|
||||||
* make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
|
- make jtag debugger id stable: https://askubuntu.com/questions/49910/how-to-distinguish-between-identical-usb-to-serial-adapters
|
||||||
* reported altitude is crap
|
- reported altitude is crap
|
||||||
* good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119
|
- good tips on which bands might be more free https://github.com/TheThingsNetwork/ttn/issues/119
|
||||||
* finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life
|
- finish power measurements (GPS on during sleep vs LCD on during sleep vs LORA on during sleep) and est battery life
|
||||||
* make screen sleep behavior work
|
- make screen sleep behavior work
|
||||||
* make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while
|
- make screen advance only when a new node update arrives, a new text arrives or the user presses a button, turn off screen after a while
|
||||||
* after reboot, channel number is getting reset to zero! fix!
|
- after reboot, channel number is getting reset to zero! fix!
|
||||||
* send user and location events much less often
|
- send user and location events much less often
|
||||||
* send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience)
|
- send location (or if not available user) when the user wakes the device from display sleep (both for testing and to improve user experience)
|
||||||
* make real implementation of getNumOnlineNodes
|
- make real implementation of getNumOnlineNodes
|
||||||
* very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen)
|
- very occasionally send our position and user packet based on the schedule in the radio info (if for nothing else so that other nodes update last_seen)
|
||||||
* show real text info on the text screen
|
- show real text info on the text screen
|
||||||
* apply radio settings from android land
|
- apply radio settings from android land
|
||||||
* cope with nodes that have 0xff or 0x00 as the last byte of their mac
|
- cope with nodes that have 0xff or 0x00 as the last byte of their mac
|
||||||
* allow setting full radio params from android
|
- allow setting full radio params from android
|
||||||
* add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone
|
- add receive timestamps to messages, inserted by esp32 when message is received but then shown on the phone
|
||||||
* update build to generate both board types
|
- update build to generate both board types
|
||||||
* have node info screen show real info (including distance and heading)
|
- have node info screen show real info (including distance and heading)
|
||||||
* blink the power led less often
|
- blink the power led less often
|
||||||
* have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send
|
- have radiohead ISR send messages to RX queue directly, to allow that thread to block until we have something to send
|
||||||
* move lora rx/tx to own thread and block on IO
|
- move lora rx/tx to own thread and block on IO
|
||||||
* keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc)
|
- keep our pseudo time moving forward even if we enter deep sleep (use esp32 rtc)
|
||||||
* for non GPS equipped devices, set time from phone
|
- for non GPS equipped devices, set time from phone
|
||||||
* GUI on oled hangs for a few seconds occasionally, but comes back
|
- GUI on oled hangs for a few seconds occasionally, but comes back
|
||||||
* update local GPS position (but do not broadcast) at whatever rate the GPS is giving it
|
- update local GPS position (but do not broadcast) at whatever rate the GPS is giving it
|
||||||
* don't send our times to other nodes
|
- don't send our times to other nodes
|
||||||
* don't trust times from other nodes
|
- don't trust times from other nodes
|
||||||
* draw compass rose based off local walking track
|
- draw compass rose based off local walking track
|
||||||
* add requestResponse optional bool - use for location broadcasts when sending tests
|
- add requestResponse optional bool - use for location broadcasts when sending tests
|
||||||
* post sample video to signal forum
|
- post sample video to signal forum
|
||||||
* support non US frequencies
|
- support non US frequencies
|
||||||
* send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project
|
- send pr https://github.com/ThingPulse/esp8266-oled-ssd1306 to tell them about this project
|
||||||
* document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep.
|
- document rules for sleep wrt lora/bluetooth/screen/gps. also: if I have text messages (only) for the phone, then give a few seconds in the hopes BLE can get it across before we have to go back to sleep.
|
||||||
* wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc)
|
- wake from light sleep as needed for our next scheduled periodic task (needed for gps position broadcasts etc)
|
||||||
* turn bluetooth off based on our sleep policy
|
- turn bluetooth off based on our sleep policy
|
||||||
* blink LED while in LS sleep mode
|
- blink LED while in LS sleep mode
|
||||||
* scrolling between screens based on press is busted
|
- scrolling between screens based on press is busted
|
||||||
* Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive)
|
- Use Neo-M8M API to put it in sleep mode (on hold until my new boards arrive)
|
||||||
* update the prebuilt bins for different regulatory regions
|
- update the prebuilt bins for different regulatory regions
|
||||||
* don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
|
- don't enter NB state if we've recently talked to the phone (to prevent breaking syncing or bluetooth sw update)
|
||||||
* have sw update prevent BLE sleep
|
- have sw update prevent BLE sleep
|
||||||
* manually delete characteristics/descs
|
- manually delete characteristics/descs
|
||||||
* leave lora receiver always on
|
- leave lora receiver always on
|
||||||
* protobufs are sometimes corrupted after sleep!
|
- protobufs are sometimes corrupted after sleep!
|
||||||
* stay awake while charging
|
- stay awake while charging
|
||||||
* check gps battery voltage
|
- check gps battery voltage
|
||||||
* if a position report includes ground truth time and we don't have time yet, set our clock from that. It is better than nothing.
|
- if a position report includes ground truth time and we don't have time yet, set our clock from that. It is better than nothing.
|
||||||
* retest BLE software update for both board types
|
- retest BLE software update for both board types
|
||||||
* report on wikifactory
|
- report on wikifactory
|
||||||
* send note to the guy who designed the cases
|
- send note to the guy who designed the cases
|
||||||
* turn light sleep on aggressively (while lora is on but BLE off)
|
- turn light sleep on aggressively (while lora is on but BLE off)
|
||||||
* Use the Periodic class for both position and user periodic broadcasts
|
- Use the Periodic class for both position and user periodic broadcasts
|
||||||
* don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
|
- don't treat north as up, instead adjust shown bearings for our guess at the users heading (i.e. subtract one from the other)
|
||||||
* sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
- sendToMesh can currently block for a long time, instead have it just queue a packet for a radio freertos thread
|
||||||
* don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
|
- don't even power on bluetooth until we have some data to send to the android phone. Most of the time we should be sleeping in a lowpower "listening for lora" only mode. Once we have some packets for the phone, then power on bluetooth
|
||||||
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
|
until the phone pulls those packets. Ever so often power on bluetooth just so we can see if the phone wants to send some packets. Possibly might need ULP processor to help with this wake process.
|
||||||
* do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
|
- do hibernation mode to get power draw down to 2.5uA https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/
|
||||||
* fix GPS.zeroOffset calculation it is wrong
|
- fix GPS.zeroOffset calculation it is wrong
|
||||||
* (needs testing) fixed the following during a plane flight:
|
- (needs testing) fixed the following during a plane flight:
|
||||||
Have state machine properly enter deep sleep based on loss of mesh and phone comms.
|
Have state machine properly enter deep sleep based on loss of mesh and phone comms.
|
||||||
Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh).
|
Default to enter deep sleep if no LORA received for two hours (indicates user has probably left the mesh).
|
||||||
* (fixed I think) text messages are not showing on local screen if screen was on
|
- (fixed I think) text messages are not showing on local screen if screen was on
|
||||||
* add links to todos
|
- add links to todos
|
||||||
* link to the kanban page
|
- link to the kanban page
|
||||||
* add a getting started page
|
- add a getting started page
|
||||||
* finish mesh alg reeval
|
- finish mesh alg reeval
|
||||||
* ublox gps parsing seems a little buggy (we shouldn't be sending out read solution commands, the device is already broadcasting them)
|
- ublox gps parsing seems a little buggy (we shouldn't be sending out read solution commands, the device is already broadcasting them)
|
||||||
* turn on gps https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
|
- turn on gps https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example18_PowerSaveMode/Example18_PowerSaveMode.ino
|
||||||
* switch gps to 38400 baud https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino
|
- switch gps to 38400 baud https://github.com/sparkfun/SparkFun_Ublox_Arduino_Library/blob/master/examples/Example11_ResetModule/Example2_FactoryDefaultsviaSerial/Example2_FactoryDefaultsviaSerial.ino
|
||||||
* Use Neo-M8M API to put it in sleep mode
|
- Use Neo-M8M API to put it in sleep mode
|
||||||
|
- use gps sleep mode instead of killing its power (to allow fast position when we wake)
|
||||||
|
- enable fast lock and low power inside the gps chip
|
||||||
|
- Make a FAQ
|
||||||
|
- add a SF12 transmit option for _super_ long range
|
||||||
|
|||||||
@@ -14,3 +14,12 @@ in these instructions I describe use of their command line tool.
|
|||||||
5. Plug the radio into your USB port
|
5. Plug the radio into your USB port
|
||||||
6. Type "pio run -t upload" (This command will fetch dependencies, build the project and install it on the board via USB)
|
6. Type "pio run -t upload" (This command will fetch dependencies, build the project and install it on the board via USB)
|
||||||
7. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it
|
7. Platform IO also installs a very nice VisualStudio Code based IDE, see their [tutorial](https://docs.platformio.org/en/latest/tutorials/espressif32/arduino_debugging_unit_testing.html) if you'd like to use it
|
||||||
|
|
||||||
|
|
||||||
|
## Decoding stack traces
|
||||||
|
|
||||||
|
If you get a crash, you can decode the addresses from the `Backtrace:` line:
|
||||||
|
1. Save the `Backtrace: 0x....` line to a file, e.g., `backtrace.txt`.
|
||||||
|
2. Run `bin/exception_decoder.py backtrace.txt` (this uses symbols from the
|
||||||
|
last `firmware.elf`, so you must be running the same binary that's still in
|
||||||
|
your `.pio/build` directory).
|
||||||
|
|||||||
BIN
images/system-info.bin
Normal file
BIN
images/system-info.bin
Normal file
Binary file not shown.
@@ -5,7 +5,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Update.h>
|
#include <Update.h>
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "screen.h"
|
|
||||||
|
|
||||||
SimpleAllocator btPool;
|
SimpleAllocator btPool;
|
||||||
|
|
||||||
@@ -173,7 +172,7 @@ uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue)
|
|||||||
|
|
||||||
class MySecurity : public BLESecurityCallbacks
|
class MySecurity : public BLESecurityCallbacks
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
bool onConfirmPIN(uint32_t pin)
|
bool onConfirmPIN(uint32_t pin)
|
||||||
{
|
{
|
||||||
Serial.printf("onConfirmPIN %u\n", pin);
|
Serial.printf("onConfirmPIN %u\n", pin);
|
||||||
@@ -189,7 +188,7 @@ class MySecurity : public BLESecurityCallbacks
|
|||||||
void onPassKeyNotify(uint32_t pass_key)
|
void onPassKeyNotify(uint32_t pass_key)
|
||||||
{
|
{
|
||||||
Serial.printf("onPassKeyNotify %u\n", pass_key);
|
Serial.printf("onPassKeyNotify %u\n", pass_key);
|
||||||
screen_start_bluetooth(pass_key);
|
startCb(pass_key);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool onSecurityRequest()
|
bool onSecurityRequest()
|
||||||
@@ -211,9 +210,13 @@ class MySecurity : public BLESecurityCallbacks
|
|||||||
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
|
Serial.printf("onAuthenticationComplete -> fail %d\n", cmpl.fail_reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove our custom screen
|
// Remove our custom PIN request screen.
|
||||||
screen.setFrames();
|
stopCb();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
StartBluetoothPinScreenCallback startCb;
|
||||||
|
StopBluetoothPinScreenCallback stopCb;
|
||||||
};
|
};
|
||||||
|
|
||||||
BLEServer *pServer;
|
BLEServer *pServer;
|
||||||
@@ -255,7 +258,10 @@ void deinitBLE()
|
|||||||
btPool.reset();
|
btPool.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
BLEServer *initBLE(
|
||||||
|
StartBluetoothPinScreenCallback startBtPinScreen,
|
||||||
|
StopBluetoothPinScreenCallback stopBtPinScreen,
|
||||||
|
std::string deviceName, std::string hwVendor, std::string swVersion, std::string hwVersion)
|
||||||
{
|
{
|
||||||
BLEDevice::init(deviceName);
|
BLEDevice::init(deviceName);
|
||||||
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
|
BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
|
||||||
@@ -264,6 +270,8 @@ BLEServer *initBLE(std::string deviceName, std::string hwVendor, std::string swV
|
|||||||
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
|
* Required in authentication process to provide displaying and/or input passkey or yes/no butttons confirmation
|
||||||
*/
|
*/
|
||||||
static MySecurity mySecurity;
|
static MySecurity mySecurity;
|
||||||
|
mySecurity.startCb = startBtPinScreen;
|
||||||
|
mySecurity.stopCb = stopBtPinScreen;
|
||||||
BLEDevice::setSecurityCallbacks(&mySecurity);
|
BLEDevice::setSecurityCallbacks(&mySecurity);
|
||||||
|
|
||||||
// Create the BLE Server
|
// Create the BLE Server
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <BLEDevice.h>
|
#include <BLEDevice.h>
|
||||||
#include <BLEServer.h>
|
#include <BLEServer.h>
|
||||||
@@ -17,8 +19,14 @@ void dumpCharacteristic(BLECharacteristic *c);
|
|||||||
/** converting endianness pull out a 32 bit value */
|
/** converting endianness pull out a 32 bit value */
|
||||||
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue);
|
uint32_t getValue32(BLECharacteristic *c, uint32_t defaultValue);
|
||||||
|
|
||||||
|
// TODO(girts): create a class for the bluetooth utils helpers?
|
||||||
|
using StartBluetoothPinScreenCallback = std::function<void(uint32_t pass_key)>;
|
||||||
|
using StopBluetoothPinScreenCallback = std::function<void(void)>;
|
||||||
|
|
||||||
void loopBLE();
|
void loopBLE();
|
||||||
BLEServer *initBLE(std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
|
BLEServer *initBLE(
|
||||||
|
StartBluetoothPinScreenCallback startBtPinScreen, StopBluetoothPinScreenCallback stopBtPinScreen,
|
||||||
|
std::string devName, std::string hwVendor, std::string swVersion, std::string hwVersion = "");
|
||||||
void deinitBLE();
|
void deinitBLE();
|
||||||
|
|
||||||
/// Add a characteristic that we will delete when we restart
|
/// Add a characteristic that we will delete when we restart
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ lib_deps =
|
|||||||
; 1260 ; OneButton - not used yet
|
; 1260 ; OneButton - not used yet
|
||||||
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
1202 ; CRC32, explicitly needed because dependency is missing in the ble ota update lib
|
||||||
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
Wire ; explicitly needed here because the AXP202 library forgets to add it
|
||||||
https://github.com/geeksville/arduino-fsm.git
|
https://github.com/meshtastic/arduino-fsm.git
|
||||||
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
https://github.com/meshtastic/SparkFun_Ublox_Arduino_Library.git
|
||||||
|
|
||||||
;[env:tbeam]
|
;[env:tbeam]
|
||||||
|
|||||||
2
proto
2
proto
Submodule proto updated: 66e926740a...1b2449b50d
1
release/.gitignore
vendored
1
release/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
*.elf
|
||||||
*.bin
|
*.bin
|
||||||
*.map
|
*.map
|
||||||
*.zip
|
*.zip
|
||||||
1
release/latest/.gitignore
vendored
Normal file
1
release/latest/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
curfirmwareversion.xml
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<!-- This file is kept in source control because it reflects the last stable
|
|
||||||
release. It is used by the android app for forcing software updates. Do not edit.
|
|
||||||
Generated by bin/buildall.sh -->
|
|
||||||
|
|
||||||
<resources>
|
|
||||||
<string name="cur_firmware_version">0.1.6</string>
|
|
||||||
</resources>
|
|
||||||
@@ -1,20 +1,16 @@
|
|||||||
#include "CustomRF95.h"
|
#include "CustomRF95.h"
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <pb_decode.h>
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "assert.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
#include "assert.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
|
||||||
/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
|
/// A temporary buffer used for sending/receving packets, sized to hold the biggest buffer we might need
|
||||||
#define MAX_RHPACKETLEN 251
|
#define MAX_RHPACKETLEN 251
|
||||||
static uint8_t radiobuf[MAX_RHPACKETLEN];
|
static uint8_t radiobuf[MAX_RHPACKETLEN];
|
||||||
|
|
||||||
CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
|
CustomRF95::CustomRF95(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
|
||||||
: RH_RF95(NSS_GPIO, DIO0_GPIO),
|
: RH_RF95(NSS_GPIO, DIO0_GPIO), pool(_pool), rxDest(_rxDest), txQueue(MAX_TX_QUEUE), sendingPacket(NULL)
|
||||||
pool(_pool),
|
|
||||||
rxDest(_rxDest),
|
|
||||||
txQueue(MAX_TX_QUEUE),
|
|
||||||
sendingPacket(NULL)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,15 +45,12 @@ ErrorCode CustomRF95::send(MeshPacket *p)
|
|||||||
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
|
// We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one).
|
||||||
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
|
// To do otherwise would be doubly bad because not only would we drop the packet that was on the way in,
|
||||||
// we almost certainly guarantee no one outside will like the packet we are sending.
|
// we almost certainly guarantee no one outside will like the packet we are sending.
|
||||||
if (_mode == RHModeIdle || (_mode == RHModeRx && !_isReceiving))
|
if (_mode == RHModeIdle || (_mode == RHModeRx && !_isReceiving)) {
|
||||||
{
|
|
||||||
// if the radio is idle, we can send right away
|
// if the radio is idle, we can send right away
|
||||||
DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad());
|
DEBUG_MSG("immedate send on mesh (txGood=%d,rxGood=%d,rxBad=%d)\n", txGood(), rxGood(), rxBad());
|
||||||
startSend(p);
|
startSend(p);
|
||||||
return ERRNO_OK;
|
return ERRNO_OK;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
|
DEBUG_MSG("enquing packet for send from=0x%x, to=0x%x\n", p->from, p->to);
|
||||||
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
|
ErrorCode res = txQueue.enqueue(p, 0) ? ERRNO_OK : ERRNO_UNKNOWN;
|
||||||
|
|
||||||
@@ -68,7 +61,8 @@ ErrorCode CustomRF95::send(MeshPacket *p)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary
|
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
|
||||||
|
// necessary
|
||||||
void CustomRF95::handleInterrupt()
|
void CustomRF95::handleInterrupt()
|
||||||
{
|
{
|
||||||
RH_RF95::handleInterrupt();
|
RH_RF95::handleInterrupt();
|
||||||
@@ -85,8 +79,7 @@ void CustomRF95::handleInterrupt()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we just finished receiving a packet, forward it into a queue
|
// If we just finished receiving a packet, forward it into a queue
|
||||||
if (_rxBufValid)
|
if (_rxBufValid) {
|
||||||
{
|
|
||||||
// We received a packet
|
// We received a packet
|
||||||
|
|
||||||
// Skip the 4 headers that are at the beginning of the rxBuf
|
// Skip the 4 headers that are at the beginning of the rxBuf
|
||||||
@@ -95,7 +88,7 @@ void CustomRF95::handleInterrupt()
|
|||||||
|
|
||||||
// FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math
|
// FIXME - throws exception if called in ISR context: frequencyError() - probably the floating point math
|
||||||
int32_t freqerr = -1, snr = lastSNR();
|
int32_t freqerr = -1, snr = lastSNR();
|
||||||
//DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n",
|
// DEBUG_MSG("Received packet from mesh src=0x%x,dest=0x%x,id=%d,len=%d rxGood=%d,rxBad=%d,freqErr=%d,snr=%d\n",
|
||||||
// srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr);
|
// srcaddr, destaddr, id, rxlen, rf95.rxGood(), rf95.rxBad(), freqerr, snr);
|
||||||
|
|
||||||
MeshPacket *mp = pool.allocZeroed();
|
MeshPacket *mp = pool.allocZeroed();
|
||||||
@@ -111,23 +104,18 @@ void CustomRF95::handleInterrupt()
|
|||||||
// Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will
|
// Note: we can't create it at this point, because it might be a bogus User node allocation. But odds are we will
|
||||||
// already have a record we can hide this debugging info in.
|
// already have a record we can hide this debugging info in.
|
||||||
NodeInfo *info = nodeDB.getNode(mp->from);
|
NodeInfo *info = nodeDB.getNode(mp->from);
|
||||||
if (info)
|
if (info) {
|
||||||
{
|
|
||||||
info->snr = snr;
|
info->snr = snr;
|
||||||
info->frequency_error = freqerr;
|
info->frequency_error = freqerr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p))
|
if (!pb_decode_from_bytes(payload, payloadLen, SubPacket_fields, p)) {
|
||||||
{
|
|
||||||
pool.releaseFromISR(mp, &higherPriWoken);
|
pool.releaseFromISR(mp, &higherPriWoken);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// parsing was successful, queue for our recipient
|
// parsing was successful, queue for our recipient
|
||||||
mp->has_payload = true;
|
mp->has_payload = true;
|
||||||
|
|
||||||
int res = rxDest.enqueueFromISR(mp, &higherPriWoken); // NOWAIT - fixme, if queue is full, delete older messages
|
assert(rxDest.enqueueFromISR(mp, &higherPriWoken)); // NOWAIT - fixme, if queue is full, delete older messages
|
||||||
assert(res == pdTRUE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearRxBuf(); // This message accepted and cleared
|
clearRxBuf(); // This message accepted and cleared
|
||||||
@@ -153,8 +141,7 @@ bool CustomRF95::handleIdleISR()
|
|||||||
MeshPacket *txp = txQueue.dequeuePtrFromISR(0);
|
MeshPacket *txp = txQueue.dequeuePtrFromISR(0);
|
||||||
if (txp)
|
if (txp)
|
||||||
startSend(txp);
|
startSend(txp);
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
// Nothing to send, let's switch back to receive mode
|
// Nothing to send, let's switch back to receive mode
|
||||||
setModeRx();
|
setModeRx();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <RH_RF95.h>
|
|
||||||
#include <RHMesh.h>
|
|
||||||
#include "MemoryPool.h"
|
#include "MemoryPool.h"
|
||||||
#include "mesh.pb.h"
|
|
||||||
#include "PointerQueue.h"
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
|
#include "PointerQueue.h"
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
#include <RHMesh.h>
|
||||||
|
#include <RH_RF95.h>
|
||||||
|
|
||||||
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
#define MAX_TX_QUEUE 16 // max number of packets which can be waiting for transmission
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!)
|
* A version of the RF95 driver which is smart enough to manage packets via queues (no polling or blocking in user threads!)
|
||||||
*/
|
*/
|
||||||
@@ -22,7 +21,7 @@ class CustomRF95 : public RH_RF95
|
|||||||
PointerQueue<MeshPacket> txQueue;
|
PointerQueue<MeshPacket> txQueue;
|
||||||
|
|
||||||
MeshPacket *sendingPacket; // The packet we are currently sending
|
MeshPacket *sendingPacket; // The packet we are currently sending
|
||||||
public:
|
public:
|
||||||
/** pool is the pool we will alloc our rx packets from
|
/** pool is the pool we will alloc our rx packets from
|
||||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||||
*/
|
*/
|
||||||
@@ -45,11 +44,12 @@ public:
|
|||||||
|
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as necessary
|
// After doing standard behavior, check to see if a new packet arrived or one was sent and start a new send or receive as
|
||||||
|
// necessary
|
||||||
virtual void handleInterrupt();
|
virtual void handleInterrupt();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Send a new packet - this low level call can be called from either ISR or userspace
|
/// Send a new packet - this low level call can be called from either ISR or userspace
|
||||||
void startSend(MeshPacket *txp);
|
void startSend(MeshPacket *txp);
|
||||||
|
|
||||||
|
|||||||
103
src/GPS.cpp
103
src/GPS.cpp
@@ -1,25 +1,25 @@
|
|||||||
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
|
#include "configuration.h"
|
||||||
#include "time.h"
|
#include "time.h"
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include "configuration.h"
|
|
||||||
|
|
||||||
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
|
HardwareSerial _serial_gps(GPS_SERIAL_NUM);
|
||||||
|
|
||||||
RTC_DATA_ATTR bool timeSetFromGPS; // We only reset our time once per _boot_ after that point just run from the internal clock (even across sleeps)
|
RTC_DATA_ATTR bool timeSetFromGPS; // We only reset our time once per _boot_ after that point just run from the internal clock
|
||||||
|
// (even across sleeps)
|
||||||
|
|
||||||
GPS gps;
|
GPS gps;
|
||||||
|
|
||||||
// stuff that really should be in in the instance instead...
|
// stuff that really should be in in the instance instead...
|
||||||
static uint32_t timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
static uint32_t
|
||||||
|
timeStartMsec; // Once we have a GPS lock, this is where we hold the initial msec clock that corresponds to that time
|
||||||
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
static uint64_t zeroOffsetSecs; // GPS based time in secs since 1970 - only updated once on initial lock
|
||||||
|
|
||||||
static bool hasValidLocation; // default to false, until we complete our first read
|
static bool hasValidLocation; // default to false, until we complete our first read
|
||||||
static bool wantNewLocation = true;
|
static bool wantNewLocation = true;
|
||||||
|
|
||||||
GPS::GPS() : PeriodicTask()
|
GPS::GPS() : PeriodicTask() {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPS::setup()
|
void GPS::setup()
|
||||||
{
|
{
|
||||||
@@ -35,50 +35,46 @@ void GPS::setup()
|
|||||||
isConnected = ublox.begin(_serial_gps);
|
isConnected = ublox.begin(_serial_gps);
|
||||||
|
|
||||||
// try a second time, the ublox lib serial parsing is buggy?
|
// try a second time, the ublox lib serial parsing is buggy?
|
||||||
// if(!isConnected) isConnected = ublox.begin(_serial_gps);
|
if (!isConnected)
|
||||||
|
isConnected = ublox.begin(_serial_gps);
|
||||||
|
|
||||||
if (isConnected)
|
if (isConnected) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN);
|
DEBUG_MSG("Connected to GPS successfully, TXpin=%d\n", GPS_TX_PIN);
|
||||||
|
|
||||||
bool factoryReset = false;
|
bool factoryReset = false;
|
||||||
bool ok;
|
bool ok;
|
||||||
if (factoryReset)
|
if (factoryReset) {
|
||||||
{
|
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have
|
||||||
// It is useful to force back into factory defaults (9600baud, NEMA to test the behavior of boards that don't have GPS_TX connected)
|
// GPS_TX connected)
|
||||||
ublox.factoryReset();
|
ublox.factoryReset();
|
||||||
delay(2000);
|
delay(2000);
|
||||||
isConnected = ublox.begin(_serial_gps);
|
isConnected = ublox.begin(_serial_gps);
|
||||||
DEBUG_MSG("Factory reset success=%d\n", isConnected);
|
DEBUG_MSG("Factory reset success=%d\n", isConnected);
|
||||||
if (isConnected)
|
if (isConnected) {
|
||||||
{
|
|
||||||
ublox.assumeAutoPVT(true, true); // Just parse NEMA for now
|
ublox.assumeAutoPVT(true, true); // Just parse NEMA for now
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
|
ok = ublox.setUART1Output(COM_TYPE_UBX, 500); // Use native API
|
||||||
assert(ok);
|
assert(ok);
|
||||||
ok = ublox.setNavigationFrequency(1, 500); //Produce 4x/sec to keep the amount of time we stall in getPVT low
|
ok = ublox.setNavigationFrequency(1, 500); // Produce 4x/sec to keep the amount of time we stall in getPVT low
|
||||||
assert(ok);
|
assert(ok);
|
||||||
//ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
|
// ok = ublox.setAutoPVT(false); // Not implemented on NEO-6M
|
||||||
//assert(ok);
|
// assert(ok);
|
||||||
//ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
|
// ok = ublox.setDynamicModel(DYN_MODEL_BIKE); // probably PEDESTRIAN but just in case assume bike speeds
|
||||||
//assert(ok);
|
// assert(ok);
|
||||||
ok = ublox.powerSaveMode(); //use power save mode
|
ok = ublox.powerSaveMode(); // use power save mode
|
||||||
assert(ok);
|
assert(ok);
|
||||||
}
|
}
|
||||||
ok = ublox.saveConfiguration(2000);
|
ok = ublox.saveConfiguration(2000);
|
||||||
assert(ok);
|
assert(ok);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
// Some boards might have only the TX line from the GPS connected, in that case, we can't configure it at all. Just
|
||||||
// assume NEMA at 9600 baud.
|
// assume NEMA at 9600 baud.
|
||||||
DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n");
|
DEBUG_MSG("ERROR: No bidirectional GPS found, hoping that it still might work\n");
|
||||||
|
|
||||||
// tell lib, we are expecting the module to send PVT messages by itself to our Rx pin
|
// tell lib, we are expecting the module to send PVT messages by itself to our Rx pin
|
||||||
// you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call checkUblox cyclically)
|
// you can set second parameter to "false" if you want to control the parsing and eviction of the data (need to call
|
||||||
|
// checkUblox cyclically)
|
||||||
ublox.assumeAutoPVT(true, true);
|
ublox.assumeAutoPVT(true, true);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -88,8 +84,7 @@ void GPS::readFromRTC()
|
|||||||
{
|
{
|
||||||
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
struct timeval tv; /* btw settimeofday() is helpfull here too*/
|
||||||
|
|
||||||
if (!gettimeofday(&tv, NULL))
|
if (!gettimeofday(&tv, NULL)) {
|
||||||
{
|
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
|
|
||||||
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
DEBUG_MSG("Read RTC time as %ld (cur millis %u) valid=%d\n", tv.tv_sec, now, timeSetFromGPS);
|
||||||
@@ -101,8 +96,7 @@ void GPS::readFromRTC()
|
|||||||
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
/// If we haven't yet set our RTC this boot, set it from a GPS derived time
|
||||||
void GPS::perhapsSetRTC(const struct timeval *tv)
|
void GPS::perhapsSetRTC(const struct timeval *tv)
|
||||||
{
|
{
|
||||||
if (!timeSetFromGPS)
|
if (!timeSetFromGPS) {
|
||||||
{
|
|
||||||
timeSetFromGPS = true;
|
timeSetFromGPS = true;
|
||||||
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
DEBUG_MSG("Setting RTC %ld secs\n", tv->tv_sec);
|
||||||
settimeofday(tv, NULL);
|
settimeofday(tv, NULL);
|
||||||
@@ -137,34 +131,39 @@ bool GPS::canSleep()
|
|||||||
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
/// Prepare the GPS for the cpu entering deep or light sleep, expect to be gone for at least 100s of msecs
|
||||||
void GPS::prepareSleep()
|
void GPS::prepareSleep()
|
||||||
{
|
{
|
||||||
ublox.powerOff();
|
if (isConnected)
|
||||||
|
ublox.powerOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPS::doTask()
|
void GPS::doTask()
|
||||||
{
|
{
|
||||||
#ifdef GPS_RX_PIN
|
#ifdef GPS_RX_PIN
|
||||||
// Consume all characters that have arrived
|
uint8_t fixtype = 3; // If we are only using the RX pin, assume we have a 3d fix
|
||||||
|
|
||||||
// getPVT automatically calls checkUblox
|
if (isConnected) {
|
||||||
ublox.checkUblox(); //See if new data is available. Process bytes as they come in.
|
// Consume all characters that have arrived
|
||||||
|
|
||||||
|
// getPVT automatically calls checkUblox
|
||||||
|
ublox.checkUblox(); // See if new data is available. Process bytes as they come in.
|
||||||
|
|
||||||
|
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
||||||
|
// Hmmm my fix type reading returns zeros for fix, which doesn't seem correct, because it is still sptting out positions
|
||||||
|
// turn off for now
|
||||||
|
// fixtype = ublox.getFixType();
|
||||||
|
DEBUG_MSG("fix type %d\n", fixtype);
|
||||||
|
}
|
||||||
|
|
||||||
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
// DEBUG_MSG("sec %d\n", ublox.getSecond());
|
||||||
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
// DEBUG_MSG("lat %d\n", ublox.getLatitude());
|
||||||
|
|
||||||
// If we don't have a fix (a quick check), don't try waiting for a solution)
|
|
||||||
uint8_t fixtype = ublox.getFixType();
|
|
||||||
DEBUG_MSG("fix type %d\n", fixtype);
|
|
||||||
|
|
||||||
// any fix that has time
|
// any fix that has time
|
||||||
if ((fixtype >= 2 && fixtype <= 5) && !timeSetFromGPS && ublox.getT())
|
if (!timeSetFromGPS && ublox.getT()) {
|
||||||
{
|
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
isConnected = true; // We just received a packet, so we must have a GPS
|
|
||||||
|
|
||||||
/* Convert to unix time
|
/* Convert to unix time
|
||||||
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
The Unix epoch (or Unix time or POSIX time or Unix timestamp) is the number of seconds that have elapsed since January 1, 1970
|
||||||
*/
|
(midnight UTC/GMT), not counting leap seconds (in ISO 8601: 1970-01-01T00:00:00Z).
|
||||||
|
*/
|
||||||
struct tm t;
|
struct tm t;
|
||||||
t.tm_sec = ublox.getSecond();
|
t.tm_sec = ublox.getSecond();
|
||||||
t.tm_min = ublox.getMinute();
|
t.tm_min = ublox.getMinute();
|
||||||
@@ -185,27 +184,23 @@ void GPS::doTask()
|
|||||||
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only
|
if ((fixtype >= 3 && fixtype <= 4) && ublox.getP()) // rd fixes only
|
||||||
{
|
{
|
||||||
// we only notify if position has changed
|
// we only notify if position has changed
|
||||||
isConnected = true; // We just received a packet, so we must have a GPS
|
|
||||||
|
|
||||||
latitude = ublox.getLatitude() * 1e-7;
|
latitude = ublox.getLatitude() * 1e-7;
|
||||||
longitude = ublox.getLongitude() * 1e-7;
|
longitude = ublox.getLongitude() * 1e-7;
|
||||||
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
|
altitude = ublox.getAltitude() / 1000; // in mm convert to meters
|
||||||
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude);
|
DEBUG_MSG("new gps pos lat=%f, lon=%f, alt=%d\n", latitude, longitude, altitude);
|
||||||
|
|
||||||
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
hasValidLocation = (latitude != 0) || (longitude != 0); // bogus lat lon is reported as 0,0
|
||||||
if (hasValidLocation)
|
if (hasValidLocation) {
|
||||||
{
|
|
||||||
wantNewLocation = false;
|
wantNewLocation = false;
|
||||||
notifyObservers();
|
notifyObservers();
|
||||||
//ublox.powerOff();
|
// ublox.powerOff();
|
||||||
}
|
}
|
||||||
}
|
} else // we didn't get a location update, go back to sleep and hope the characters show up
|
||||||
else // we didn't get a location update, go back to sleep and hope the characters show up
|
|
||||||
wantNewLocation = true;
|
wantNewLocation = true;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over the serial
|
// Once we have sent a location once we only poll the GPS rarely, otherwise check back every 1s until we have something over
|
||||||
|
// the serial
|
||||||
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
setPeriod(hasValidLocation && !wantNewLocation ? 30 * 1000 : 10 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "PeriodicTask.h"
|
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
#include "sys/time.h"
|
#include "PeriodicTask.h"
|
||||||
#include "SparkFun_Ublox_Arduino_Library.h"
|
#include "SparkFun_Ublox_Arduino_Library.h"
|
||||||
|
#include "sys/time.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
* A gps class that only reads from the GPS periodically (and FIXME - eventually keeps the gps powered down except when reading)
|
||||||
@@ -14,7 +14,7 @@ class GPS : public PeriodicTask, public Observable
|
|||||||
{
|
{
|
||||||
SFE_UBLOX_GPS ublox;
|
SFE_UBLOX_GPS ublox;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
double latitude, longitude;
|
double latitude, longitude;
|
||||||
uint32_t altitude;
|
uint32_t altitude;
|
||||||
bool isConnected; // Do we have a GPS we are talking to
|
bool isConnected; // Do we have a GPS we are talking to
|
||||||
@@ -45,9 +45,8 @@ public:
|
|||||||
/// Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
/// Restart our lock attempt - try to get and broadcast a GPS reading ASAP
|
||||||
void startLock();
|
void startLock();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void readFromRTC();
|
void readFromRTC();
|
||||||
};
|
};
|
||||||
|
|
||||||
extern GPS gps;
|
extern GPS gps;
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,7 @@
|
|||||||
*
|
*
|
||||||
* Eventually this routine will even be safe for ISR use...
|
* Eventually this routine will even be safe for ISR use...
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <class T> class MemoryPool
|
||||||
class MemoryPool
|
|
||||||
{
|
{
|
||||||
PointerQueue<T> dead;
|
PointerQueue<T> dead;
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ class MemoryPool
|
|||||||
|
|
||||||
size_t maxElements;
|
size_t maxElements;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements)
|
MemoryPool(size_t _maxElements) : dead(_maxElements), maxElements(_maxElements)
|
||||||
{
|
{
|
||||||
buf = new T[maxElements];
|
buf = new T[maxElements];
|
||||||
@@ -29,10 +28,7 @@ public:
|
|||||||
release(&buf[i]);
|
release(&buf[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
~MemoryPool()
|
~MemoryPool() { delete[] buf; }
|
||||||
{
|
|
||||||
delete[] buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
|
/// Return a queable object which has been prefilled with zeros. Panic if no buffer is available
|
||||||
T *allocZeroed()
|
T *allocZeroed()
|
||||||
@@ -43,7 +39,8 @@ public:
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably don't want this version)
|
/// Return a queable object which has been prefilled with zeros - allow timeout to wait for available buffers (you probably
|
||||||
|
/// don't want this version)
|
||||||
T *allocZeroed(TickType_t maxWait)
|
T *allocZeroed(TickType_t maxWait)
|
||||||
{
|
{
|
||||||
T *p = dead.dequeuePtr(maxWait);
|
T *p = dead.dequeuePtr(maxWait);
|
||||||
@@ -66,16 +63,18 @@ public:
|
|||||||
/// Return a buffer for use by others
|
/// Return a buffer for use by others
|
||||||
void release(T *p)
|
void release(T *p)
|
||||||
{
|
{
|
||||||
int res = dead.enqueue(p, 0);
|
assert(dead.enqueue(p, 0));
|
||||||
assert(res == pdTRUE);
|
assert(p >= buf &&
|
||||||
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
(p - buf) <
|
||||||
|
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
|
/// Return a buffer from an ISR, if higherPriWoken is set to true you have some work to do ;-)
|
||||||
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
|
void releaseFromISR(T *p, BaseType_t *higherPriWoken)
|
||||||
{
|
{
|
||||||
int res = dead.enqueueFromISR(p, higherPriWoken);
|
assert(dead.enqueueFromISR(p, higherPriWoken));
|
||||||
assert(res == pdTRUE);
|
assert(p >= buf &&
|
||||||
assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
(p - buf) <
|
||||||
|
maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
#include "BluetoothUtil.h"
|
|
||||||
#include "MeshBluetoothService.h"
|
#include "MeshBluetoothService.h"
|
||||||
#include <esp_gatt_defs.h>
|
#include "BluetoothUtil.h"
|
||||||
#include <BLE2902.h>
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <BLE2902.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <esp_gatt_defs.h>
|
||||||
|
|
||||||
#include "mesh.pb.h"
|
|
||||||
#include "MeshService.h"
|
|
||||||
#include "mesh-pb-constants.h"
|
|
||||||
#include "NodeDB.h"
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "PowerFSM.h"
|
|
||||||
#include "CallbackCharacteristic.h"
|
#include "CallbackCharacteristic.h"
|
||||||
|
#include "MeshService.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "PowerFSM.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
|
||||||
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in proccess at once
|
#include "GPS.h"
|
||||||
|
|
||||||
|
// This scratch buffer is used for various bluetooth reads/writes - but it is safe because only one bt operation can be in
|
||||||
|
// proccess at once
|
||||||
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
|
static uint8_t trBytes[_max(_max(_max(_max(ToRadio_size, RadioConfig_size), User_size), MyNodeInfo_size), FromRadio_size)];
|
||||||
|
|
||||||
class ProtobufCharacteristic : public CallbackCharacteristic
|
class ProtobufCharacteristic : public CallbackCharacteristic
|
||||||
@@ -21,11 +24,9 @@ class ProtobufCharacteristic : public CallbackCharacteristic
|
|||||||
const pb_msgdesc_t *fields;
|
const pb_msgdesc_t *fields;
|
||||||
void *my_struct;
|
void *my_struct;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct)
|
ProtobufCharacteristic(const char *uuid, uint32_t btprops, const pb_msgdesc_t *_fields, void *_my_struct)
|
||||||
: CallbackCharacteristic(uuid, btprops),
|
: CallbackCharacteristic(uuid, btprops), fields(_fields), my_struct(_my_struct)
|
||||||
fields(_fields),
|
|
||||||
my_struct(_my_struct)
|
|
||||||
{
|
{
|
||||||
setCallbacks(this);
|
setCallbacks(this);
|
||||||
}
|
}
|
||||||
@@ -44,7 +45,7 @@ public:
|
|||||||
writeToDest(c, my_struct);
|
writeToDest(c, my_struct);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// like onWrite, but we provide an different destination to write to, for use by subclasses that
|
/// like onWrite, but we provide an different destination to write to, for use by subclasses that
|
||||||
/// want to optionally ignore parts of writes.
|
/// want to optionally ignore parts of writes.
|
||||||
/// returns true for success
|
/// returns true for success
|
||||||
@@ -59,9 +60,10 @@ protected:
|
|||||||
|
|
||||||
class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
|
class NodeInfoCharacteristic : public BLECharacteristic, public BLEKeepAliveCallbacks
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NodeInfoCharacteristic()
|
NodeInfoCharacteristic()
|
||||||
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
: BLECharacteristic("d31e02e0-c8ab-4d3f-9cc9-0b8466bdabe8",
|
||||||
|
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ)
|
||||||
{
|
{
|
||||||
setCallbacks(this);
|
setCallbacks(this);
|
||||||
}
|
}
|
||||||
@@ -72,14 +74,12 @@ public:
|
|||||||
|
|
||||||
const NodeInfo *info = nodeDB.readNextInfo();
|
const NodeInfo *info = nodeDB.readNextInfo();
|
||||||
|
|
||||||
if (info)
|
if (info) {
|
||||||
{
|
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id,
|
||||||
DEBUG_MSG("Sending nodeinfo: num=0x%x, lastseen=%u, id=%s, name=%s\n", info->num, info->position.time, info->user.id, info->user.long_name);
|
info->user.long_name);
|
||||||
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info);
|
size_t numbytes = pb_encode_to_bytes(trBytes, sizeof(trBytes), NodeInfo_fields, info);
|
||||||
c->setValue(trBytes, numbytes);
|
c->setValue(trBytes, numbytes);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
c->setValue(trBytes, 0); // Send an empty response
|
c->setValue(trBytes, 0); // Send an empty response
|
||||||
DEBUG_MSG("Done sending nodeinfos\n");
|
DEBUG_MSG("Done sending nodeinfos\n");
|
||||||
}
|
}
|
||||||
@@ -96,12 +96,20 @@ public:
|
|||||||
// wrap our protobuf version with something that forces the service to reload the config
|
// wrap our protobuf version with something that forces the service to reload the config
|
||||||
class RadioCharacteristic : public ProtobufCharacteristic
|
class RadioCharacteristic : public ProtobufCharacteristic
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
RadioCharacteristic()
|
RadioCharacteristic()
|
||||||
: ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields, &radioConfig)
|
: ProtobufCharacteristic("b56786c8-839a-44a1-b98e-a1724c4a0262",
|
||||||
|
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, RadioConfig_fields,
|
||||||
|
&radioConfig)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onRead(BLECharacteristic *c)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Reading radio config\n");
|
||||||
|
ProtobufCharacteristic::onRead(c);
|
||||||
|
}
|
||||||
|
|
||||||
void onWrite(BLECharacteristic *c)
|
void onWrite(BLECharacteristic *c)
|
||||||
{
|
{
|
||||||
ProtobufCharacteristic::onWrite(c);
|
ProtobufCharacteristic::onWrite(c);
|
||||||
@@ -112,33 +120,31 @@ public:
|
|||||||
// wrap our protobuf version with something that forces the service to reload the owner
|
// wrap our protobuf version with something that forces the service to reload the owner
|
||||||
class OwnerCharacteristic : public ProtobufCharacteristic
|
class OwnerCharacteristic : public ProtobufCharacteristic
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
OwnerCharacteristic()
|
OwnerCharacteristic()
|
||||||
: ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6", BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner)
|
: ProtobufCharacteristic("6ff1d8b6-e2de-41e3-8c0b-8fa384f64eb6",
|
||||||
|
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ, User_fields, &owner)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void onWrite(BLECharacteristic *c)
|
void onWrite(BLECharacteristic *c)
|
||||||
{
|
{
|
||||||
BLEKeepAliveCallbacks::onWrite(c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior
|
BLEKeepAliveCallbacks::onWrite(
|
||||||
|
c); // NOTE: We do not call the standard ProtobufCharacteristic superclass, because we want custom write behavior
|
||||||
|
|
||||||
static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr
|
static User o; // if the phone doesn't set ID we are careful to keep ours, we also always keep our macaddr
|
||||||
if (writeToDest(c, &o))
|
if (writeToDest(c, &o)) {
|
||||||
{
|
|
||||||
int changed = 0;
|
int changed = 0;
|
||||||
|
|
||||||
if (*o.long_name)
|
if (*o.long_name) {
|
||||||
{
|
|
||||||
changed |= strcmp(owner.long_name, o.long_name);
|
changed |= strcmp(owner.long_name, o.long_name);
|
||||||
strcpy(owner.long_name, o.long_name);
|
strcpy(owner.long_name, o.long_name);
|
||||||
}
|
}
|
||||||
if (*o.short_name)
|
if (*o.short_name) {
|
||||||
{
|
|
||||||
changed |= strcmp(owner.short_name, o.short_name);
|
changed |= strcmp(owner.short_name, o.short_name);
|
||||||
strcpy(owner.short_name, o.short_name);
|
strcpy(owner.short_name, o.short_name);
|
||||||
}
|
}
|
||||||
if (*o.id)
|
if (*o.id) {
|
||||||
{
|
|
||||||
changed |= strcmp(owner.id, o.id);
|
changed |= strcmp(owner.id, o.id);
|
||||||
strcpy(owner.id, o.id);
|
strcpy(owner.id, o.id);
|
||||||
}
|
}
|
||||||
@@ -151,11 +157,8 @@ public:
|
|||||||
|
|
||||||
class ToRadioCharacteristic : public CallbackCharacteristic
|
class ToRadioCharacteristic : public CallbackCharacteristic
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
ToRadioCharacteristic()
|
ToRadioCharacteristic() : CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE) {}
|
||||||
: CallbackCharacteristic("f75c76d2-129e-4dad-a1dd-7866124401e7", BLECharacteristic::PROPERTY_WRITE)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void onWrite(BLECharacteristic *c)
|
void onWrite(BLECharacteristic *c)
|
||||||
{
|
{
|
||||||
@@ -168,9 +171,8 @@ public:
|
|||||||
|
|
||||||
class FromRadioCharacteristic : public CallbackCharacteristic
|
class FromRadioCharacteristic : public CallbackCharacteristic
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FromRadioCharacteristic()
|
FromRadioCharacteristic() : CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
|
||||||
: CallbackCharacteristic("8ba2bcc2-ee02-4a55-a531-c525c5e454d5", BLECharacteristic::PROPERTY_READ)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,13 +183,10 @@ public:
|
|||||||
|
|
||||||
// Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue
|
// Someone is going to read our value as soon as this callback returns. So fill it with the next message in the queue
|
||||||
// or make empty if the queue is empty
|
// or make empty if the queue is empty
|
||||||
if (!mp)
|
if (!mp) {
|
||||||
{
|
|
||||||
DEBUG_MSG("toPhone queue is empty\n");
|
DEBUG_MSG("toPhone queue is empty\n");
|
||||||
c->setValue((uint8_t *)"", 0);
|
c->setValue((uint8_t *)"", 0);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
static FromRadio fRadio;
|
static FromRadio fRadio;
|
||||||
|
|
||||||
// Encapsulate as a FromRadio packet
|
// Encapsulate as a FromRadio packet
|
||||||
@@ -206,10 +205,11 @@ public:
|
|||||||
|
|
||||||
class FromNumCharacteristic : public CallbackCharacteristic
|
class FromNumCharacteristic : public CallbackCharacteristic
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FromNumCharacteristic()
|
FromNumCharacteristic()
|
||||||
: CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453",
|
: CallbackCharacteristic("ed9da18c-a800-4f66-a670-aa7547e34453", BLECharacteristic::PROPERTY_WRITE |
|
||||||
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY)
|
BLECharacteristic::PROPERTY_READ |
|
||||||
|
BLECharacteristic::PROPERTY_NOTIFY)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +220,27 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MyNodeInfoCharacteristic : public ProtobufCharacteristic
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MyNodeInfoCharacteristic()
|
||||||
|
: ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields,
|
||||||
|
&myNodeInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRead(BLECharacteristic *c)
|
||||||
|
{
|
||||||
|
// update gps connection state
|
||||||
|
myNodeInfo.has_gps = gps.isConnected;
|
||||||
|
|
||||||
|
ProtobufCharacteristic::onRead(c);
|
||||||
|
|
||||||
|
myNodeInfo.error_code = 0; // The phone just read us, so throw it away
|
||||||
|
myNodeInfo.error_address = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
FromNumCharacteristic *meshFromNumCharacteristic;
|
FromNumCharacteristic *meshFromNumCharacteristic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,8 +248,7 @@ FromNumCharacteristic *meshFromNumCharacteristic;
|
|||||||
*/
|
*/
|
||||||
void bluetoothNotifyFromNum(uint32_t newValue)
|
void bluetoothNotifyFromNum(uint32_t newValue)
|
||||||
{
|
{
|
||||||
if (meshFromNumCharacteristic)
|
if (meshFromNumCharacteristic) {
|
||||||
{
|
|
||||||
// if bt not running ignore
|
// if bt not running ignore
|
||||||
meshFromNumCharacteristic->setValue(newValue);
|
meshFromNumCharacteristic->setValue(newValue);
|
||||||
meshFromNumCharacteristic->notify();
|
meshFromNumCharacteristic->notify();
|
||||||
@@ -251,8 +271,7 @@ BLEService *createMeshBluetoothService(BLEServer *server)
|
|||||||
addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
|
addWithDesc(service, meshFromNumCharacteristic, "fromRadio");
|
||||||
addWithDesc(service, new ToRadioCharacteristic, "toRadio");
|
addWithDesc(service, new ToRadioCharacteristic, "toRadio");
|
||||||
addWithDesc(service, new FromRadioCharacteristic, "fromNum");
|
addWithDesc(service, new FromRadioCharacteristic, "fromNum");
|
||||||
|
addWithDesc(service, new MyNodeInfoCharacteristic, "myNode");
|
||||||
addWithDesc(service, new ProtobufCharacteristic("ea9f3f82-8dc4-4733-9452-1f6da28892a2", BLECharacteristic::PROPERTY_READ, MyNodeInfo_fields, &myNodeInfo), "myNode");
|
|
||||||
addWithDesc(service, new RadioCharacteristic, "radio");
|
addWithDesc(service, new RadioCharacteristic, "radio");
|
||||||
addWithDesc(service, new OwnerCharacteristic, "owner");
|
addWithDesc(service, new OwnerCharacteristic, "owner");
|
||||||
addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo");
|
addWithDesc(service, new NodeInfoCharacteristic, "nodeinfo");
|
||||||
@@ -263,8 +282,7 @@ BLEService *createMeshBluetoothService(BLEServer *server)
|
|||||||
|
|
||||||
// We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies
|
// We only add to advertisting once, because the ESP32 arduino code is dumb and that object never dies
|
||||||
static bool firstTime = true;
|
static bool firstTime = true;
|
||||||
if (firstTime)
|
if (firstTime) {
|
||||||
{
|
|
||||||
firstTime = false;
|
firstTime = false;
|
||||||
server->getAdvertising()->addServiceUUID(service->getUUID());
|
server->getAdvertising()->addServiceUUID(service->getUUID());
|
||||||
}
|
}
|
||||||
@@ -282,8 +300,6 @@ void stopMeshBluetoothService()
|
|||||||
meshService->stop();
|
meshService->stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void destroyMeshBluetoothService()
|
void destroyMeshBluetoothService()
|
||||||
{
|
{
|
||||||
assert(meshService);
|
assert(meshService);
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <BLEService.h>
|
|
||||||
#include <BLEServer.h>
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
#include <BLEServer.h>
|
||||||
|
#include <BLEService.h>
|
||||||
|
|
||||||
BLEService *createMeshBluetoothService(BLEServer* server);
|
BLEService *createMeshBluetoothService(BLEServer *server);
|
||||||
void destroyMeshBluetoothService();
|
void destroyMeshBluetoothService();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
#include <SPI.h>
|
|
||||||
#include "RH_RF95.h"
|
#include "RH_RF95.h"
|
||||||
|
#include "error.h"
|
||||||
#include <RHMesh.h>
|
#include <RHMesh.h>
|
||||||
|
#include <SPI.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <pb_decode.h>
|
|
||||||
#include "MeshRadio.h"
|
#include "MeshRadio.h"
|
||||||
#include "configuration.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
#include "configuration.h"
|
||||||
#define DEFAULT_CHANNEL_NUM 3 // we randomly pick one
|
#include <pb_decode.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
|
||||||
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
|
/// 16 bytes of random PSK for our _public_ default channel that all devices power up on
|
||||||
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59, 0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
static const uint8_t defaultpsk[] = {0xd4, 0xf1, 0xbb, 0x3a, 0x20, 0x29, 0x07, 0x59,
|
||||||
|
0xf0, 0xbc, 0xff, 0xab, 0xcf, 0x4e, 0x69, 0xbf};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ## LoRaWAN for North America
|
* ## LoRaWAN for North America
|
||||||
@@ -21,112 +21,136 @@ LoRaWAN defines 64, 125 kHz channels from 902.3 to 914.9 MHz increments.
|
|||||||
|
|
||||||
The maximum output power for North America is +30 dBM.
|
The maximum output power for North America is +30 dBM.
|
||||||
|
|
||||||
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are separated by 2.16 MHz with respect to the adjacent channels.
|
The band is from 902 to 928 MHz. It mentions channel number and its respective channel frequency. All the 13 channels are
|
||||||
Channel zero starts at 903.08 MHz center frequency.
|
separated by 2.16 MHz with respect to the adjacent channels. Channel zero starts at 903.08 MHz center frequency.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses
|
/// Sometimes while debugging it is useful to set this false, to disable rf95 accesses
|
||||||
bool useHardware = true;
|
bool useHardware = true;
|
||||||
|
|
||||||
MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest)
|
MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_rxDest) : rf95(_pool, _rxDest), manager(rf95)
|
||||||
: rf95(_pool, _rxDest),
|
|
||||||
manager(rf95)
|
|
||||||
{
|
{
|
||||||
myNodeInfo.num_channels = NUM_CHANNELS;
|
myNodeInfo.num_channels = NUM_CHANNELS;
|
||||||
|
|
||||||
//radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
// radioConfig.modem_config = RadioConfig_ModemConfig_Bw125Cr45Sf128; // medium range and fast
|
||||||
//channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth so incompatible radios can talk together
|
// channelSettings.modem_config = ChannelSettings_ModemConfig_Bw500Cr45Sf128; // short range and fast, but wide bandwidth so
|
||||||
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
// incompatible radios can talk together
|
||||||
|
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range
|
||||||
|
|
||||||
channelSettings.tx_power = 23;
|
channelSettings.tx_power = 23;
|
||||||
channelSettings.channel_num = DEFAULT_CHANNEL_NUM;
|
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
|
||||||
memcpy(&channelSettings.psk, &defaultpsk, sizeof(channelSettings.psk));
|
strcpy(channelSettings.name, "Default");
|
||||||
strcpy(channelSettings.name, "Default");
|
// Can't print strings this early - serial not setup yet
|
||||||
// Can't print strings this early - serial not setup yet
|
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
||||||
// DEBUG_MSG("Set meshradio defaults name=%s\n", channelSettings.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MeshRadio::init()
|
bool MeshRadio::init()
|
||||||
{
|
{
|
||||||
if (!useHardware)
|
if (!useHardware)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
DEBUG_MSG("Starting meshradio init...\n");
|
DEBUG_MSG("Starting meshradio init...\n");
|
||||||
|
|
||||||
#ifdef RESET_GPIO
|
#ifdef RESET_GPIO
|
||||||
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
|
pinMode(RESET_GPIO, OUTPUT); // Deassert reset
|
||||||
digitalWrite(RESET_GPIO, HIGH);
|
digitalWrite(RESET_GPIO, HIGH);
|
||||||
|
|
||||||
// pulse reset
|
// pulse reset
|
||||||
digitalWrite(RESET_GPIO, LOW);
|
digitalWrite(RESET_GPIO, LOW);
|
||||||
delay(10);
|
delay(10);
|
||||||
digitalWrite(RESET_GPIO, HIGH);
|
digitalWrite(RESET_GPIO, HIGH);
|
||||||
delay(10);
|
delay(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
manager.setThisAddress(nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
|
manager.setThisAddress(
|
||||||
|
nodeDB.getNodeNum()); // Note: we must do this here, because the nodenum isn't inited at constructor time.
|
||||||
|
|
||||||
if (!manager.init())
|
if (!manager.init()) {
|
||||||
{
|
DEBUG_MSG("LoRa radio init failed\n");
|
||||||
DEBUG_MSG("LoRa radio init failed\n");
|
DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n");
|
||||||
DEBUG_MSG("Uncomment '#define SERIAL_DEBUG' in RH_RF95.cpp for detailed debug info\n");
|
return false;
|
||||||
return false;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// not needed - defaults on
|
// not needed - defaults on
|
||||||
// rf95.setPayloadCRC(true);
|
// rf95.setPayloadCRC(true);
|
||||||
|
|
||||||
reloadConfig();
|
reloadConfig();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** hash a string into an integer
|
||||||
|
*
|
||||||
|
* djb2 by Dan Bernstein.
|
||||||
|
* http://www.cse.yorku.ca/~oz/hash.html
|
||||||
|
*/
|
||||||
|
unsigned long hash(char *str)
|
||||||
|
{
|
||||||
|
unsigned long hash = 5381;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
while ((c = *str++) != 0)
|
||||||
|
hash = ((hash << 5) + hash) + (unsigned char)c; /* hash * 33 + c */
|
||||||
|
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MeshRadio::reloadConfig()
|
void MeshRadio::reloadConfig()
|
||||||
{
|
{
|
||||||
rf95.setModeIdle(); // Need to be idle before doing init
|
rf95.setModeIdle(); // Need to be idle before doing init
|
||||||
|
|
||||||
// Set up default configuration
|
// Set up default configuration
|
||||||
// No Sync Words in LORA mode.
|
// No Sync Words in LORA mode.
|
||||||
rf95.setModemConfig((RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default
|
rf95.setModemConfig(
|
||||||
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
|
(RH_RF95::ModemConfigChoice)channelSettings.modem_config); // Radio default
|
||||||
// rf95.setPreambleLength(8); // Default is 8
|
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
|
||||||
|
// rf95.setPreambleLength(8); // Default is 8
|
||||||
|
|
||||||
assert(channelSettings.channel_num < NUM_CHANNELS); // If the phone tries to tell us to use an illegal channel then panic
|
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
||||||
|
int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
|
||||||
|
float center_freq = CH0 + CH_SPACING * channel_num;
|
||||||
|
if (!rf95.setFrequency(center_freq)) {
|
||||||
|
DEBUG_MSG("setFrequency failed\n");
|
||||||
|
assert(0); // fixme panic
|
||||||
|
}
|
||||||
|
|
||||||
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
|
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
|
||||||
float center_freq = CH0 + CH_SPACING * channelSettings.channel_num;
|
|
||||||
if (!rf95.setFrequency(center_freq))
|
|
||||||
{
|
|
||||||
DEBUG_MSG("setFrequency failed\n");
|
|
||||||
assert(0); // fixme panic
|
|
||||||
}
|
|
||||||
|
|
||||||
// Defaults after init are 434.0MHz, 13dBm, Bw = 125 kHz, Cr = 4/5, Sf = 128chips/symbol, CRC on
|
// The default transmitter power is 13dBm, using PA_BOOST.
|
||||||
|
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
|
||||||
|
// you can set transmitter powers from 5 to 23 dBm:
|
||||||
|
// FIXME - can we do this? It seems to be in the Heltec board.
|
||||||
|
rf95.setTxPower(channelSettings.tx_power, false);
|
||||||
|
|
||||||
// The default transmitter power is 13dBm, using PA_BOOST.
|
DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config,
|
||||||
// If you are using RFM95/96/97/98 modules which uses the PA_BOOST transmitter pin, then
|
channel_num, channelSettings.tx_power);
|
||||||
// you can set transmitter powers from 5 to 23 dBm:
|
|
||||||
// FIXME - can we do this? It seems to be in the Heltec board.
|
|
||||||
rf95.setTxPower(channelSettings.tx_power, false);
|
|
||||||
|
|
||||||
DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channelSettings.channel_num, channelSettings.tx_power);
|
// Done with init tell radio to start receiving
|
||||||
|
rf95.setModeRx();
|
||||||
// Done with init tell radio to start receiving
|
|
||||||
rf95.setModeRx();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode MeshRadio::send(MeshPacket *p)
|
ErrorCode MeshRadio::send(MeshPacket *p)
|
||||||
{
|
{
|
||||||
if (useHardware)
|
lastTxStart = millis();
|
||||||
return rf95.send(p);
|
|
||||||
else
|
if (useHardware)
|
||||||
{
|
return rf95.send(p);
|
||||||
rf95.pool.release(p);
|
else {
|
||||||
return ERRNO_OK;
|
rf95.pool.release(p);
|
||||||
}
|
return ERRNO_OK;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define TX_WATCHDOG_TIMEOUT 30 * 1000
|
||||||
|
|
||||||
void MeshRadio::loop()
|
void MeshRadio::loop()
|
||||||
{
|
{
|
||||||
// Currently does nothing, since we do it all in ISRs now
|
// It should never take us more than 30 secs to send a packet, if it does, we have a bug
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (lastTxStart != 0 && (now - lastTxStart) > TX_WATCHDOG_TIMEOUT && rf95.mode() == RHGenericDriver::RHModeTx) {
|
||||||
|
DEBUG_MSG("ERROR! Bug! Tx packet took too long to send, forcing radio into rx mode");
|
||||||
|
rf95.setModeRx();
|
||||||
|
recordCriticalError(ErrTxWatchdog);
|
||||||
|
lastTxStart = 0; // Stop checking for now, because we just warned the developer
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,36 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CustomRF95.h"
|
#include "CustomRF95.h"
|
||||||
#include <RHMesh.h>
|
|
||||||
#include "MemoryPool.h"
|
#include "MemoryPool.h"
|
||||||
#include "mesh.pb.h"
|
|
||||||
#include "PointerQueue.h"
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
|
#include "PointerQueue.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
#include <RHMesh.h>
|
||||||
|
|
||||||
// US channel settings
|
// US channel settings
|
||||||
#define CH0_US 903.08f // MHz
|
#define CH0_US 903.08f // MHz
|
||||||
#define CH_SPACING_US 2.16f // MHz
|
#define CH_SPACING_US 2.16f // MHz
|
||||||
#define NUM_CHANNELS_US 13
|
#define NUM_CHANNELS_US 13
|
||||||
|
|
||||||
// EU433 channel settings
|
// EU433 channel settings
|
||||||
#define CH0_EU433 433.175f // MHz
|
#define CH0_EU433 433.175f // MHz
|
||||||
#define CH_SPACING_EU433 0.2f // MHz
|
#define CH_SPACING_EU433 0.2f // MHz
|
||||||
#define NUM_CHANNELS_EU433 8
|
#define NUM_CHANNELS_EU433 8
|
||||||
|
|
||||||
// EU865 channel settings
|
// EU865 channel settings
|
||||||
#define CH0_EU865 865.2f // MHz
|
#define CH0_EU865 865.2f // MHz
|
||||||
#define CH_SPACING_EU865 0.3f // MHz
|
#define CH_SPACING_EU865 0.3f // MHz
|
||||||
#define NUM_CHANNELS_EU865 10
|
#define NUM_CHANNELS_EU865 10
|
||||||
|
|
||||||
// CN channel settings
|
// CN channel settings
|
||||||
#define CH0_CN 470.0f // MHz
|
#define CH0_CN 470.0f // MHz
|
||||||
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
#define CH_SPACING_CN 2.0f // MHz FIXME, this is just a guess for 470-510
|
||||||
#define NUM_CHANNELS_CN 20
|
#define NUM_CHANNELS_CN 20
|
||||||
|
|
||||||
// JP channel settings
|
// JP channel settings
|
||||||
#define CH0_JP 920.0f // MHz
|
#define CH0_JP 920.0f // MHz
|
||||||
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
|
#define CH_SPACING_JP 0.5f // MHz FIXME, this is just a guess for 920-925
|
||||||
#define NUM_CHANNELS_JP 10
|
#define NUM_CHANNELS_JP 10
|
||||||
|
|
||||||
// FIXME add defs for other regions and use them here
|
// FIXME add defs for other regions and use them here
|
||||||
@@ -58,13 +58,14 @@
|
|||||||
#error "HW_VERSION not set"
|
#error "HW_VERSION not set"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
|
* A raw low level interface to our mesh. Only understands nodenums and bytes (not protobufs or node ids)
|
||||||
*/
|
*/
|
||||||
class MeshRadio {
|
class MeshRadio
|
||||||
public:
|
{
|
||||||
CustomRF95 rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing
|
public:
|
||||||
|
CustomRF95
|
||||||
|
rf95; // the raw radio interface - for now I'm leaving public - because this class is shrinking to be almost nothing
|
||||||
|
|
||||||
/** pool is the pool we will alloc our rx packets from
|
/** pool is the pool we will alloc our rx packets from
|
||||||
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
* rxDest is where we will send any rx packets, it becomes receivers responsibility to return packet to the pool
|
||||||
@@ -85,12 +86,12 @@ public:
|
|||||||
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
/// The radioConfig object just changed, call this to force the hw to change to the new settings
|
||||||
void reloadConfig();
|
void reloadConfig();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
// RHDatagram manager;
|
|
||||||
// RHReliableDatagram manager; // don't use mesh yet
|
// RHReliableDatagram manager; // don't use mesh yet
|
||||||
RHMesh manager;
|
RHMesh manager;
|
||||||
// MeshRXHandler rxHandler;
|
|
||||||
|
/// Used for the tx timer watchdog, to check for bugs in our transmit code, msec of last time we did a send
|
||||||
|
uint32_t lastTxStart = 0;
|
||||||
|
|
||||||
/// low level send, might block for mutiple seconds
|
/// low level send, might block for mutiple seconds
|
||||||
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
|
ErrorCode sendTo(NodeNum dest, const uint8_t *buf, size_t len);
|
||||||
@@ -98,5 +99,3 @@ private:
|
|||||||
/// enqueue a received packet in rxDest
|
/// enqueue a received packet in rxDest
|
||||||
void handleReceive(MeshPacket *p);
|
void handleReceive(MeshPacket *p);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,37 +2,41 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "mesh-pb-constants.h"
|
|
||||||
#include "MeshService.h"
|
|
||||||
#include "MeshBluetoothService.h"
|
|
||||||
#include "NodeDB.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "screen.h"
|
#include "MeshBluetoothService.h"
|
||||||
|
#include "MeshService.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "main.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
receivedPacketQueue - this is a queue of messages we've received from the mesh, which we are keeping to deliver to the phone.
|
||||||
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were alloced with new).
|
It is implemented with a FreeRTos queue (wrapped with a little RTQueue class) of pointers to MeshPacket protobufs (which were
|
||||||
After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move sent packets into a 'sentToPhone' queue
|
alloced with new). After a packet ptr is removed from the queue and processed it should be deleted. (eventually we should move
|
||||||
of packets we can delete just as soon as we are sure the phone has acked those packets - when the phone writes to FromNum)
|
sent packets into a 'sentToPhone' queue of packets we can delete just as soon as we are sure the phone has acked those packets -
|
||||||
|
when the phone writes to FromNum)
|
||||||
|
|
||||||
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes, arbitrating to select
|
mesh - an instance of Mesh class. Which manages the interface to the mesh radio library, reception of packets from other nodes,
|
||||||
a node number and keeping the current nodedb.
|
arbitrating to select a node number and keeping the current nodedb.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Broadcast when a newly powered mesh node wants to find a node num it can use
|
/* Broadcast when a newly powered mesh node wants to find a node num it can use
|
||||||
|
|
||||||
The algoritm is as follows:
|
The algoritm is as follows:
|
||||||
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so the new node can build its node db)
|
* when a node starts up, it broadcasts their user and the normal flow is for all other nodes to reply with their User as well (so
|
||||||
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that indicates a collision has occurred and the following steps should happen:
|
the new node can build its node db)
|
||||||
|
* If a node ever receives a User (not just the first broadcast) message where the sender node number equals our node number, that
|
||||||
|
indicates a collision has occurred and the following steps should happen:
|
||||||
|
|
||||||
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to keep its nodenum. We send a broadcast message
|
If the receiving node (that was already in the mesh)'s macaddr is LOWER than the new User who just tried to sign in: it gets to
|
||||||
of OUR User (we use a broadcast so that the other node can receive our message, considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
|
keep its nodenum. We send a broadcast message of OUR User (we use a broadcast so that the other node can receive our message,
|
||||||
|
considering we have the same id - it also serves to let observers correct their nodedb) - this case is rare so it should be okay.
|
||||||
|
|
||||||
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random nodenum (filtering against whatever it knows about the nodedb) and
|
If any node receives a User where the macaddr is GTE than their local macaddr, they have been vetoed and should pick a new random
|
||||||
rebroadcast their User.
|
nodenum (filtering against whatever it knows about the nodedb) and rebroadcast their User.
|
||||||
|
|
||||||
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
|
FIXME in the initial proof of concept we just skip the entire want/deny flow and just hand pick node numbers at first.
|
||||||
*/
|
*/
|
||||||
@@ -40,15 +44,15 @@ FIXME in the initial proof of concept we just skip the entire want/deny flow and
|
|||||||
MeshService service;
|
MeshService service;
|
||||||
|
|
||||||
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
|
// I think this is right, one packet for each of the three fifos + one packet being currently assembled for TX or RX
|
||||||
#define MAX_PACKETS (MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + 2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
#define MAX_PACKETS \
|
||||||
|
(MAX_RX_TOPHONE + MAX_RX_FROMRADIO + MAX_TX_QUEUE + \
|
||||||
|
2) // max number of packets which can be in flight (either queued from reception or queued for sending)
|
||||||
|
|
||||||
#define MAX_RX_FROMRADIO 4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
|
#define MAX_RX_FROMRADIO \
|
||||||
|
4 // max number of packets destined to our queue, we dispatch packets quickly so it doesn't need to be big
|
||||||
|
|
||||||
MeshService::MeshService()
|
MeshService::MeshService()
|
||||||
: packetPool(MAX_PACKETS),
|
: packetPool(MAX_PACKETS), toPhoneQueue(MAX_RX_TOPHONE), fromRadioQueue(MAX_RX_FROMRADIO), fromNum(0),
|
||||||
toPhoneQueue(MAX_RX_TOPHONE),
|
|
||||||
fromRadioQueue(MAX_RX_FROMRADIO),
|
|
||||||
fromNum(0),
|
|
||||||
radio(packetPool, fromRadioQueue)
|
radio(packetPool, fromRadioQueue)
|
||||||
{
|
{
|
||||||
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
|
// assert(MAX_RX_TOPHONE == 32); // FIXME, delete this, just checking my clever macro
|
||||||
@@ -88,29 +92,25 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
|
|||||||
// we win if we have a lower macaddr
|
// we win if we have a lower macaddr
|
||||||
bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0;
|
bool weWin = memcmp(&owner.macaddr, &mp->payload.variant.user.macaddr, sizeof(owner.macaddr)) < 0;
|
||||||
|
|
||||||
if (isCollision)
|
if (isCollision) {
|
||||||
{
|
if (weWin) {
|
||||||
if (weWin)
|
|
||||||
{
|
|
||||||
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
|
DEBUG_MSG("NOTE! Received a nodenum collision and we are vetoing\n");
|
||||||
|
|
||||||
packetPool.release(mp); // discard it
|
packetPool.release(mp); // discard it
|
||||||
mp = NULL;
|
mp = NULL;
|
||||||
|
|
||||||
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
|
sendOurOwner(); // send our owner as a _broadcast_ because that other guy is mistakenly using our nodenum
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// we lost, we need to try for a new nodenum!
|
// we lost, we need to try for a new nodenum!
|
||||||
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
|
DEBUG_MSG("NOTE! Received a nodenum collision we lost, so picking a new nodenum\n");
|
||||||
nodeDB.updateFrom(*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
|
nodeDB.updateFrom(
|
||||||
|
*mp); // update the DB early - before trying to repick (so we don't select the same node number again)
|
||||||
nodeDB.pickNewNodeNum();
|
nodeDB.pickNewNodeNum();
|
||||||
sendOurOwner(); // broadcast our new attempt at a node number
|
sendOurOwner(); // broadcast our new attempt at a node number
|
||||||
}
|
}
|
||||||
}
|
} else if (wasBroadcast) {
|
||||||
else if (wasBroadcast)
|
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can
|
||||||
{
|
// build their DB
|
||||||
// If we haven't yet abandoned the packet and it was a broadcast, reply (just to them) with our User record so they can build their DB
|
|
||||||
|
|
||||||
// Someone just sent us a User, reply with our Owner
|
// Someone just sent us a User, reply with our Owner
|
||||||
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
|
DEBUG_MSG("Received broadcast Owner from 0x%x, replying with our owner\n", mp->from);
|
||||||
@@ -118,7 +118,7 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
|
|||||||
sendOurOwner(mp->from);
|
sendOurOwner(mp->from);
|
||||||
|
|
||||||
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
|
String lcd = String("Joined: ") + mp->payload.variant.user.long_name + "\n";
|
||||||
screen_print(lcd.c_str());
|
screen.print(lcd.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
return mp;
|
return mp;
|
||||||
@@ -126,12 +126,10 @@ MeshPacket *MeshService::handleFromRadioUser(MeshPacket *mp)
|
|||||||
|
|
||||||
void MeshService::handleIncomingPosition(MeshPacket *mp)
|
void MeshService::handleIncomingPosition(MeshPacket *mp)
|
||||||
{
|
{
|
||||||
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag)
|
if (mp->has_payload && mp->payload.which_variant == SubPacket_position_tag) {
|
||||||
{
|
|
||||||
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time);
|
DEBUG_MSG("handled incoming position time=%u\n", mp->payload.variant.position.time);
|
||||||
|
|
||||||
if (mp->payload.variant.position.time)
|
if (mp->payload.variant.position.time) {
|
||||||
{
|
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
uint32_t secs = mp->payload.variant.position.time;
|
uint32_t secs = mp->payload.variant.position.time;
|
||||||
|
|
||||||
@@ -153,32 +151,28 @@ void MeshService::handleFromRadio(MeshPacket *mp)
|
|||||||
if (!myNodeInfo.has_gps)
|
if (!myNodeInfo.has_gps)
|
||||||
handleIncomingPosition(mp);
|
handleIncomingPosition(mp);
|
||||||
|
|
||||||
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag)
|
if (mp->has_payload && mp->payload.which_variant == SubPacket_user_tag) {
|
||||||
{
|
|
||||||
mp = handleFromRadioUser(mp);
|
mp = handleFromRadioUser(mp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
|
// If we veto a received User packet, we don't put it into the DB or forward it to the phone (to prevent confusing it)
|
||||||
if (mp)
|
if (mp) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time);
|
DEBUG_MSG("Forwarding to phone, from=0x%x, rx_time=%u\n", mp->from, mp->rx_time);
|
||||||
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
nodeDB.updateFrom(*mp); // update our DB state based off sniffing every RX packet from the radio
|
||||||
|
|
||||||
fromNum++;
|
fromNum++;
|
||||||
|
|
||||||
if (toPhoneQueue.numFree() == 0)
|
if (toPhoneQueue.numFree() == 0) {
|
||||||
{
|
|
||||||
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
|
DEBUG_MSG("NOTE: tophone queue is full, discarding oldest\n");
|
||||||
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
|
MeshPacket *d = toPhoneQueue.dequeuePtr(0);
|
||||||
if (d)
|
if (d)
|
||||||
releaseToPool(d);
|
releaseToPool(d);
|
||||||
}
|
}
|
||||||
assert(toPhoneQueue.enqueue(mp, 0) == pdTRUE); // FIXME, instead of failing for full queue, delete the oldest mssages
|
assert(toPhoneQueue.enqueue(mp, 0)); // FIXME, instead of failing for full queue, delete the oldest mssages
|
||||||
|
|
||||||
if (mp->payload.want_response)
|
if (mp->payload.want_response)
|
||||||
sendNetworkPing(mp->from);
|
sendNetworkPing(mp->from);
|
||||||
}
|
} else
|
||||||
else
|
|
||||||
DEBUG_MSG("Dropping vetoed User message\n");
|
DEBUG_MSG("Dropping vetoed User message\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,8 +180,7 @@ void MeshService::handleFromRadio()
|
|||||||
{
|
{
|
||||||
MeshPacket *mp;
|
MeshPacket *mp;
|
||||||
uint32_t oldFromNum = fromNum;
|
uint32_t oldFromNum = fromNum;
|
||||||
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL)
|
while ((mp = fromRadioQueue.dequeuePtr(0)) != NULL) {
|
||||||
{
|
|
||||||
handleFromRadio(mp);
|
handleFromRadio(mp);
|
||||||
}
|
}
|
||||||
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
|
if (oldFromNum != fromNum) // We don't want to generate extra notifies for multiple new packets
|
||||||
@@ -227,23 +220,20 @@ void MeshService::handleToRadio(std::string s)
|
|||||||
{
|
{
|
||||||
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
|
static ToRadio r; // this is a static scratch object, any data must be copied elsewhere before returning
|
||||||
|
|
||||||
if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r))
|
if (pb_decode_from_bytes((const uint8_t *)s.c_str(), s.length(), ToRadio_fields, &r)) {
|
||||||
{
|
switch (r.which_variant) {
|
||||||
switch (r.which_variant)
|
case ToRadio_packet_tag: {
|
||||||
{
|
|
||||||
case ToRadio_packet_tag:
|
|
||||||
{
|
|
||||||
// If our phone is sending a position, see if we can use it to set our RTC
|
// If our phone is sending a position, see if we can use it to set our RTC
|
||||||
handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock
|
handleIncomingPosition(&r.variant.packet); // If it is a position packet, perhaps set our clock
|
||||||
|
|
||||||
r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our nodedb for the local node)
|
r.variant.packet.rx_time = gps.getValidTime(); // Record the time the packet arrived from the phone (so we update our
|
||||||
|
// nodedb for the local node)
|
||||||
|
|
||||||
// Send the packet into the mesh
|
// Send the packet into the mesh
|
||||||
sendToMesh(packetPool.allocCopy(r.variant.packet));
|
sendToMesh(packetPool.allocCopy(r.variant.packet));
|
||||||
|
|
||||||
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
|
bool loopback = false; // if true send any packet the phone sends back itself (for testing)
|
||||||
if (loopback)
|
if (loopback) {
|
||||||
{
|
|
||||||
MeshPacket *mp = packetPool.allocCopy(r.variant.packet);
|
MeshPacket *mp = packetPool.allocCopy(r.variant.packet);
|
||||||
handleFromRadio(mp);
|
handleFromRadio(mp);
|
||||||
bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived
|
bluetoothNotifyFromNum(fromNum); // tell the phone a new packet arrived
|
||||||
@@ -254,6 +244,8 @@ void MeshService::handleToRadio(std::string s)
|
|||||||
DEBUG_MSG("Error: unexpected ToRadio variant\n");
|
DEBUG_MSG("Error: unexpected ToRadio variant\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("Error: ignoring malformed toradio\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,10 +253,10 @@ void MeshService::sendToMesh(MeshPacket *p)
|
|||||||
{
|
{
|
||||||
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
nodeDB.updateFrom(*p); // update our local DB for this packet (because phone might have sent position packets etc...)
|
||||||
|
|
||||||
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other nodes shouldn't trust it anyways)
|
// Strip out any time information before sending packets to other nodes - to keep the wire size small (and because other
|
||||||
// Note: for now, we allow a device with a local GPS to include the time, so that gpsless devices can get time.
|
// nodes shouldn't trust it anyways) Note: for now, we allow a device with a local GPS to include the time, so that gpsless
|
||||||
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag)
|
// devices can get time.
|
||||||
{
|
if (p->has_payload && p->payload.which_variant == SubPacket_position_tag) {
|
||||||
if (!myNodeInfo.has_gps)
|
if (!myNodeInfo.has_gps)
|
||||||
p->payload.variant.position.time = 0;
|
p->payload.variant.position.time = 0;
|
||||||
else
|
else
|
||||||
@@ -274,8 +266,7 @@ void MeshService::sendToMesh(MeshPacket *p)
|
|||||||
// If the phone sent a packet just to us, don't send it out into the network
|
// If the phone sent a packet just to us, don't send it out into the network
|
||||||
if (p->to == nodeDB.getNodeNum())
|
if (p->to == nodeDB.getNodeNum())
|
||||||
DEBUG_MSG("Dropping locally processed message\n");
|
DEBUG_MSG("Dropping locally processed message\n");
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
// Note: We might return !OK if our fifo was full, at that point the only option we have is to drop it
|
||||||
if (radio.send(p) != ERRNO_OK)
|
if (radio.send(p) != ERRNO_OK)
|
||||||
DEBUG_MSG("Dropped packet because send queue was full!\n");
|
DEBUG_MSG("Dropped packet because send queue was full!\n");
|
||||||
@@ -316,7 +307,8 @@ void MeshService::sendOurPosition(NodeNum dest)
|
|||||||
p->to = dest;
|
p->to = dest;
|
||||||
p->payload.which_variant = SubPacket_position_tag;
|
p->payload.which_variant = SubPacket_position_tag;
|
||||||
p->payload.variant.position = node->position;
|
p->payload.variant.position = node->position;
|
||||||
p->payload.variant.position.time = gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
p->payload.variant.position.time =
|
||||||
|
gps.getValidTime(); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||||
sendToMesh(p);
|
sendToMesh(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,27 +317,26 @@ void MeshService::onGPSChanged()
|
|||||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||||
MeshPacket *p = allocForSending();
|
MeshPacket *p = allocForSending();
|
||||||
p->payload.which_variant = SubPacket_position_tag;
|
p->payload.which_variant = SubPacket_position_tag;
|
||||||
|
|
||||||
Position &pos = p->payload.variant.position;
|
Position &pos = p->payload.variant.position;
|
||||||
#if 0
|
// !zero or !zero lat/long means valid
|
||||||
if (gps.altitude.isValid())
|
if (gps.latitude != 0 || gps.longitude != 0) {
|
||||||
pos.altitude = gps.altitude.meters();
|
if (gps.altitude != 0)
|
||||||
pos.latitude = gps.location.lat();
|
pos.altitude = gps.altitude;
|
||||||
pos.longitude = gps.location.lng();
|
pos.latitude = gps.latitude;
|
||||||
pos.time = gps.getValidTime();
|
pos.longitude = gps.longitude;
|
||||||
#endif
|
pos.time = gps.getValidTime();
|
||||||
|
}
|
||||||
|
|
||||||
// We limit our GPS broadcasts to a max rate
|
// We limit our GPS broadcasts to a max rate
|
||||||
static uint32_t lastGpsSend;
|
static uint32_t lastGpsSend;
|
||||||
uint32_t now = millis();
|
uint32_t now = millis();
|
||||||
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000)
|
if (lastGpsSend == 0 || now - lastGpsSend > radioConfig.preferences.position_broadcast_secs * 1000) {
|
||||||
{
|
|
||||||
lastGpsSend = now;
|
lastGpsSend = now;
|
||||||
DEBUG_MSG("Sending position to mesh\n");
|
DEBUG_MSG("Sending position to mesh\n");
|
||||||
|
|
||||||
sendToMesh(p);
|
sendToMesh(p);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state
|
// We don't need to send this packet to anyone else, but it still serves as a nice uniform way to update our local state
|
||||||
nodeDB.updateFrom(*p);
|
nodeDB.updateFrom(*p);
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,17 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "mesh.pb.h"
|
|
||||||
#include "MeshRadio.h"
|
|
||||||
#include "PointerQueue.h"
|
|
||||||
#include "MemoryPool.h"
|
#include "MemoryPool.h"
|
||||||
|
#include "MeshRadio.h"
|
||||||
#include "Observer.h"
|
#include "Observer.h"
|
||||||
|
#include "PointerQueue.h"
|
||||||
|
#include "mesh.pb.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
|
* Top level app for this service. keeps the mesh, the radio config and the queue of received packets.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class MeshService: private Observer
|
class MeshService : private Observer
|
||||||
{
|
{
|
||||||
MemoryPool<MeshPacket> packetPool;
|
MemoryPool<MeshPacket> packetPool;
|
||||||
|
|
||||||
@@ -30,8 +30,7 @@ class MeshService: private Observer
|
|||||||
/// The current nonce for the newest packet which has been queued for the phone
|
/// The current nonce for the newest packet which has been queued for the phone
|
||||||
uint32_t fromNum;
|
uint32_t fromNum;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
MeshRadio radio;
|
MeshRadio radio;
|
||||||
|
|
||||||
MeshService();
|
MeshService();
|
||||||
@@ -60,17 +59,20 @@ public:
|
|||||||
/// Allocate and return a meshpacket which defaults as send to broadcast from the current node.
|
/// Allocate and return a meshpacket which defaults as send to broadcast from the current node.
|
||||||
MeshPacket *allocForSending();
|
MeshPacket *allocForSending();
|
||||||
|
|
||||||
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least sends our owner
|
/// Called when the user wakes up our GUI, normally sends our latest location to the mesh (if we have it), otherwise at least
|
||||||
|
/// sends our owner
|
||||||
void sendNetworkPing(NodeNum dest = NODENUM_BROADCAST);
|
void sendNetworkPing(NodeNum dest = NODENUM_BROADCAST);
|
||||||
|
|
||||||
/// Send our owner info to a particular node
|
/// Send our owner info to a particular node
|
||||||
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST);
|
void sendOurOwner(NodeNum dest = NODENUM_BROADCAST);
|
||||||
private:
|
|
||||||
|
private:
|
||||||
/// Broadcasts our last known position
|
/// Broadcasts our last known position
|
||||||
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST);
|
void sendOurPosition(NodeNum dest = NODENUM_BROADCAST);
|
||||||
|
|
||||||
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after sending.
|
/// Send a packet into the mesh - note p must have been allocated from packetPool. We will return it to that pool after
|
||||||
/// This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb cache
|
/// sending. This is the ONLY function you should use for sending messages into the mesh, because it also updates the nodedb
|
||||||
|
/// cache
|
||||||
void sendToMesh(MeshPacket *p);
|
void sendToMesh(MeshPacket *p);
|
||||||
|
|
||||||
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
||||||
@@ -92,4 +94,3 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern MeshService service;
|
extern MeshService service;
|
||||||
|
|
||||||
|
|||||||
104
src/NodeDB.cpp
104
src/NodeDB.cpp
@@ -5,13 +5,14 @@
|
|||||||
#include "FS.h"
|
#include "FS.h"
|
||||||
#include "SPIFFS.h"
|
#include "SPIFFS.h"
|
||||||
|
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <pb_decode.h>
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "mesh-pb-constants.h"
|
|
||||||
#include "NodeDB.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
#include "PowerFSM.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "error.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
|
||||||
NodeDB nodeDB;
|
NodeDB nodeDB;
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ DeviceState versions used to be defined in the .proto file but really only this
|
|||||||
#define here.
|
#define here.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define DEVICESTATE_CUR_VER 2
|
#define DEVICESTATE_CUR_VER 6
|
||||||
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
||||||
|
|
||||||
#define FS SPIFFS
|
#define FS SPIFFS
|
||||||
@@ -41,9 +42,7 @@ User &owner = devicestate.owner;
|
|||||||
|
|
||||||
static uint8_t ourMacAddr[6];
|
static uint8_t ourMacAddr[6];
|
||||||
|
|
||||||
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count)
|
NodeDB::NodeDB() : nodes(devicestate.node_db), numNodes(&devicestate.node_db_count) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void NodeDB::init()
|
void NodeDB::init()
|
||||||
{
|
{
|
||||||
@@ -52,7 +51,7 @@ void NodeDB::init()
|
|||||||
devicestate.has_my_node = true;
|
devicestate.has_my_node = true;
|
||||||
devicestate.has_radio = true;
|
devicestate.has_radio = true;
|
||||||
devicestate.has_owner = true;
|
devicestate.has_owner = true;
|
||||||
devicestate.has_radio = true;
|
devicestate.has_radio = false;
|
||||||
devicestate.radio.has_channel_settings = true;
|
devicestate.radio.has_channel_settings = true;
|
||||||
devicestate.radio.has_preferences = true;
|
devicestate.radio.has_preferences = true;
|
||||||
devicestate.node_db_count = 0;
|
devicestate.node_db_count = 0;
|
||||||
@@ -78,8 +77,8 @@ void NodeDB::init()
|
|||||||
|
|
||||||
// Init our blank owner info to reasonable defaults
|
// Init our blank owner info to reasonable defaults
|
||||||
esp_efuse_mac_get_default(ourMacAddr);
|
esp_efuse_mac_get_default(ourMacAddr);
|
||||||
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0],
|
sprintf(owner.id, "!%02x%02x%02x%02x%02x%02x", ourMacAddr[0], ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4],
|
||||||
ourMacAddr[1], ourMacAddr[2], ourMacAddr[3], ourMacAddr[4], ourMacAddr[5]);
|
ourMacAddr[5]);
|
||||||
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
|
memcpy(owner.macaddr, ourMacAddr, sizeof(owner.macaddr));
|
||||||
|
|
||||||
// make each node start with ad different random seed (but okay that the sequence is the same each boot)
|
// make each node start with ad different random seed (but okay that the sequence is the same each boot)
|
||||||
@@ -122,8 +121,7 @@ void NodeDB::pickNewNodeNum()
|
|||||||
r = NUM_RESERVED; // don't pick a reserved node number
|
r = NUM_RESERVED; // don't pick a reserved node number
|
||||||
|
|
||||||
NodeInfo *found;
|
NodeInfo *found;
|
||||||
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr)))
|
while ((found = getNode(r)) && memcmp(found->user.macaddr, owner.macaddr, sizeof(owner.macaddr))) {
|
||||||
{
|
|
||||||
NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice
|
NodeNum n = random(NUM_RESERVED, NODENUM_BROADCAST); // try a new random choice
|
||||||
DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n);
|
DEBUG_MSG("NOTE! Our desired nodenum 0x%x is in use, so trying for 0x%x\n", r, n);
|
||||||
r = n;
|
r = n;
|
||||||
@@ -140,35 +138,29 @@ void NodeDB::loadFromDisk()
|
|||||||
static DeviceState scratch;
|
static DeviceState scratch;
|
||||||
|
|
||||||
File f = FS.open(preffile);
|
File f = FS.open(preffile);
|
||||||
if (f)
|
if (f) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Loading saved preferences\n");
|
DEBUG_MSG("Loading saved preferences\n");
|
||||||
pb_istream_t stream = {&readcb, &f, DeviceState_size};
|
pb_istream_t stream = {&readcb, &f, DeviceState_size};
|
||||||
|
|
||||||
//DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
|
// DEBUG_MSG("Preload channel name=%s\n", channelSettings.name);
|
||||||
|
|
||||||
memset(&scratch, 0, sizeof(scratch));
|
memset(&scratch, 0, sizeof(scratch));
|
||||||
if (!pb_decode(&stream, DeviceState_fields, &scratch))
|
if (!pb_decode(&stream, DeviceState_fields, &scratch)) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
||||||
// FIXME - report failure to phone
|
// FIXME - report failure to phone
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
if (scratch.version < DEVICESTATE_MIN_VER)
|
if (scratch.version < DEVICESTATE_MIN_VER)
|
||||||
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
DEBUG_MSG("Warn: devicestate is old, discarding\n");
|
||||||
else
|
else {
|
||||||
{
|
DEBUG_MSG("Loaded saved preferences version %d\n", scratch.version);
|
||||||
devicestate = scratch;
|
devicestate = scratch;
|
||||||
}
|
}
|
||||||
|
|
||||||
//DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
|
// DEBUG_MSG("Postload channel name=%s\n", channelSettings.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
f.close();
|
f.close();
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
DEBUG_MSG("No saved preferences found\n");
|
DEBUG_MSG("No saved preferences found\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,17 +168,15 @@ void NodeDB::loadFromDisk()
|
|||||||
void NodeDB::saveToDisk()
|
void NodeDB::saveToDisk()
|
||||||
{
|
{
|
||||||
File f = FS.open(preftmp, "w");
|
File f = FS.open(preftmp, "w");
|
||||||
if (f)
|
if (f) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Writing preferences\n");
|
DEBUG_MSG("Writing preferences\n");
|
||||||
|
|
||||||
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
pb_ostream_t stream = {&writecb, &f, SIZE_MAX, 0};
|
||||||
|
|
||||||
//DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
// DEBUG_MSG("Presave channel name=%s\n", channelSettings.name);
|
||||||
|
|
||||||
devicestate.version = DEVICESTATE_CUR_VER;
|
devicestate.version = DEVICESTATE_CUR_VER;
|
||||||
if (!pb_encode(&stream, DeviceState_fields, &devicestate))
|
if (!pb_encode(&stream, DeviceState_fields, &devicestate)) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
DEBUG_MSG("Error: can't write protobuf %s\n", PB_GET_ERROR(&stream));
|
||||||
// FIXME - report failure to phone
|
// FIXME - report failure to phone
|
||||||
}
|
}
|
||||||
@@ -198,9 +188,7 @@ void NodeDB::saveToDisk()
|
|||||||
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
DEBUG_MSG("Warning: Can't remove old pref file\n");
|
||||||
if (!FS.rename(preftmp, preffile))
|
if (!FS.rename(preftmp, preffile))
|
||||||
DEBUG_MSG("Error: can't rename new pref file\n");
|
DEBUG_MSG("Error: can't rename new pref file\n");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
DEBUG_MSG("ERROR: can't write prefs\n"); // FIXME report to app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,8 +232,7 @@ size_t NodeDB::getNumOnlineNodes()
|
|||||||
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
/// we updateGUI and updateGUIforNode if we think our this change is big enough for a redraw
|
||||||
void NodeDB::updateFrom(const MeshPacket &mp)
|
void NodeDB::updateFrom(const MeshPacket &mp)
|
||||||
{
|
{
|
||||||
if (mp.has_payload)
|
if (mp.has_payload) {
|
||||||
{
|
|
||||||
const SubPacket &p = mp.payload;
|
const SubPacket &p = mp.payload;
|
||||||
DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time);
|
DEBUG_MSG("Update DB node 0x%x for variant %d, rx_time=%u\n", mp.from, p.which_variant, mp.rx_time);
|
||||||
|
|
||||||
@@ -255,16 +242,13 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
if (oldNumNodes != *numNodes)
|
if (oldNumNodes != *numNodes)
|
||||||
updateGUI = true; // we just created a nodeinfo
|
updateGUI = true; // we just created a nodeinfo
|
||||||
|
|
||||||
if (mp.rx_time)
|
if (mp.rx_time) { // if the packet has a valid timestamp use it to update our last_seen
|
||||||
{ // if the packet has a valid timestamp use it to update our last_seen
|
|
||||||
info->has_position = true; // at least the time is valid
|
info->has_position = true; // at least the time is valid
|
||||||
info->position.time = mp.rx_time;
|
info->position.time = mp.rx_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (p.which_variant)
|
switch (p.which_variant) {
|
||||||
{
|
case SubPacket_position_tag: {
|
||||||
case SubPacket_position_tag:
|
|
||||||
{
|
|
||||||
// we carefully preserve the old time, because we always trust our local timestamps more
|
// we carefully preserve the old time, because we always trust our local timestamps more
|
||||||
uint32_t oldtime = info->position.time;
|
uint32_t oldtime = info->position.time;
|
||||||
info->position = p.variant.position;
|
info->position = p.variant.position;
|
||||||
@@ -274,14 +258,12 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SubPacket_data_tag:
|
case SubPacket_data_tag: {
|
||||||
{
|
|
||||||
// Keep a copy of the most recent text message.
|
// Keep a copy of the most recent text message.
|
||||||
if (p.variant.data.typ == Data_Type_CLEAR_TEXT)
|
if (p.variant.data.typ == Data_Type_CLEAR_TEXT) {
|
||||||
{
|
DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size,
|
||||||
DEBUG_MSG("Received text msg from=0%0x, msg=%.*s\n", mp.from, p.variant.data.payload.size, p.variant.data.payload.bytes);
|
p.variant.data.payload.bytes);
|
||||||
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum())
|
if (mp.to == NODENUM_BROADCAST || mp.to == nodeDB.getNodeNum()) {
|
||||||
{
|
|
||||||
// We only store/display messages destined for us.
|
// We only store/display messages destined for us.
|
||||||
devicestate.rx_text_message = mp;
|
devicestate.rx_text_message = mp;
|
||||||
devicestate.has_rx_text_message = true;
|
devicestate.has_rx_text_message = true;
|
||||||
@@ -292,18 +274,17 @@ void NodeDB::updateFrom(const MeshPacket &mp)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case SubPacket_user_tag:
|
case SubPacket_user_tag: {
|
||||||
{
|
|
||||||
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
|
DEBUG_MSG("old user %s/%s/%s\n", info->user.id, info->user.long_name, info->user.short_name);
|
||||||
|
|
||||||
bool changed = memcmp(&info->user, &p.variant.user, sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
|
bool changed = memcmp(&info->user, &p.variant.user,
|
||||||
|
sizeof(info->user)); // Both of these blocks start as filled with zero so I think this is okay
|
||||||
|
|
||||||
info->user = p.variant.user;
|
info->user = p.variant.user;
|
||||||
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
|
DEBUG_MSG("updating changed=%d user %s/%s/%s\n", changed, info->user.id, info->user.long_name, info->user.short_name);
|
||||||
info->has_user = true;
|
info->has_user = true;
|
||||||
|
|
||||||
if (changed)
|
if (changed) {
|
||||||
{
|
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
powerFSM.trigger(EVENT_NODEDB_UPDATED);
|
||||||
|
|
||||||
@@ -336,8 +317,7 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
|
|||||||
{
|
{
|
||||||
NodeInfo *info = getNode(n);
|
NodeInfo *info = getNode(n);
|
||||||
|
|
||||||
if (!info)
|
if (!info) {
|
||||||
{
|
|
||||||
// add the node
|
// add the node
|
||||||
assert(*numNodes < MAX_NUM_NODES);
|
assert(*numNodes < MAX_NUM_NODES);
|
||||||
info = &nodes[(*numNodes)++];
|
info = &nodes[(*numNodes)++];
|
||||||
@@ -349,3 +329,11 @@ NodeInfo *NodeDB::getOrCreateNode(NodeNum n)
|
|||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Record an error that should be reported via analytics
|
||||||
|
void recordCriticalError(CriticalErrorCode code, uint32_t address)
|
||||||
|
{
|
||||||
|
myNodeInfo.error_code = code;
|
||||||
|
myNodeInfo.error_address = address;
|
||||||
|
myNodeInfo.error_count++;
|
||||||
|
}
|
||||||
|
|||||||
27
src/NodeDB.h
27
src/NodeDB.h
@@ -3,8 +3,8 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include "mesh-pb-constants.h"
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
|
||||||
extern DeviceState devicestate;
|
extern DeviceState devicestate;
|
||||||
extern MyNodeInfo &myNodeInfo;
|
extern MyNodeInfo &myNodeInfo;
|
||||||
@@ -28,10 +28,10 @@ class NodeDB
|
|||||||
|
|
||||||
int readPointer = 0;
|
int readPointer = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
bool updateGUI = false; // we think the gui should definitely be redrawn, screen will clear this once handled
|
||||||
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
NodeInfo *updateGUIforNode = NULL; // if currently showing this node, we think you should update the GUI
|
||||||
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
bool updateTextMessage = false; // if true, the GUI should show a new text message
|
||||||
|
|
||||||
/// don't do mesh based algoritm for node id assignment (initially)
|
/// don't do mesh based algoritm for node id assignment (initially)
|
||||||
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
/// instead just store in flash - possibly even in the initial alpha release do this hack
|
||||||
@@ -56,10 +56,11 @@ public:
|
|||||||
// bool handleWantNodeNum(NodeNum n);
|
// bool handleWantNodeNum(NodeNum n);
|
||||||
|
|
||||||
/* void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
|
/* void handleDenyNodeNum(NodeNum FIXME read mesh proto docs, perhaps picking a random node num is not a great idea
|
||||||
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message. the
|
and instead we should use a special 'im unconfigured node number' and include our desired node number in the wantnum message.
|
||||||
unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we randomly select
|
the unconfigured node num would only be used while initially joining the mesh so low odds of conflicting (especially if we
|
||||||
from a small number of nodenums which can be used temporarily for this operation). figure out what the lower level
|
randomly select from a small number of nodenums which can be used temporarily for this operation). figure out what the lower
|
||||||
mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast their denial?)
|
level mesh sw does if it does conflict? would it be better for people who are replying with denynode num to just broadcast
|
||||||
|
their denial?)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/// Called from bluetooth when the user wants to start reading the node DB from scratch.
|
/// Called from bluetooth when the user wants to start reading the node DB from scratch.
|
||||||
@@ -74,13 +75,16 @@ public:
|
|||||||
/// Find a node in our DB, return null for missing
|
/// Find a node in our DB, return null for missing
|
||||||
NodeInfo *getNode(NodeNum n);
|
NodeInfo *getNode(NodeNum n);
|
||||||
|
|
||||||
NodeInfo *getNodeByIndex(size_t x) { assert(x < *numNodes); return &nodes[x]; }
|
NodeInfo *getNodeByIndex(size_t x)
|
||||||
|
{
|
||||||
|
assert(x < *numNodes);
|
||||||
|
return &nodes[x];
|
||||||
|
}
|
||||||
|
|
||||||
/// Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
/// Return the number of nodes we've heard from recently (within the last 2 hrs?)
|
||||||
size_t getNumOnlineNodes();
|
size_t getNumOnlineNodes();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/// Find a node in our DB, create an empty NodeInfo if missing
|
/// Find a node in our DB, create an empty NodeInfo if missing
|
||||||
NodeInfo *getOrCreateNode(NodeNum n);
|
NodeInfo *getOrCreateNode(NodeNum n);
|
||||||
|
|
||||||
@@ -89,4 +93,3 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
extern NodeDB nodeDB;
|
extern NodeDB nodeDB;
|
||||||
|
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ class Observer
|
|||||||
{
|
{
|
||||||
Observable *observed;
|
Observable *observed;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Observer() : observed(NULL) {}
|
Observer() : observed(NULL) {}
|
||||||
|
|
||||||
virtual ~Observer();
|
virtual ~Observer();
|
||||||
|
|
||||||
void observe(Observable *o);
|
void observe(Observable *o);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Observable;
|
friend class Observable;
|
||||||
|
|
||||||
virtual void onNotify(Observable *o) = 0;
|
virtual void onNotify(Observable *o) = 0;
|
||||||
@@ -27,23 +27,15 @@ class Observable
|
|||||||
{
|
{
|
||||||
std::list<Observer *> observers;
|
std::list<Observer *> observers;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void notifyObservers()
|
void notifyObservers()
|
||||||
{
|
{
|
||||||
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator)
|
for (std::list<Observer *>::const_iterator iterator = observers.begin(); iterator != observers.end(); ++iterator) {
|
||||||
{
|
|
||||||
(*iterator)->onNotify(this);
|
(*iterator)->onNotify(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void addObserver(Observer *o)
|
void addObserver(Observer *o) { observers.push_back(o); }
|
||||||
{
|
|
||||||
observers.push_back(o);
|
|
||||||
}
|
|
||||||
|
|
||||||
void removeObserver(Observer *o)
|
void removeObserver(Observer *o) { observers.remove(o); }
|
||||||
{
|
|
||||||
observers.remove(o);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
|
||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Periodically invoke a callback.
|
* Periodically invoke a callback.
|
||||||
@@ -10,13 +10,12 @@
|
|||||||
*/
|
*/
|
||||||
class Periodic : public PeriodicTask
|
class Periodic : public PeriodicTask
|
||||||
{
|
{
|
||||||
uint32_t (*callback)();
|
uint32_t (*callback)();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
// callback returns the period for the next callback invocation (or 0 if we should no longer be called)
|
||||||
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
|
Periodic(uint32_t (*_callback)()) : callback(_callback) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void doTask();
|
||||||
void doTask();
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
|
|
||||||
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod)
|
PeriodicTask::PeriodicTask(uint32_t initialPeriod) : period(initialPeriod) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// call this from loop
|
/// call this from loop
|
||||||
void PeriodicTask::loop()
|
void PeriodicTask::loop()
|
||||||
{
|
{
|
||||||
uint32_t now = millis();
|
|
||||||
if (period && (now - lastMsec) >= period)
|
|
||||||
{
|
{
|
||||||
|
meshtastic::LockGuard lg(&lock);
|
||||||
|
uint32_t now = millis();
|
||||||
|
if (!period || (now - lastMsec) < period) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
lastMsec = now;
|
lastMsec = now;
|
||||||
doTask();
|
|
||||||
}
|
}
|
||||||
|
// Release the lock in case the task wants to change the period.
|
||||||
|
doTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Periodic::doTask()
|
void Periodic::doTask()
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <cstdint>
|
||||||
#include "configuration.h"
|
|
||||||
|
#include "lock.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for tasks that want their doTask() method invoked periodically
|
* A base class for tasks that want their doTask() method invoked periodically
|
||||||
@@ -15,9 +16,10 @@ class PeriodicTask
|
|||||||
uint32_t lastMsec = 0;
|
uint32_t lastMsec = 0;
|
||||||
uint32_t period = 1; // call soon after creation
|
uint32_t period = 1; // call soon after creation
|
||||||
|
|
||||||
public:
|
// Protects the above variables.
|
||||||
uint32_t periodMsec;
|
meshtastic::Lock lock;
|
||||||
|
|
||||||
|
public:
|
||||||
virtual ~PeriodicTask() {}
|
virtual ~PeriodicTask() {}
|
||||||
|
|
||||||
PeriodicTask(uint32_t initialPeriod = 1);
|
PeriodicTask(uint32_t initialPeriod = 1);
|
||||||
@@ -26,8 +28,12 @@ public:
|
|||||||
virtual void loop();
|
virtual void loop();
|
||||||
|
|
||||||
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
/// Set a new period in msecs (can be called from doTask or elsewhere and the scheduler will cope)
|
||||||
void setPeriod(uint32_t p) { period = p; }
|
void setPeriod(uint32_t p)
|
||||||
|
{
|
||||||
|
meshtastic::LockGuard lg(&lock);
|
||||||
|
period = p;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void doTask() = 0;
|
virtual void doTask() = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,20 +5,17 @@
|
|||||||
/**
|
/**
|
||||||
* A wrapper for freertos queues that assumes each element is a pointer
|
* A wrapper for freertos queues that assumes each element is a pointer
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <class T> class PointerQueue : public TypedQueue<T *>
|
||||||
class PointerQueue : public TypedQueue<T *>
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PointerQueue(int maxElements) : TypedQueue<T *>(maxElements)
|
PointerQueue(int maxElements) : TypedQueue<T *>(maxElements) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a ptr or null if the queue was empty
|
// returns a ptr or null if the queue was empty
|
||||||
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
|
T *dequeuePtr(TickType_t maxWait = portMAX_DELAY)
|
||||||
{
|
{
|
||||||
T *p;
|
T *p;
|
||||||
|
|
||||||
return this->dequeue(&p, maxWait) == pdTRUE ? p : NULL;
|
return this->dequeue(&p, maxWait) ? p : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns a ptr or null if the queue was empty
|
// returns a ptr or null if the queue was empty
|
||||||
@@ -26,6 +23,6 @@ public:
|
|||||||
{
|
{
|
||||||
T *p;
|
T *p;
|
||||||
|
|
||||||
return this->dequeueFromISR(&p, higherPriWoken) == pdTRUE ? p : NULL;
|
return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
#include "sleep.h"
|
#include "PowerFSM.h"
|
||||||
|
#include "GPS.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "screen.h"
|
|
||||||
#include "PowerFSM.h"
|
|
||||||
#include "GPS.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "screen.h"
|
||||||
|
#include "sleep.h"
|
||||||
|
|
||||||
static void sdsEnter()
|
static void sdsEnter()
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,7 @@ static void sdsEnter()
|
|||||||
|
|
||||||
static void lsEnter()
|
static void lsEnter()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("lsEnter begin\n");
|
DEBUG_MSG("lsEnter begin, ls_secs=%u\n", radioConfig.preferences.ls_secs);
|
||||||
screen.setOn(false);
|
screen.setOn(false);
|
||||||
|
|
||||||
while (!service.radio.rf95.canSleep())
|
while (!service.radio.rf95.canSleep())
|
||||||
@@ -33,7 +33,8 @@ static void lsEnter()
|
|||||||
|
|
||||||
gps.prepareSleep(); // abandon in-process parsing
|
gps.prepareSleep(); // abandon in-process parsing
|
||||||
|
|
||||||
//if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then leave GPS on
|
// if (!isUSBPowered) // FIXME - temp hack until we can put gps in sleep mode, if we have AC when we go to sleep then
|
||||||
|
// leave GPS on
|
||||||
// setGPSPower(false); // kill GPS power
|
// setGPSPower(false); // kill GPS power
|
||||||
|
|
||||||
DEBUG_MSG("lsEnter end\n");
|
DEBUG_MSG("lsEnter end\n");
|
||||||
@@ -47,8 +48,7 @@ static void lsIdle()
|
|||||||
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
esp_sleep_source_t wakeCause = ESP_SLEEP_WAKEUP_UNDEFINED;
|
||||||
bool reached_ls_secs = false;
|
bool reached_ls_secs = false;
|
||||||
|
|
||||||
while (!reached_ls_secs)
|
while (!reached_ls_secs) {
|
||||||
{
|
|
||||||
// Briefly come out of sleep long enough to blink the led once every few seconds
|
// Briefly come out of sleep long enough to blink the led once every few seconds
|
||||||
uint32_t sleepTime = 5;
|
uint32_t sleepTime = 5;
|
||||||
|
|
||||||
@@ -67,17 +67,19 @@ static void lsIdle()
|
|||||||
}
|
}
|
||||||
setLed(false);
|
setLed(false);
|
||||||
|
|
||||||
if (reached_ls_secs)
|
if (reached_ls_secs) {
|
||||||
{
|
|
||||||
// stay in LS mode but let loop check whatever it wants
|
// stay in LS mode but let loop check whatever it wants
|
||||||
DEBUG_MSG("reached ls_secs, servicing loop()\n");
|
DEBUG_MSG("reached ls_secs, servicing loop()\n");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
DEBUG_MSG("wakeCause %d\n", wakeCause);
|
||||||
|
|
||||||
// Regardless of why we woke just transition to NB (and that state will handle stuff like IRQs etc)
|
if (!digitalRead(BUTTON_PIN)) // If we woke because of press, instead generate a PRESS event.
|
||||||
powerFSM.trigger(EVENT_WAKE_TIMER);
|
{
|
||||||
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
|
} else {
|
||||||
|
// Otherwise let the NB state handle the IRQ (and that state will handle stuff like IRQs etc)
|
||||||
|
powerFSM.trigger(EVENT_WAKE_TIMER);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,38 +106,47 @@ static void onEnter()
|
|||||||
{
|
{
|
||||||
screen.setOn(true);
|
screen.setOn(true);
|
||||||
setBluetoothEnable(true);
|
setBluetoothEnable(true);
|
||||||
|
|
||||||
|
static uint32_t lastPingMs;
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
|
||||||
|
if (now - lastPingMs > 60 * 1000) { // if more than a minute since our last press, ask other nodes to update their state
|
||||||
|
service.sendNetworkPing();
|
||||||
|
lastPingMs = now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wakeForPing() {}
|
||||||
static void wakeForPing()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
static void screenPress()
|
static void screenPress()
|
||||||
{
|
{
|
||||||
screen.onPress();
|
screen.onPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void bootEnter() {}
|
||||||
|
|
||||||
State stateSDS(sdsEnter, NULL, NULL, "SDS");
|
State stateSDS(sdsEnter, NULL, NULL, "SDS");
|
||||||
State stateLS(lsEnter, lsIdle, lsExit, "LS");
|
State stateLS(lsEnter, lsIdle, lsExit, "LS");
|
||||||
State stateNB(nbEnter, NULL, NULL, "NB");
|
State stateNB(nbEnter, NULL, NULL, "NB");
|
||||||
State stateDARK(darkEnter, NULL, NULL, "DARK");
|
State stateDARK(darkEnter, NULL, NULL, "DARK");
|
||||||
|
State stateBOOT(bootEnter, NULL, NULL, "BOOT");
|
||||||
State stateON(onEnter, NULL, NULL, "ON");
|
State stateON(onEnter, NULL, NULL, "ON");
|
||||||
Fsm powerFSM(&stateDARK);
|
Fsm powerFSM(&stateBOOT);
|
||||||
|
|
||||||
void PowerFSM_setup()
|
void PowerFSM_setup()
|
||||||
{
|
{
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_BOOT, NULL, "Boot");
|
powerFSM.add_timed_transition(&stateBOOT, &stateON, 3 * 1000, NULL, "boot timeout");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
powerFSM.add_transition(&stateLS, &stateDARK, EVENT_WAKE_TIMER, wakeForPing, "Wake timer");
|
||||||
|
|
||||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
|
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it
|
||||||
// powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
// handles things powerFSM.add_transition(&stateLS, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
powerFSM.add_transition(&stateNB, &stateNB, EVENT_RECEIVED_PACKET, NULL, "Received packet, resetting win wake");
|
||||||
|
|
||||||
// Note we don't really use this transition, because when we wake from light sleep we _always_ transition to NB and then it handles things
|
// Handle press events
|
||||||
// powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateLS, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
|
|
||||||
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateNB, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
powerFSM.add_transition(&stateDARK, &stateON, EVENT_PRESS, NULL, "Press");
|
||||||
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
powerFSM.add_transition(&stateON, &stateON, EVENT_PRESS, screenPress, "Press"); // reenter On to restart our timers
|
||||||
@@ -162,11 +173,14 @@ void PowerFSM_setup()
|
|||||||
|
|
||||||
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
powerFSM.add_timed_transition(&stateNB, &stateLS, radioConfig.preferences.min_wake_secs * 1000, NULL, "Min wake timeout");
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL, "Bluetooth timeout");
|
powerFSM.add_timed_transition(&stateDARK, &stateLS, radioConfig.preferences.wait_bluetooth_secs * 1000, NULL,
|
||||||
|
"Bluetooth timeout");
|
||||||
|
|
||||||
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL, "mesh timeout");
|
powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.mesh_sds_timeout_secs * 1000, NULL,
|
||||||
|
"mesh timeout");
|
||||||
// removing for now, because some users don't even have phones
|
// removing for now, because some users don't even have phones
|
||||||
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone timeout");
|
// powerFSM.add_timed_transition(&stateLS, &stateSDS, radioConfig.preferences.phone_sds_timeout_sec * 1000, NULL, "phone
|
||||||
|
// timeout");
|
||||||
|
|
||||||
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
powerFSM.run_machine(); // run one interation of the state machine, so we run our on enter tasks for the initial DARK state
|
||||||
}
|
}
|
||||||
@@ -9,9 +9,9 @@
|
|||||||
#define EVENT_RECEIVED_PACKET 3
|
#define EVENT_RECEIVED_PACKET 3
|
||||||
#define EVENT_PACKET_FOR_PHONE 4
|
#define EVENT_PACKET_FOR_PHONE 4
|
||||||
#define EVENT_RECEIVED_TEXT_MSG 5
|
#define EVENT_RECEIVED_TEXT_MSG 5
|
||||||
#define EVENT_BOOT 6
|
// #define EVENT_BOOT 6 // now done with a timed transition
|
||||||
#define EVENT_BLUETOOTH_PAIR 7
|
#define EVENT_BLUETOOTH_PAIR 7
|
||||||
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
#define EVENT_NODEDB_UPDATED 8 // NodeDB has a big enough change that we think you should turn on the screen
|
||||||
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
#define EVENT_CONTACT_FROM_PHONE 9 // the phone just talked to us over bluetooth
|
||||||
|
|
||||||
extern Fsm powerFSM;
|
extern Fsm powerFSM;
|
||||||
|
|||||||
@@ -1,58 +1,38 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <cassert>
|
||||||
#include <assert.h>
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/queue.h>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wrapper for freertos queues. Note: each element object must be quite small, so T should be only
|
* A wrapper for freertos queues. Note: each element object should be small
|
||||||
* pointer types or ints
|
* and POD (Plain Old Data type) as elements are memcpied by value.
|
||||||
*/
|
*/
|
||||||
template <class T>
|
template <class T> class TypedQueue
|
||||||
class TypedQueue
|
|
||||||
{
|
{
|
||||||
|
static_assert(std::is_pod<T>::value, "T must be pod");
|
||||||
QueueHandle_t h;
|
QueueHandle_t h;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TypedQueue(int maxElements)
|
TypedQueue(int maxElements)
|
||||||
{
|
{
|
||||||
h = xQueueCreate(maxElements, sizeof(T));
|
h = xQueueCreate(maxElements, sizeof(T));
|
||||||
assert(h);
|
assert(h);
|
||||||
}
|
}
|
||||||
|
|
||||||
~TypedQueue()
|
~TypedQueue() { vQueueDelete(h); }
|
||||||
{
|
|
||||||
vQueueDelete(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
int numFree()
|
int numFree() { return uxQueueSpacesAvailable(h); }
|
||||||
{
|
|
||||||
return uxQueueSpacesAvailable(h);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isEmpty()
|
bool isEmpty() { return uxQueueMessagesWaiting(h) == 0; }
|
||||||
{
|
|
||||||
return uxQueueMessagesWaiting(h) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdTRUE for success else failure
|
bool enqueue(T x, TickType_t maxWait = portMAX_DELAY) { return xQueueSendToBack(h, &x, maxWait) == pdTRUE; }
|
||||||
BaseType_t enqueue(T x, TickType_t maxWait = portMAX_DELAY)
|
|
||||||
{
|
|
||||||
return xQueueSendToBack(h, &x, maxWait);
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseType_t enqueueFromISR(T x, BaseType_t *higherPriWoken)
|
bool enqueueFromISR(T x, BaseType_t *higherPriWoken) { return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE; }
|
||||||
{
|
|
||||||
return xQueueSendToBackFromISR(h, &x, higherPriWoken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pdTRUE for success else failure
|
bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY) { return xQueueReceive(h, p, maxWait) == pdTRUE; }
|
||||||
BaseType_t dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
|
|
||||||
{
|
|
||||||
return xQueueReceive(h, p, maxWait);
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseType_t dequeueFromISR(T *p, BaseType_t *higherPriWoken)
|
bool dequeueFromISR(T *p, BaseType_t *higherPriWoken) { return xQueueReceiveFromISR(h, p, higherPriWoken); }
|
||||||
{
|
|
||||||
return xQueueReceiveFromISR(h, p, higherPriWoken);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
// Select which board is being used. If the outside build environment has sent a choice, just use that
|
// Select which board is being used. If the outside build environment has sent a choice, just use that
|
||||||
#if !defined(T_BEAM_V10) && !defined(HELTEC_LORA32)
|
#if !defined(T_BEAM_V10) && !defined(HELTEC_LORA32)
|
||||||
#define T_BEAM_V10 // AKA Rev1 (second board released)
|
#define T_BEAM_V10 // AKA Rev1 (second board released)
|
||||||
// #define HELTEC_LORA32
|
// #define HELTEC_LORA32
|
||||||
|
|
||||||
#define HW_VERSION_US // We encode the hardware freq range in the hw version string, so sw update can eventually install the correct build
|
#define HW_VERSION_US // We encode the hardware freq range in the hw version string, so sw update can eventually install the
|
||||||
|
// correct build
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled)
|
// If we are using the JTAG port for debugging, some pins must be left free for that (and things like GPS have to be disabled)
|
||||||
@@ -77,6 +78,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
#define SSD1306_ADDRESS 0x3C
|
#define SSD1306_ADDRESS 0x3C
|
||||||
|
|
||||||
|
// Flip the screen upside down by default as it makes more sense on T-BEAM
|
||||||
|
// devices. Comment this out to not rotate screen 180 degrees.
|
||||||
|
#define FLIP_SCREEN_VERTICALLY
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// GPS
|
// GPS
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -84,14 +89,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
#define GPS_SERIAL_NUM 1
|
#define GPS_SERIAL_NUM 1
|
||||||
#define GPS_BAUDRATE 9600
|
#define GPS_BAUDRATE 9600
|
||||||
|
|
||||||
#if defined(T_BEAM_V10)
|
|
||||||
#define GPS_RX_PIN 34
|
#define GPS_RX_PIN 34
|
||||||
#ifdef USE_JTAG
|
#ifdef USE_JTAG
|
||||||
#define GPS_TX_PIN -1
|
#define GPS_TX_PIN -1
|
||||||
#else
|
#else
|
||||||
#define GPS_TX_PIN 12
|
#define GPS_TX_PIN 12
|
||||||
#endif
|
#endif
|
||||||
#endif
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// LoRa SPI
|
// LoRa SPI
|
||||||
@@ -106,8 +109,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
// This string must exactly match the case used in release file names or the android updater won't work
|
// This string must exactly match the case used in release file names or the android updater won't work
|
||||||
#define HW_VENDOR "TBEAM"
|
#define HW_VENDOR "TBEAM"
|
||||||
|
|
||||||
#define BICOLOR_DISPLAY // we have yellow at the top 16 lines
|
|
||||||
|
|
||||||
// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
|
// #define BUTTON_NEED_PULLUP // if set we need to turn on the internal CPU pullup during sleep
|
||||||
|
|
||||||
#define I2C_SDA 21
|
#define I2C_SDA 21
|
||||||
|
|||||||
20
src/debug.cpp
Normal file
20
src/debug.cpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include "configuration.h"
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
void printThreadInfo(const char *extra)
|
||||||
|
{
|
||||||
|
uint32_t taskHandle = reinterpret_cast<uint32_t>(xTaskGetCurrentTaskHandle());
|
||||||
|
DEBUG_MSG("printThreadInfo(%s) task: %" PRIx32 " core id: %u min free stack: %u\n", extra, taskHandle, xPortGetCoreID(),
|
||||||
|
uxTaskGetStackHighWaterMark(nullptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
10
src/debug.h
Normal file
10
src/debug.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Dumps out which core we are running on, and min level of remaining stack
|
||||||
|
/// seen.
|
||||||
|
void printThreadInfo(const char *extra);
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
7
src/error.h
Normal file
7
src/error.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// Error codes for critical error
|
||||||
|
enum CriticalErrorCode { NoError, ErrTxWatchdog };
|
||||||
|
|
||||||
|
/// Record an error that should be reported via analytics
|
||||||
|
void recordCriticalError(CriticalErrorCode code, uint32_t address = 0);
|
||||||
838
src/fonts.h
838
src/fonts.h
@@ -1,423 +1,423 @@
|
|||||||
const uint8_t Custom_ArialMT_Plain_10[] PROGMEM = {
|
const uint8_t Custom_ArialMT_Plain_10[] PROGMEM = {
|
||||||
0x0A, // Width: 10
|
0x0A, // Width: 10
|
||||||
0x0A, // Height: 10
|
0x0A, // Height: 10
|
||||||
0x20, // First Char: 32
|
0x20, // First Char: 32
|
||||||
0xE0, // Numbers of Chars: 224
|
0xE0, // Numbers of Chars: 224
|
||||||
|
|
||||||
// Jump Table:
|
// Jump Table:
|
||||||
0xFF, 0xFF, 0x00, 0x03, // 32:65535
|
0xFF, 0xFF, 0x00, 0x03, // 32:65535
|
||||||
0x00, 0x00, 0x04, 0x03, // 33:0
|
0x00, 0x00, 0x04, 0x03, // 33:0
|
||||||
0x00, 0x04, 0x05, 0x04, // 34:4
|
0x00, 0x04, 0x05, 0x04, // 34:4
|
||||||
0x00, 0x09, 0x09, 0x06, // 35:9
|
0x00, 0x09, 0x09, 0x06, // 35:9
|
||||||
0x00, 0x12, 0x0A, 0x06, // 36:18
|
0x00, 0x12, 0x0A, 0x06, // 36:18
|
||||||
0x00, 0x1C, 0x10, 0x09, // 37:28
|
0x00, 0x1C, 0x10, 0x09, // 37:28
|
||||||
0x00, 0x2C, 0x0E, 0x07, // 38:44
|
0x00, 0x2C, 0x0E, 0x07, // 38:44
|
||||||
0x00, 0x3A, 0x01, 0x02, // 39:58
|
0x00, 0x3A, 0x01, 0x02, // 39:58
|
||||||
0x00, 0x3B, 0x06, 0x03, // 40:59
|
0x00, 0x3B, 0x06, 0x03, // 40:59
|
||||||
0x00, 0x41, 0x06, 0x03, // 41:65
|
0x00, 0x41, 0x06, 0x03, // 41:65
|
||||||
0x00, 0x47, 0x05, 0x04, // 42:71
|
0x00, 0x47, 0x05, 0x04, // 42:71
|
||||||
0x00, 0x4C, 0x09, 0x06, // 43:76
|
0x00, 0x4C, 0x09, 0x06, // 43:76
|
||||||
0x00, 0x55, 0x04, 0x03, // 44:85
|
0x00, 0x55, 0x04, 0x03, // 44:85
|
||||||
0x00, 0x59, 0x03, 0x03, // 45:89
|
0x00, 0x59, 0x03, 0x03, // 45:89
|
||||||
0x00, 0x5C, 0x04, 0x03, // 46:92
|
0x00, 0x5C, 0x04, 0x03, // 46:92
|
||||||
0x00, 0x60, 0x05, 0x03, // 47:96
|
0x00, 0x60, 0x05, 0x03, // 47:96
|
||||||
0x00, 0x65, 0x0A, 0x06, // 48:101
|
0x00, 0x65, 0x0A, 0x06, // 48:101
|
||||||
0x00, 0x6F, 0x08, 0x06, // 49:111
|
0x00, 0x6F, 0x08, 0x06, // 49:111
|
||||||
0x00, 0x77, 0x0A, 0x06, // 50:119
|
0x00, 0x77, 0x0A, 0x06, // 50:119
|
||||||
0x00, 0x81, 0x0A, 0x06, // 51:129
|
0x00, 0x81, 0x0A, 0x06, // 51:129
|
||||||
0x00, 0x8B, 0x0B, 0x06, // 52:139
|
0x00, 0x8B, 0x0B, 0x06, // 52:139
|
||||||
0x00, 0x96, 0x0A, 0x06, // 53:150
|
0x00, 0x96, 0x0A, 0x06, // 53:150
|
||||||
0x00, 0xA0, 0x0A, 0x06, // 54:160
|
0x00, 0xA0, 0x0A, 0x06, // 54:160
|
||||||
0x00, 0xAA, 0x09, 0x06, // 55:170
|
0x00, 0xAA, 0x09, 0x06, // 55:170
|
||||||
0x00, 0xB3, 0x0A, 0x06, // 56:179
|
0x00, 0xB3, 0x0A, 0x06, // 56:179
|
||||||
0x00, 0xBD, 0x0A, 0x06, // 57:189
|
0x00, 0xBD, 0x0A, 0x06, // 57:189
|
||||||
0x00, 0xC7, 0x04, 0x03, // 58:199
|
0x00, 0xC7, 0x04, 0x03, // 58:199
|
||||||
0x00, 0xCB, 0x04, 0x03, // 59:203
|
0x00, 0xCB, 0x04, 0x03, // 59:203
|
||||||
0x00, 0xCF, 0x0A, 0x06, // 60:207
|
0x00, 0xCF, 0x0A, 0x06, // 60:207
|
||||||
0x00, 0xD9, 0x09, 0x06, // 61:217
|
0x00, 0xD9, 0x09, 0x06, // 61:217
|
||||||
0x00, 0xE2, 0x09, 0x06, // 62:226
|
0x00, 0xE2, 0x09, 0x06, // 62:226
|
||||||
0x00, 0xEB, 0x0B, 0x06, // 63:235
|
0x00, 0xEB, 0x0B, 0x06, // 63:235
|
||||||
0x00, 0xF6, 0x14, 0x0A, // 64:246
|
0x00, 0xF6, 0x14, 0x0A, // 64:246
|
||||||
0x01, 0x0A, 0x0E, 0x07, // 65:266
|
0x01, 0x0A, 0x0E, 0x07, // 65:266
|
||||||
0x01, 0x18, 0x0C, 0x07, // 66:280
|
0x01, 0x18, 0x0C, 0x07, // 66:280
|
||||||
0x01, 0x24, 0x0C, 0x07, // 67:292
|
0x01, 0x24, 0x0C, 0x07, // 67:292
|
||||||
0x01, 0x30, 0x0B, 0x07, // 68:304
|
0x01, 0x30, 0x0B, 0x07, // 68:304
|
||||||
0x01, 0x3B, 0x0C, 0x07, // 69:315
|
0x01, 0x3B, 0x0C, 0x07, // 69:315
|
||||||
0x01, 0x47, 0x09, 0x06, // 70:327
|
0x01, 0x47, 0x09, 0x06, // 70:327
|
||||||
0x01, 0x50, 0x0D, 0x08, // 71:336
|
0x01, 0x50, 0x0D, 0x08, // 71:336
|
||||||
0x01, 0x5D, 0x0C, 0x07, // 72:349
|
0x01, 0x5D, 0x0C, 0x07, // 72:349
|
||||||
0x01, 0x69, 0x04, 0x03, // 73:361
|
0x01, 0x69, 0x04, 0x03, // 73:361
|
||||||
0x01, 0x6D, 0x08, 0x05, // 74:365
|
0x01, 0x6D, 0x08, 0x05, // 74:365
|
||||||
0x01, 0x75, 0x0E, 0x07, // 75:373
|
0x01, 0x75, 0x0E, 0x07, // 75:373
|
||||||
0x01, 0x83, 0x0C, 0x06, // 76:387
|
0x01, 0x83, 0x0C, 0x06, // 76:387
|
||||||
0x01, 0x8F, 0x10, 0x08, // 77:399
|
0x01, 0x8F, 0x10, 0x08, // 77:399
|
||||||
0x01, 0x9F, 0x0C, 0x07, // 78:415
|
0x01, 0x9F, 0x0C, 0x07, // 78:415
|
||||||
0x01, 0xAB, 0x0E, 0x08, // 79:427
|
0x01, 0xAB, 0x0E, 0x08, // 79:427
|
||||||
0x01, 0xB9, 0x0B, 0x07, // 80:441
|
0x01, 0xB9, 0x0B, 0x07, // 80:441
|
||||||
0x01, 0xC4, 0x0E, 0x08, // 81:452
|
0x01, 0xC4, 0x0E, 0x08, // 81:452
|
||||||
0x01, 0xD2, 0x0C, 0x07, // 82:466
|
0x01, 0xD2, 0x0C, 0x07, // 82:466
|
||||||
0x01, 0xDE, 0x0C, 0x07, // 83:478
|
0x01, 0xDE, 0x0C, 0x07, // 83:478
|
||||||
0x01, 0xEA, 0x0B, 0x06, // 84:490
|
0x01, 0xEA, 0x0B, 0x06, // 84:490
|
||||||
0x01, 0xF5, 0x0C, 0x07, // 85:501
|
0x01, 0xF5, 0x0C, 0x07, // 85:501
|
||||||
0x02, 0x01, 0x0D, 0x07, // 86:513
|
0x02, 0x01, 0x0D, 0x07, // 86:513
|
||||||
0x02, 0x0E, 0x11, 0x09, // 87:526
|
0x02, 0x0E, 0x11, 0x09, // 87:526
|
||||||
0x02, 0x1F, 0x0E, 0x07, // 88:543
|
0x02, 0x1F, 0x0E, 0x07, // 88:543
|
||||||
0x02, 0x2D, 0x0D, 0x07, // 89:557
|
0x02, 0x2D, 0x0D, 0x07, // 89:557
|
||||||
0x02, 0x3A, 0x0C, 0x06, // 90:570
|
0x02, 0x3A, 0x0C, 0x06, // 90:570
|
||||||
0x02, 0x46, 0x06, 0x03, // 91:582
|
0x02, 0x46, 0x06, 0x03, // 91:582
|
||||||
0x02, 0x4C, 0x06, 0x03, // 92:588
|
0x02, 0x4C, 0x06, 0x03, // 92:588
|
||||||
0x02, 0x52, 0x04, 0x03, // 93:594
|
0x02, 0x52, 0x04, 0x03, // 93:594
|
||||||
0x02, 0x56, 0x09, 0x05, // 94:598
|
0x02, 0x56, 0x09, 0x05, // 94:598
|
||||||
0x02, 0x5F, 0x0C, 0x06, // 95:607
|
0x02, 0x5F, 0x0C, 0x06, // 95:607
|
||||||
0x02, 0x6B, 0x03, 0x03, // 96:619
|
0x02, 0x6B, 0x03, 0x03, // 96:619
|
||||||
0x02, 0x6E, 0x0A, 0x06, // 97:622
|
0x02, 0x6E, 0x0A, 0x06, // 97:622
|
||||||
0x02, 0x78, 0x0A, 0x06, // 98:632
|
0x02, 0x78, 0x0A, 0x06, // 98:632
|
||||||
0x02, 0x82, 0x0A, 0x05, // 99:642
|
0x02, 0x82, 0x0A, 0x05, // 99:642
|
||||||
0x02, 0x8C, 0x0A, 0x06, // 100:652
|
0x02, 0x8C, 0x0A, 0x06, // 100:652
|
||||||
0x02, 0x96, 0x0A, 0x06, // 101:662
|
0x02, 0x96, 0x0A, 0x06, // 101:662
|
||||||
0x02, 0xA0, 0x05, 0x03, // 102:672
|
0x02, 0xA0, 0x05, 0x03, // 102:672
|
||||||
0x02, 0xA5, 0x0A, 0x06, // 103:677
|
0x02, 0xA5, 0x0A, 0x06, // 103:677
|
||||||
0x02, 0xAF, 0x0A, 0x06, // 104:687
|
0x02, 0xAF, 0x0A, 0x06, // 104:687
|
||||||
0x02, 0xB9, 0x04, 0x02, // 105:697
|
0x02, 0xB9, 0x04, 0x02, // 105:697
|
||||||
0x02, 0xBD, 0x04, 0x02, // 106:701
|
0x02, 0xBD, 0x04, 0x02, // 106:701
|
||||||
0x02, 0xC1, 0x08, 0x05, // 107:705
|
0x02, 0xC1, 0x08, 0x05, // 107:705
|
||||||
0x02, 0xC9, 0x04, 0x02, // 108:713
|
0x02, 0xC9, 0x04, 0x02, // 108:713
|
||||||
0x02, 0xCD, 0x10, 0x08, // 109:717
|
0x02, 0xCD, 0x10, 0x08, // 109:717
|
||||||
0x02, 0xDD, 0x0A, 0x06, // 110:733
|
0x02, 0xDD, 0x0A, 0x06, // 110:733
|
||||||
0x02, 0xE7, 0x0A, 0x06, // 111:743
|
0x02, 0xE7, 0x0A, 0x06, // 111:743
|
||||||
0x02, 0xF1, 0x0A, 0x06, // 112:753
|
0x02, 0xF1, 0x0A, 0x06, // 112:753
|
||||||
0x02, 0xFB, 0x0A, 0x06, // 113:763
|
0x02, 0xFB, 0x0A, 0x06, // 113:763
|
||||||
0x03, 0x05, 0x05, 0x03, // 114:773
|
0x03, 0x05, 0x05, 0x03, // 114:773
|
||||||
0x03, 0x0A, 0x08, 0x05, // 115:778
|
0x03, 0x0A, 0x08, 0x05, // 115:778
|
||||||
0x03, 0x12, 0x06, 0x03, // 116:786
|
0x03, 0x12, 0x06, 0x03, // 116:786
|
||||||
0x03, 0x18, 0x0A, 0x06, // 117:792
|
0x03, 0x18, 0x0A, 0x06, // 117:792
|
||||||
0x03, 0x22, 0x09, 0x05, // 118:802
|
0x03, 0x22, 0x09, 0x05, // 118:802
|
||||||
0x03, 0x2B, 0x0E, 0x07, // 119:811
|
0x03, 0x2B, 0x0E, 0x07, // 119:811
|
||||||
0x03, 0x39, 0x0A, 0x05, // 120:825
|
0x03, 0x39, 0x0A, 0x05, // 120:825
|
||||||
0x03, 0x43, 0x09, 0x05, // 121:835
|
0x03, 0x43, 0x09, 0x05, // 121:835
|
||||||
0x03, 0x4C, 0x0A, 0x05, // 122:844
|
0x03, 0x4C, 0x0A, 0x05, // 122:844
|
||||||
0x03, 0x56, 0x06, 0x03, // 123:854
|
0x03, 0x56, 0x06, 0x03, // 123:854
|
||||||
0x03, 0x5C, 0x04, 0x03, // 124:860
|
0x03, 0x5C, 0x04, 0x03, // 124:860
|
||||||
0x03, 0x60, 0x05, 0x03, // 125:864
|
0x03, 0x60, 0x05, 0x03, // 125:864
|
||||||
0x03, 0x65, 0x09, 0x06, // 126:869
|
0x03, 0x65, 0x09, 0x06, // 126:869
|
||||||
0xFF, 0xFF, 0x00, 0x00, // 127:65535
|
0xFF, 0xFF, 0x00, 0x00, // 127:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 128:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 128:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 129:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 129:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 130:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 130:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 131:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 131:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 132:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 132:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 133:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 133:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 134:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 134:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 135:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 135:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 136:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 136:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 137:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 137:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 138:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 138:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 139:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 139:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 140:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 140:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 141:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 141:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 142:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 142:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 143:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 143:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 144:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 144:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 145:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 145:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 146:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 146:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 147:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 147:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 148:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 148:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 149:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 149:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 150:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 150:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 151:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 151:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 152:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 152:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 153:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 153:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 154:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 154:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 155:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 155:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 156:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 156:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 157:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 157:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 158:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 158:65535
|
||||||
0xFF, 0xFF, 0x00, 0x0A, // 159:65535
|
0xFF, 0xFF, 0x00, 0x0A, // 159:65535
|
||||||
0xFF, 0xFF, 0x00, 0x03, // 160:65535
|
0xFF, 0xFF, 0x00, 0x03, // 160:65535
|
||||||
0x03, 0x6E, 0x04, 0x03, // 161:878
|
0x03, 0x6E, 0x04, 0x03, // 161:878
|
||||||
0x03, 0x72, 0x0A, 0x06, // 162:882
|
0x03, 0x72, 0x0A, 0x06, // 162:882
|
||||||
0x03, 0x7C, 0x0C, 0x06, // 163:892
|
0x03, 0x7C, 0x0C, 0x06, // 163:892
|
||||||
0x03, 0x88, 0x0A, 0x06, // 164:904
|
0x03, 0x88, 0x0A, 0x06, // 164:904
|
||||||
0x03, 0x92, 0x0A, 0x06, // 165:914
|
0x03, 0x92, 0x0A, 0x06, // 165:914
|
||||||
0x03, 0x9C, 0x04, 0x03, // 166:924
|
0x03, 0x9C, 0x04, 0x03, // 166:924
|
||||||
0x03, 0xA0, 0x0A, 0x06, // 167:928
|
0x03, 0xA0, 0x0A, 0x06, // 167:928
|
||||||
0x03, 0xAA, 0x05, 0x03, // 168:938
|
0x03, 0xAA, 0x05, 0x03, // 168:938
|
||||||
0x03, 0xAF, 0x0D, 0x07, // 169:943
|
0x03, 0xAF, 0x0D, 0x07, // 169:943
|
||||||
0x03, 0xBC, 0x07, 0x04, // 170:956
|
0x03, 0xBC, 0x07, 0x04, // 170:956
|
||||||
0x03, 0xC3, 0x0A, 0x06, // 171:963
|
0x03, 0xC3, 0x0A, 0x06, // 171:963
|
||||||
0x03, 0xCD, 0x09, 0x06, // 172:973
|
0x03, 0xCD, 0x09, 0x06, // 172:973
|
||||||
0x03, 0xD6, 0x03, 0x03, // 173:982
|
0x03, 0xD6, 0x03, 0x03, // 173:982
|
||||||
0x03, 0xD9, 0x0D, 0x07, // 174:985
|
0x03, 0xD9, 0x0D, 0x07, // 174:985
|
||||||
0x03, 0xE6, 0x0B, 0x06, // 175:998
|
0x03, 0xE6, 0x0B, 0x06, // 175:998
|
||||||
0x03, 0xF1, 0x07, 0x04, // 176:1009
|
0x03, 0xF1, 0x07, 0x04, // 176:1009
|
||||||
0x03, 0xF8, 0x0A, 0x05, // 177:1016
|
0x03, 0xF8, 0x0A, 0x05, // 177:1016
|
||||||
0x04, 0x02, 0x05, 0x03, // 178:1026
|
0x04, 0x02, 0x05, 0x03, // 178:1026
|
||||||
0x04, 0x07, 0x05, 0x03, // 179:1031
|
0x04, 0x07, 0x05, 0x03, // 179:1031
|
||||||
0x04, 0x0C, 0x05, 0x03, // 180:1036
|
0x04, 0x0C, 0x05, 0x03, // 180:1036
|
||||||
0x04, 0x11, 0x0A, 0x06, // 181:1041
|
0x04, 0x11, 0x0A, 0x06, // 181:1041
|
||||||
0x04, 0x1B, 0x09, 0x05, // 182:1051
|
0x04, 0x1B, 0x09, 0x05, // 182:1051
|
||||||
0x04, 0x24, 0x03, 0x03, // 183:1060
|
0x04, 0x24, 0x03, 0x03, // 183:1060
|
||||||
0x04, 0x27, 0x06, 0x03, // 184:1063
|
0x04, 0x27, 0x06, 0x03, // 184:1063
|
||||||
0x04, 0x2D, 0x05, 0x03, // 185:1069
|
0x04, 0x2D, 0x05, 0x03, // 185:1069
|
||||||
0x04, 0x32, 0x07, 0x04, // 186:1074
|
0x04, 0x32, 0x07, 0x04, // 186:1074
|
||||||
0x04, 0x39, 0x0A, 0x06, // 187:1081
|
0x04, 0x39, 0x0A, 0x06, // 187:1081
|
||||||
0x04, 0x43, 0x10, 0x08, // 188:1091
|
0x04, 0x43, 0x10, 0x08, // 188:1091
|
||||||
0x04, 0x53, 0x10, 0x08, // 189:1107
|
0x04, 0x53, 0x10, 0x08, // 189:1107
|
||||||
0x04, 0x63, 0x10, 0x08, // 190:1123
|
0x04, 0x63, 0x10, 0x08, // 190:1123
|
||||||
0x04, 0x73, 0x0A, 0x06, // 191:1139
|
0x04, 0x73, 0x0A, 0x06, // 191:1139
|
||||||
0x04, 0x7D, 0x0E, 0x07, // 192:1149
|
0x04, 0x7D, 0x0E, 0x07, // 192:1149
|
||||||
0x04, 0x8B, 0x0E, 0x07, // 193:1163
|
0x04, 0x8B, 0x0E, 0x07, // 193:1163
|
||||||
0x04, 0x99, 0x0E, 0x07, // 194:1177
|
0x04, 0x99, 0x0E, 0x07, // 194:1177
|
||||||
0x04, 0xA7, 0x0E, 0x07, // 195:1191
|
0x04, 0xA7, 0x0E, 0x07, // 195:1191
|
||||||
0x04, 0xB5, 0x0E, 0x07, // 196:1205
|
0x04, 0xB5, 0x0E, 0x07, // 196:1205
|
||||||
0x04, 0xC3, 0x0E, 0x07, // 197:1219
|
0x04, 0xC3, 0x0E, 0x07, // 197:1219
|
||||||
0x04, 0xD1, 0x12, 0x0A, // 198:1233
|
0x04, 0xD1, 0x12, 0x0A, // 198:1233
|
||||||
0x04, 0xE3, 0x0C, 0x07, // 199:1251
|
0x04, 0xE3, 0x0C, 0x07, // 199:1251
|
||||||
0x04, 0xEF, 0x0C, 0x07, // 200:1263
|
0x04, 0xEF, 0x0C, 0x07, // 200:1263
|
||||||
0x04, 0xFB, 0x0C, 0x07, // 201:1275
|
0x04, 0xFB, 0x0C, 0x07, // 201:1275
|
||||||
0x05, 0x07, 0x0C, 0x07, // 202:1287
|
0x05, 0x07, 0x0C, 0x07, // 202:1287
|
||||||
0x05, 0x13, 0x0C, 0x07, // 203:1299
|
0x05, 0x13, 0x0C, 0x07, // 203:1299
|
||||||
0x05, 0x1F, 0x05, 0x03, // 204:1311
|
0x05, 0x1F, 0x05, 0x03, // 204:1311
|
||||||
0x05, 0x24, 0x04, 0x03, // 205:1316
|
0x05, 0x24, 0x04, 0x03, // 205:1316
|
||||||
0x05, 0x28, 0x04, 0x03, // 206:1320
|
0x05, 0x28, 0x04, 0x03, // 206:1320
|
||||||
0x05, 0x2C, 0x05, 0x03, // 207:1324
|
0x05, 0x2C, 0x05, 0x03, // 207:1324
|
||||||
0x05, 0x31, 0x0B, 0x07, // 208:1329
|
0x05, 0x31, 0x0B, 0x07, // 208:1329
|
||||||
0x05, 0x3C, 0x0C, 0x07, // 209:1340
|
0x05, 0x3C, 0x0C, 0x07, // 209:1340
|
||||||
0x05, 0x48, 0x0E, 0x08, // 210:1352
|
0x05, 0x48, 0x0E, 0x08, // 210:1352
|
||||||
0x05, 0x56, 0x0E, 0x08, // 211:1366
|
0x05, 0x56, 0x0E, 0x08, // 211:1366
|
||||||
0x05, 0x64, 0x0E, 0x08, // 212:1380
|
0x05, 0x64, 0x0E, 0x08, // 212:1380
|
||||||
0x05, 0x72, 0x0E, 0x08, // 213:1394
|
0x05, 0x72, 0x0E, 0x08, // 213:1394
|
||||||
0x05, 0x80, 0x0E, 0x08, // 214:1408
|
0x05, 0x80, 0x0E, 0x08, // 214:1408
|
||||||
0x05, 0x8E, 0x0A, 0x06, // 215:1422
|
0x05, 0x8E, 0x0A, 0x06, // 215:1422
|
||||||
0x05, 0x98, 0x0D, 0x08, // 216:1432
|
0x05, 0x98, 0x0D, 0x08, // 216:1432
|
||||||
0x05, 0xA5, 0x0C, 0x07, // 217:1445
|
0x05, 0xA5, 0x0C, 0x07, // 217:1445
|
||||||
0x05, 0xB1, 0x0C, 0x07, // 218:1457
|
0x05, 0xB1, 0x0C, 0x07, // 218:1457
|
||||||
0x05, 0xBD, 0x0C, 0x07, // 219:1469
|
0x05, 0xBD, 0x0C, 0x07, // 219:1469
|
||||||
0x05, 0xC9, 0x0C, 0x07, // 220:1481
|
0x05, 0xC9, 0x0C, 0x07, // 220:1481
|
||||||
0x05, 0xD5, 0x0D, 0x07, // 221:1493
|
0x05, 0xD5, 0x0D, 0x07, // 221:1493
|
||||||
0x05, 0xE2, 0x0B, 0x07, // 222:1506
|
0x05, 0xE2, 0x0B, 0x07, // 222:1506
|
||||||
0x05, 0xED, 0x0C, 0x06, // 223:1517
|
0x05, 0xED, 0x0C, 0x06, // 223:1517
|
||||||
0x05, 0xF9, 0x0A, 0x06, // 224:1529
|
0x05, 0xF9, 0x0A, 0x06, // 224:1529
|
||||||
0x06, 0x03, 0x0A, 0x06, // 225:1539
|
0x06, 0x03, 0x0A, 0x06, // 225:1539
|
||||||
0x06, 0x0D, 0x0A, 0x06, // 226:1549
|
0x06, 0x0D, 0x0A, 0x06, // 226:1549
|
||||||
0x06, 0x17, 0x0A, 0x06, // 227:1559
|
0x06, 0x17, 0x0A, 0x06, // 227:1559
|
||||||
0x06, 0x21, 0x0A, 0x06, // 228:1569
|
0x06, 0x21, 0x0A, 0x06, // 228:1569
|
||||||
0x06, 0x2B, 0x0A, 0x06, // 229:1579
|
0x06, 0x2B, 0x0A, 0x06, // 229:1579
|
||||||
0x06, 0x35, 0x10, 0x09, // 230:1589
|
0x06, 0x35, 0x10, 0x09, // 230:1589
|
||||||
0x06, 0x45, 0x0A, 0x05, // 231:1605
|
0x06, 0x45, 0x0A, 0x05, // 231:1605
|
||||||
0x06, 0x4F, 0x0A, 0x06, // 232:1615
|
0x06, 0x4F, 0x0A, 0x06, // 232:1615
|
||||||
0x06, 0x59, 0x0A, 0x06, // 233:1625
|
0x06, 0x59, 0x0A, 0x06, // 233:1625
|
||||||
0x06, 0x63, 0x0A, 0x06, // 234:1635
|
0x06, 0x63, 0x0A, 0x06, // 234:1635
|
||||||
0x06, 0x6D, 0x0A, 0x06, // 235:1645
|
0x06, 0x6D, 0x0A, 0x06, // 235:1645
|
||||||
0x06, 0x77, 0x05, 0x03, // 236:1655
|
0x06, 0x77, 0x05, 0x03, // 236:1655
|
||||||
0x06, 0x7C, 0x04, 0x03, // 237:1660
|
0x06, 0x7C, 0x04, 0x03, // 237:1660
|
||||||
0x06, 0x80, 0x05, 0x03, // 238:1664
|
0x06, 0x80, 0x05, 0x03, // 238:1664
|
||||||
0x06, 0x85, 0x05, 0x03, // 239:1669
|
0x06, 0x85, 0x05, 0x03, // 239:1669
|
||||||
0x06, 0x8A, 0x0A, 0x06, // 240:1674
|
0x06, 0x8A, 0x0A, 0x06, // 240:1674
|
||||||
0x06, 0x94, 0x0A, 0x06, // 241:1684
|
0x06, 0x94, 0x0A, 0x06, // 241:1684
|
||||||
0x06, 0x9E, 0x0A, 0x06, // 242:1694
|
0x06, 0x9E, 0x0A, 0x06, // 242:1694
|
||||||
0x06, 0xA8, 0x0A, 0x06, // 243:1704
|
0x06, 0xA8, 0x0A, 0x06, // 243:1704
|
||||||
0x06, 0xB2, 0x0A, 0x06, // 244:1714
|
0x06, 0xB2, 0x0A, 0x06, // 244:1714
|
||||||
0x06, 0xBC, 0x0A, 0x06, // 245:1724
|
0x06, 0xBC, 0x0A, 0x06, // 245:1724
|
||||||
0x06, 0xC6, 0x0A, 0x06, // 246:1734
|
0x06, 0xC6, 0x0A, 0x06, // 246:1734
|
||||||
0x06, 0xD0, 0x09, 0x05, // 247:1744
|
0x06, 0xD0, 0x09, 0x05, // 247:1744
|
||||||
0x06, 0xD9, 0x0A, 0x06, // 248:1753
|
0x06, 0xD9, 0x0A, 0x06, // 248:1753
|
||||||
0x06, 0xE3, 0x0A, 0x06, // 249:1763
|
0x06, 0xE3, 0x0A, 0x06, // 249:1763
|
||||||
0x06, 0xED, 0x0A, 0x06, // 250:1773
|
0x06, 0xED, 0x0A, 0x06, // 250:1773
|
||||||
0x06, 0xF7, 0x0A, 0x06, // 251:1783
|
0x06, 0xF7, 0x0A, 0x06, // 251:1783
|
||||||
0x07, 0x01, 0x0A, 0x06, // 252:1793
|
0x07, 0x01, 0x0A, 0x06, // 252:1793
|
||||||
0x07, 0x0B, 0x09, 0x05, // 253:1803
|
0x07, 0x0B, 0x09, 0x05, // 253:1803
|
||||||
0x07, 0x14, 0x0A, 0x06, // 254:1812
|
0x07, 0x14, 0x0A, 0x06, // 254:1812
|
||||||
0x07, 0x1E, 0x09, 0x05, // 255:1822
|
0x07, 0x1E, 0x09, 0x05, // 255:1822
|
||||||
|
|
||||||
// Font Data:
|
// Font Data:
|
||||||
0x00,0x00,0xF8,0x02, // 33
|
0x00, 0x00, 0xF8, 0x02, // 33
|
||||||
0x38,0x00,0x00,0x00,0x38, // 34
|
0x38, 0x00, 0x00, 0x00, 0x38, // 34
|
||||||
0xA0,0x03,0xE0,0x00,0xB8,0x03,0xE0,0x00,0xB8, // 35
|
0xA0, 0x03, 0xE0, 0x00, 0xB8, 0x03, 0xE0, 0x00, 0xB8, // 35
|
||||||
0x30,0x01,0x28,0x02,0xF8,0x07,0x48,0x02,0x90,0x01, // 36
|
0x30, 0x01, 0x28, 0x02, 0xF8, 0x07, 0x48, 0x02, 0x90, 0x01, // 36
|
||||||
0x00,0x00,0x30,0x00,0x48,0x00,0x30,0x03,0xC0,0x00,0xB0,0x01,0x48,0x02,0x80,0x01, // 37
|
0x00, 0x00, 0x30, 0x00, 0x48, 0x00, 0x30, 0x03, 0xC0, 0x00, 0xB0, 0x01, 0x48, 0x02, 0x80, 0x01, // 37
|
||||||
0x80,0x01,0x50,0x02,0x68,0x02,0xA8,0x02,0x18,0x01,0x80,0x03,0x80,0x02, // 38
|
0x80, 0x01, 0x50, 0x02, 0x68, 0x02, 0xA8, 0x02, 0x18, 0x01, 0x80, 0x03, 0x80, 0x02, // 38
|
||||||
0x38, // 39
|
0x38, // 39
|
||||||
0xE0,0x03,0x10,0x04,0x08,0x08, // 40
|
0xE0, 0x03, 0x10, 0x04, 0x08, 0x08, // 40
|
||||||
0x08,0x08,0x10,0x04,0xE0,0x03, // 41
|
0x08, 0x08, 0x10, 0x04, 0xE0, 0x03, // 41
|
||||||
0x28,0x00,0x18,0x00,0x28, // 42
|
0x28, 0x00, 0x18, 0x00, 0x28, // 42
|
||||||
0x40,0x00,0x40,0x00,0xF0,0x01,0x40,0x00,0x40, // 43
|
0x40, 0x00, 0x40, 0x00, 0xF0, 0x01, 0x40, 0x00, 0x40, // 43
|
||||||
0x00,0x00,0x00,0x06, // 44
|
0x00, 0x00, 0x00, 0x06, // 44
|
||||||
0x80,0x00,0x80, // 45
|
0x80, 0x00, 0x80, // 45
|
||||||
0x00,0x00,0x00,0x02, // 46
|
0x00, 0x00, 0x00, 0x02, // 46
|
||||||
0x00,0x03,0xE0,0x00,0x18, // 47
|
0x00, 0x03, 0xE0, 0x00, 0x18, // 47
|
||||||
0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 48
|
0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 48
|
||||||
0x00,0x00,0x20,0x00,0x10,0x00,0xF8,0x03, // 49
|
0x00, 0x00, 0x20, 0x00, 0x10, 0x00, 0xF8, 0x03, // 49
|
||||||
0x10,0x02,0x08,0x03,0x88,0x02,0x48,0x02,0x30,0x02, // 50
|
0x10, 0x02, 0x08, 0x03, 0x88, 0x02, 0x48, 0x02, 0x30, 0x02, // 50
|
||||||
0x10,0x01,0x08,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 51
|
0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 51
|
||||||
0xC0,0x00,0xA0,0x00,0x90,0x00,0x88,0x00,0xF8,0x03,0x80, // 52
|
0xC0, 0x00, 0xA0, 0x00, 0x90, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x80, // 52
|
||||||
0x60,0x01,0x38,0x02,0x28,0x02,0x28,0x02,0xC8,0x01, // 53
|
0x60, 0x01, 0x38, 0x02, 0x28, 0x02, 0x28, 0x02, 0xC8, 0x01, // 53
|
||||||
0xF0,0x01,0x28,0x02,0x28,0x02,0x28,0x02,0xD0,0x01, // 54
|
0xF0, 0x01, 0x28, 0x02, 0x28, 0x02, 0x28, 0x02, 0xD0, 0x01, // 54
|
||||||
0x08,0x00,0x08,0x03,0xC8,0x00,0x38,0x00,0x08, // 55
|
0x08, 0x00, 0x08, 0x03, 0xC8, 0x00, 0x38, 0x00, 0x08, // 55
|
||||||
0xB0,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0xB0,0x01, // 56
|
0xB0, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xB0, 0x01, // 56
|
||||||
0x70,0x01,0x88,0x02,0x88,0x02,0x88,0x02,0xF0,0x01, // 57
|
0x70, 0x01, 0x88, 0x02, 0x88, 0x02, 0x88, 0x02, 0xF0, 0x01, // 57
|
||||||
0x00,0x00,0x20,0x02, // 58
|
0x00, 0x00, 0x20, 0x02, // 58
|
||||||
0x00,0x00,0x20,0x06, // 59
|
0x00, 0x00, 0x20, 0x06, // 59
|
||||||
0x00,0x00,0x40,0x00,0xA0,0x00,0xA0,0x00,0x10,0x01, // 60
|
0x00, 0x00, 0x40, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 60
|
||||||
0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0,0x00,0xA0, // 61
|
0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, // 61
|
||||||
0x00,0x00,0x10,0x01,0xA0,0x00,0xA0,0x00,0x40, // 62
|
0x00, 0x00, 0x10, 0x01, 0xA0, 0x00, 0xA0, 0x00, 0x40, // 62
|
||||||
0x10,0x00,0x08,0x00,0x08,0x00,0xC8,0x02,0x48,0x00,0x30, // 63
|
0x10, 0x00, 0x08, 0x00, 0x08, 0x00, 0xC8, 0x02, 0x48, 0x00, 0x30, // 63
|
||||||
0x00,0x00,0xC0,0x03,0x30,0x04,0xD0,0x09,0x28,0x0A,0x28,0x0A,0xC8,0x0B,0x68,0x0A,0x10,0x05,0xE0,0x04, // 64
|
0x00, 0x00, 0xC0, 0x03, 0x30, 0x04, 0xD0, 0x09, 0x28, 0x0A, 0x28, 0x0A, 0xC8, 0x0B, 0x68, 0x0A, 0x10, 0x05, 0xE0, 0x04, // 64
|
||||||
0x00,0x02,0xC0,0x01,0xB0,0x00,0x88,0x00,0xB0,0x00,0xC0,0x01,0x00,0x02, // 65
|
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x88, 0x00, 0xB0, 0x00, 0xC0, 0x01, 0x00, 0x02, // 65
|
||||||
0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0xF0,0x01, // 66
|
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0xF0, 0x01, // 66
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x10,0x01, // 67
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, // 67
|
||||||
0x00,0x00,0xF8,0x03,0x08,0x02,0x08,0x02,0x10,0x01,0xE0, // 68
|
0x00, 0x00, 0xF8, 0x03, 0x08, 0x02, 0x08, 0x02, 0x10, 0x01, 0xE0, // 68
|
||||||
0x00,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02,0x48,0x02, // 69
|
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 69
|
||||||
0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x08, // 70
|
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x08, // 70
|
||||||
0x00,0x00,0xE0,0x00,0x10,0x01,0x08,0x02,0x48,0x02,0x50,0x01,0xC0, // 71
|
0x00, 0x00, 0xE0, 0x00, 0x10, 0x01, 0x08, 0x02, 0x48, 0x02, 0x50, 0x01, 0xC0, // 71
|
||||||
0x00,0x00,0xF8,0x03,0x40,0x00,0x40,0x00,0x40,0x00,0xF8,0x03, // 72
|
0x00, 0x00, 0xF8, 0x03, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xF8, 0x03, // 72
|
||||||
0x00,0x00,0xF8,0x03, // 73
|
0x00, 0x00, 0xF8, 0x03, // 73
|
||||||
0x00,0x03,0x00,0x02,0x00,0x02,0xF8,0x01, // 74
|
0x00, 0x03, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 74
|
||||||
0x00,0x00,0xF8,0x03,0x80,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 75
|
0x00, 0x00, 0xF8, 0x03, 0x80, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 75
|
||||||
0x00,0x00,0xF8,0x03,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 76
|
0x00, 0x00, 0xF8, 0x03, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 76
|
||||||
0x00,0x00,0xF8,0x03,0x30,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x30,0x00,0xF8,0x03, // 77
|
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x30, 0x00, 0xF8, 0x03, // 77
|
||||||
0x00,0x00,0xF8,0x03,0x30,0x00,0x40,0x00,0x80,0x01,0xF8,0x03, // 78
|
0x00, 0x00, 0xF8, 0x03, 0x30, 0x00, 0x40, 0x00, 0x80, 0x01, 0xF8, 0x03, // 78
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x02,0x08,0x02,0xF0,0x01, // 79
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0x08, 0x02, 0xF0, 0x01, // 79
|
||||||
0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0x48,0x00,0x30, // 80
|
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 80
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x02,0x08,0x03,0x08,0x03,0xF0,0x02, // 81
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x02, 0x08, 0x03, 0x08, 0x03, 0xF0, 0x02, // 81
|
||||||
0x00,0x00,0xF8,0x03,0x48,0x00,0x48,0x00,0xC8,0x00,0x30,0x03, // 82
|
0x00, 0x00, 0xF8, 0x03, 0x48, 0x00, 0x48, 0x00, 0xC8, 0x00, 0x30, 0x03, // 82
|
||||||
0x00,0x00,0x30,0x01,0x48,0x02,0x48,0x02,0x48,0x02,0x90,0x01, // 83
|
0x00, 0x00, 0x30, 0x01, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, 0x90, 0x01, // 83
|
||||||
0x00,0x00,0x08,0x00,0x08,0x00,0xF8,0x03,0x08,0x00,0x08, // 84
|
0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0xF8, 0x03, 0x08, 0x00, 0x08, // 84
|
||||||
0x00,0x00,0xF8,0x01,0x00,0x02,0x00,0x02,0x00,0x02,0xF8,0x01, // 85
|
0x00, 0x00, 0xF8, 0x01, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0xF8, 0x01, // 85
|
||||||
0x08,0x00,0x70,0x00,0x80,0x01,0x00,0x02,0x80,0x01,0x70,0x00,0x08, // 86
|
0x08, 0x00, 0x70, 0x00, 0x80, 0x01, 0x00, 0x02, 0x80, 0x01, 0x70, 0x00, 0x08, // 86
|
||||||
0x18,0x00,0xE0,0x01,0x00,0x02,0xF0,0x01,0x08,0x00,0xF0,0x01,0x00,0x02,0xE0,0x01,0x18, // 87
|
0x18, 0x00, 0xE0, 0x01, 0x00, 0x02, 0xF0, 0x01, 0x08, 0x00, 0xF0, 0x01, 0x00, 0x02, 0xE0, 0x01, 0x18, // 87
|
||||||
0x00,0x02,0x08,0x01,0x90,0x00,0x60,0x00,0x90,0x00,0x08,0x01,0x00,0x02, // 88
|
0x00, 0x02, 0x08, 0x01, 0x90, 0x00, 0x60, 0x00, 0x90, 0x00, 0x08, 0x01, 0x00, 0x02, // 88
|
||||||
0x08,0x00,0x10,0x00,0x20,0x00,0xC0,0x03,0x20,0x00,0x10,0x00,0x08, // 89
|
0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC0, 0x03, 0x20, 0x00, 0x10, 0x00, 0x08, // 89
|
||||||
0x08,0x03,0x88,0x02,0xC8,0x02,0x68,0x02,0x38,0x02,0x18,0x02, // 90
|
0x08, 0x03, 0x88, 0x02, 0xC8, 0x02, 0x68, 0x02, 0x38, 0x02, 0x18, 0x02, // 90
|
||||||
0x00,0x00,0xF8,0x0F,0x08,0x08, // 91
|
0x00, 0x00, 0xF8, 0x0F, 0x08, 0x08, // 91
|
||||||
0x18,0x00,0xE0,0x00,0x00,0x03, // 92
|
0x18, 0x00, 0xE0, 0x00, 0x00, 0x03, // 92
|
||||||
0x08,0x08,0xF8,0x0F, // 93
|
0x08, 0x08, 0xF8, 0x0F, // 93
|
||||||
0x40,0x00,0x30,0x00,0x08,0x00,0x30,0x00,0x40, // 94
|
0x40, 0x00, 0x30, 0x00, 0x08, 0x00, 0x30, 0x00, 0x40, // 94
|
||||||
0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08,0x00,0x08, // 95
|
0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, // 95
|
||||||
0x08,0x00,0x10, // 96
|
0x08, 0x00, 0x10, // 96
|
||||||
0x00,0x00,0x00,0x03,0xA0,0x02,0xA0,0x02,0xE0,0x03, // 97
|
0x00, 0x00, 0x00, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xE0, 0x03, // 97
|
||||||
0x00,0x00,0xF8,0x03,0x20,0x02,0x20,0x02,0xC0,0x01, // 98
|
0x00, 0x00, 0xF8, 0x03, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 98
|
||||||
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0x40,0x01, // 99
|
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0x40, 0x01, // 99
|
||||||
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xF8,0x03, // 100
|
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xF8, 0x03, // 100
|
||||||
0x00,0x00,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 101
|
0x00, 0x00, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 101
|
||||||
0x20,0x00,0xF0,0x03,0x28, // 102
|
0x20, 0x00, 0xF0, 0x03, 0x28, // 102
|
||||||
0x00,0x00,0xC0,0x05,0x20,0x0A,0x20,0x0A,0xE0,0x07, // 103
|
0x00, 0x00, 0xC0, 0x05, 0x20, 0x0A, 0x20, 0x0A, 0xE0, 0x07, // 103
|
||||||
0x00,0x00,0xF8,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 104
|
0x00, 0x00, 0xF8, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 104
|
||||||
0x00,0x00,0xE8,0x03, // 105
|
0x00, 0x00, 0xE8, 0x03, // 105
|
||||||
0x00,0x08,0xE8,0x07, // 106
|
0x00, 0x08, 0xE8, 0x07, // 106
|
||||||
0xF8,0x03,0x80,0x00,0xC0,0x01,0x20,0x02, // 107
|
0xF8, 0x03, 0x80, 0x00, 0xC0, 0x01, 0x20, 0x02, // 107
|
||||||
0x00,0x00,0xF8,0x03, // 108
|
0x00, 0x00, 0xF8, 0x03, // 108
|
||||||
0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 109
|
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 109
|
||||||
0x00,0x00,0xE0,0x03,0x20,0x00,0x20,0x00,0xC0,0x03, // 110
|
0x00, 0x00, 0xE0, 0x03, 0x20, 0x00, 0x20, 0x00, 0xC0, 0x03, // 110
|
||||||
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xC0,0x01, // 111
|
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 111
|
||||||
0x00,0x00,0xE0,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 112
|
0x00, 0x00, 0xE0, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 112
|
||||||
0x00,0x00,0xC0,0x01,0x20,0x02,0x20,0x02,0xE0,0x0F, // 113
|
0x00, 0x00, 0xC0, 0x01, 0x20, 0x02, 0x20, 0x02, 0xE0, 0x0F, // 113
|
||||||
0x00,0x00,0xE0,0x03,0x20, // 114
|
0x00, 0x00, 0xE0, 0x03, 0x20, // 114
|
||||||
0x40,0x02,0xA0,0x02,0xA0,0x02,0x20,0x01, // 115
|
0x40, 0x02, 0xA0, 0x02, 0xA0, 0x02, 0x20, 0x01, // 115
|
||||||
0x20,0x00,0xF8,0x03,0x20,0x02, // 116
|
0x20, 0x00, 0xF8, 0x03, 0x20, 0x02, // 116
|
||||||
0x00,0x00,0xE0,0x01,0x00,0x02,0x00,0x02,0xE0,0x03, // 117
|
0x00, 0x00, 0xE0, 0x01, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 117
|
||||||
0x20,0x00,0xC0,0x01,0x00,0x02,0xC0,0x01,0x20, // 118
|
0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, // 118
|
||||||
0xE0,0x01,0x00,0x02,0xC0,0x01,0x20,0x00,0xC0,0x01,0x00,0x02,0xE0,0x01, // 119
|
0xE0, 0x01, 0x00, 0x02, 0xC0, 0x01, 0x20, 0x00, 0xC0, 0x01, 0x00, 0x02, 0xE0, 0x01, // 119
|
||||||
0x20,0x02,0x40,0x01,0x80,0x00,0x40,0x01,0x20,0x02, // 120
|
0x20, 0x02, 0x40, 0x01, 0x80, 0x00, 0x40, 0x01, 0x20, 0x02, // 120
|
||||||
0x20,0x00,0xC0,0x09,0x00,0x06,0xC0,0x01,0x20, // 121
|
0x20, 0x00, 0xC0, 0x09, 0x00, 0x06, 0xC0, 0x01, 0x20, // 121
|
||||||
0x20,0x02,0x20,0x03,0xA0,0x02,0x60,0x02,0x20,0x02, // 122
|
0x20, 0x02, 0x20, 0x03, 0xA0, 0x02, 0x60, 0x02, 0x20, 0x02, // 122
|
||||||
0x80,0x00,0x78,0x0F,0x08,0x08, // 123
|
0x80, 0x00, 0x78, 0x0F, 0x08, 0x08, // 123
|
||||||
0x00,0x00,0xF8,0x0F, // 124
|
0x00, 0x00, 0xF8, 0x0F, // 124
|
||||||
0x08,0x08,0x78,0x0F,0x80, // 125
|
0x08, 0x08, 0x78, 0x0F, 0x80, // 125
|
||||||
0xC0,0x00,0x40,0x00,0xC0,0x00,0x80,0x00,0xC0, // 126
|
0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x80, 0x00, 0xC0, // 126
|
||||||
0x00,0x00,0xA0,0x0F, // 161
|
0x00, 0x00, 0xA0, 0x0F, // 161
|
||||||
0x00,0x00,0xC0,0x01,0xA0,0x0F,0x78,0x02,0x40,0x01, // 162
|
0x00, 0x00, 0xC0, 0x01, 0xA0, 0x0F, 0x78, 0x02, 0x40, 0x01, // 162
|
||||||
0x40,0x02,0x70,0x03,0xC8,0x02,0x48,0x02,0x08,0x02,0x10,0x02, // 163
|
0x40, 0x02, 0x70, 0x03, 0xC8, 0x02, 0x48, 0x02, 0x08, 0x02, 0x10, 0x02, // 163
|
||||||
0x00,0x00,0xE0,0x01,0x20,0x01,0x20,0x01,0xE0,0x01, // 164
|
0x00, 0x00, 0xE0, 0x01, 0x20, 0x01, 0x20, 0x01, 0xE0, 0x01, // 164
|
||||||
0x48,0x01,0x70,0x01,0xC0,0x03,0x70,0x01,0x48,0x01, // 165
|
0x48, 0x01, 0x70, 0x01, 0xC0, 0x03, 0x70, 0x01, 0x48, 0x01, // 165
|
||||||
0x00,0x00,0x38,0x0F, // 166
|
0x00, 0x00, 0x38, 0x0F, // 166
|
||||||
0xD0,0x04,0x28,0x09,0x48,0x09,0x48,0x0A,0x90,0x05, // 167
|
0xD0, 0x04, 0x28, 0x09, 0x48, 0x09, 0x48, 0x0A, 0x90, 0x05, // 167
|
||||||
0x08,0x00,0x00,0x00,0x08, // 168
|
0x08, 0x00, 0x00, 0x00, 0x08, // 168
|
||||||
0xE0,0x00,0x10,0x01,0x48,0x02,0xA8,0x02,0xA8,0x02,0x10,0x01,0xE0, // 169
|
0xE0, 0x00, 0x10, 0x01, 0x48, 0x02, 0xA8, 0x02, 0xA8, 0x02, 0x10, 0x01, 0xE0, // 169
|
||||||
0x68,0x00,0x68,0x00,0x68,0x00,0x78, // 170
|
0x68, 0x00, 0x68, 0x00, 0x68, 0x00, 0x78, // 170
|
||||||
0x00,0x00,0x80,0x01,0x40,0x02,0x80,0x01,0x40,0x02, // 171
|
0x00, 0x00, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, // 171
|
||||||
0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0xE0, // 172
|
0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xE0, // 172
|
||||||
0x80,0x00,0x80, // 173
|
0x80, 0x00, 0x80, // 173
|
||||||
0xE0,0x00,0x10,0x01,0xE8,0x02,0x68,0x02,0xC8,0x02,0x10,0x01,0xE0, // 174
|
0xE0, 0x00, 0x10, 0x01, 0xE8, 0x02, 0x68, 0x02, 0xC8, 0x02, 0x10, 0x01, 0xE0, // 174
|
||||||
0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02,0x00,0x02, // 175
|
0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, 0x00, 0x02, // 175
|
||||||
0x00,0x00,0x38,0x00,0x28,0x00,0x38, // 176
|
0x00, 0x00, 0x38, 0x00, 0x28, 0x00, 0x38, // 176
|
||||||
0x40,0x02,0x40,0x02,0xF0,0x03,0x40,0x02,0x40,0x02, // 177
|
0x40, 0x02, 0x40, 0x02, 0xF0, 0x03, 0x40, 0x02, 0x40, 0x02, // 177
|
||||||
0x48,0x00,0x68,0x00,0x58, // 178
|
0x48, 0x00, 0x68, 0x00, 0x58, // 178
|
||||||
0x48,0x00,0x58,0x00,0x68, // 179
|
0x48, 0x00, 0x58, 0x00, 0x68, // 179
|
||||||
0x00,0x00,0x10,0x00,0x08, // 180
|
0x00, 0x00, 0x10, 0x00, 0x08, // 180
|
||||||
0x00,0x00,0xE0,0x0F,0x00,0x02,0x00,0x02,0xE0,0x03, // 181
|
0x00, 0x00, 0xE0, 0x0F, 0x00, 0x02, 0x00, 0x02, 0xE0, 0x03, // 181
|
||||||
0x70,0x00,0xF8,0x0F,0x08,0x00,0xF8,0x0F,0x08, // 182
|
0x70, 0x00, 0xF8, 0x0F, 0x08, 0x00, 0xF8, 0x0F, 0x08, // 182
|
||||||
0x00,0x00,0x40, // 183
|
0x00, 0x00, 0x40, // 183
|
||||||
0x00,0x00,0x00,0x14,0x00,0x18, // 184
|
0x00, 0x00, 0x00, 0x14, 0x00, 0x18, // 184
|
||||||
0x00,0x00,0x10,0x00,0x78, // 185
|
0x00, 0x00, 0x10, 0x00, 0x78, // 185
|
||||||
0x30,0x00,0x48,0x00,0x48,0x00,0x30, // 186
|
0x30, 0x00, 0x48, 0x00, 0x48, 0x00, 0x30, // 186
|
||||||
0x00,0x00,0x40,0x02,0x80,0x01,0x40,0x02,0x80,0x01, // 187
|
0x00, 0x00, 0x40, 0x02, 0x80, 0x01, 0x40, 0x02, 0x80, 0x01, // 187
|
||||||
0x00,0x00,0x10,0x02,0x78,0x01,0xC0,0x00,0x20,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 188
|
0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0xC0, 0x00, 0x20, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 188
|
||||||
0x00,0x00,0x10,0x02,0x78,0x01,0x80,0x00,0x60,0x00,0x50,0x02,0x48,0x03,0xC0,0x02, // 189
|
0x00, 0x00, 0x10, 0x02, 0x78, 0x01, 0x80, 0x00, 0x60, 0x00, 0x50, 0x02, 0x48, 0x03, 0xC0, 0x02, // 189
|
||||||
0x48,0x00,0x58,0x00,0x68,0x03,0x80,0x00,0x60,0x01,0x90,0x01,0xC8,0x03,0x00,0x01, // 190
|
0x48, 0x00, 0x58, 0x00, 0x68, 0x03, 0x80, 0x00, 0x60, 0x01, 0x90, 0x01, 0xC8, 0x03, 0x00, 0x01, // 190
|
||||||
0x00,0x00,0x00,0x06,0x00,0x09,0xA0,0x09,0x00,0x04, // 191
|
0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0xA0, 0x09, 0x00, 0x04, // 191
|
||||||
0x00,0x02,0xC0,0x01,0xB0,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 192
|
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 192
|
||||||
0x00,0x02,0xC0,0x01,0xB0,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 193
|
0x00, 0x02, 0xC0, 0x01, 0xB0, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 193
|
||||||
0x00,0x02,0xC0,0x01,0xB2,0x00,0x89,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 194
|
0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x89, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 194
|
||||||
0x00,0x02,0xC2,0x01,0xB1,0x00,0x8A,0x00,0xB1,0x00,0xC0,0x01,0x00,0x02, // 195
|
0x00, 0x02, 0xC2, 0x01, 0xB1, 0x00, 0x8A, 0x00, 0xB1, 0x00, 0xC0, 0x01, 0x00, 0x02, // 195
|
||||||
0x00,0x02,0xC0,0x01,0xB2,0x00,0x88,0x00,0xB2,0x00,0xC0,0x01,0x00,0x02, // 196
|
0x00, 0x02, 0xC0, 0x01, 0xB2, 0x00, 0x88, 0x00, 0xB2, 0x00, 0xC0, 0x01, 0x00, 0x02, // 196
|
||||||
0x00,0x02,0xC0,0x01,0xBE,0x00,0x8A,0x00,0xBE,0x00,0xC0,0x01,0x00,0x02, // 197
|
0x00, 0x02, 0xC0, 0x01, 0xBE, 0x00, 0x8A, 0x00, 0xBE, 0x00, 0xC0, 0x01, 0x00, 0x02, // 197
|
||||||
0x00,0x03,0xC0,0x00,0xE0,0x00,0x98,0x00,0x88,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x48,0x02, // 198
|
0x00, 0x03, 0xC0, 0x00, 0xE0, 0x00, 0x98, 0x00, 0x88, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x48, 0x02, // 198
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x08,0x16,0x08,0x1A,0x10,0x01, // 199
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x08, 0x16, 0x08, 0x1A, 0x10, 0x01, // 199
|
||||||
0x00,0x00,0xF8,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 200
|
0x00, 0x00, 0xF8, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 200
|
||||||
0x00,0x00,0xF8,0x03,0x48,0x02,0x4A,0x02,0x49,0x02,0x48,0x02, // 201
|
0x00, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x4A, 0x02, 0x49, 0x02, 0x48, 0x02, // 201
|
||||||
0x00,0x00,0xFA,0x03,0x49,0x02,0x4A,0x02,0x48,0x02,0x48,0x02, // 202
|
0x00, 0x00, 0xFA, 0x03, 0x49, 0x02, 0x4A, 0x02, 0x48, 0x02, 0x48, 0x02, // 202
|
||||||
0x00,0x00,0xF8,0x03,0x4A,0x02,0x48,0x02,0x4A,0x02,0x48,0x02, // 203
|
0x00, 0x00, 0xF8, 0x03, 0x4A, 0x02, 0x48, 0x02, 0x4A, 0x02, 0x48, 0x02, // 203
|
||||||
0x00,0x00,0xF9,0x03,0x02, // 204
|
0x00, 0x00, 0xF9, 0x03, 0x02, // 204
|
||||||
0x02,0x00,0xF9,0x03, // 205
|
0x02, 0x00, 0xF9, 0x03, // 205
|
||||||
0x01,0x00,0xFA,0x03, // 206
|
0x01, 0x00, 0xFA, 0x03, // 206
|
||||||
0x02,0x00,0xF8,0x03,0x02, // 207
|
0x02, 0x00, 0xF8, 0x03, 0x02, // 207
|
||||||
0x40,0x00,0xF8,0x03,0x48,0x02,0x48,0x02,0x10,0x01,0xE0, // 208
|
0x40, 0x00, 0xF8, 0x03, 0x48, 0x02, 0x48, 0x02, 0x10, 0x01, 0xE0, // 208
|
||||||
0x00,0x00,0xFA,0x03,0x31,0x00,0x42,0x00,0x81,0x01,0xF8,0x03, // 209
|
0x00, 0x00, 0xFA, 0x03, 0x31, 0x00, 0x42, 0x00, 0x81, 0x01, 0xF8, 0x03, // 209
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x09,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 210
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 210
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x08,0x02,0xF0,0x01, // 211
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x08, 0x02, 0xF0, 0x01, // 211
|
||||||
0x00,0x00,0xF0,0x01,0x08,0x02,0x0A,0x02,0x09,0x02,0x0A,0x02,0xF0,0x01, // 212
|
0x00, 0x00, 0xF0, 0x01, 0x08, 0x02, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0xF0, 0x01, // 212
|
||||||
0x00,0x00,0xF0,0x01,0x0A,0x02,0x09,0x02,0x0A,0x02,0x09,0x02,0xF0,0x01, // 213
|
0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x09, 0x02, 0x0A, 0x02, 0x09, 0x02, 0xF0, 0x01, // 213
|
||||||
0x00,0x00,0xF0,0x01,0x0A,0x02,0x08,0x02,0x0A,0x02,0x08,0x02,0xF0,0x01, // 214
|
0x00, 0x00, 0xF0, 0x01, 0x0A, 0x02, 0x08, 0x02, 0x0A, 0x02, 0x08, 0x02, 0xF0, 0x01, // 214
|
||||||
0x10,0x01,0xA0,0x00,0xE0,0x00,0xA0,0x00,0x10,0x01, // 215
|
0x10, 0x01, 0xA0, 0x00, 0xE0, 0x00, 0xA0, 0x00, 0x10, 0x01, // 215
|
||||||
0x00,0x00,0xF0,0x02,0x08,0x03,0xC8,0x02,0x28,0x02,0x18,0x03,0xE8, // 216
|
0x00, 0x00, 0xF0, 0x02, 0x08, 0x03, 0xC8, 0x02, 0x28, 0x02, 0x18, 0x03, 0xE8, // 216
|
||||||
0x00,0x00,0xF8,0x01,0x01,0x02,0x02,0x02,0x00,0x02,0xF8,0x01, // 217
|
0x00, 0x00, 0xF8, 0x01, 0x01, 0x02, 0x02, 0x02, 0x00, 0x02, 0xF8, 0x01, // 217
|
||||||
0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x00,0x02,0xF8,0x01, // 218
|
0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x00, 0x02, 0xF8, 0x01, // 218
|
||||||
0x00,0x00,0xF8,0x01,0x02,0x02,0x01,0x02,0x02,0x02,0xF8,0x01, // 219
|
0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x01, 0x02, 0x02, 0x02, 0xF8, 0x01, // 219
|
||||||
0x00,0x00,0xF8,0x01,0x02,0x02,0x00,0x02,0x02,0x02,0xF8,0x01, // 220
|
0x00, 0x00, 0xF8, 0x01, 0x02, 0x02, 0x00, 0x02, 0x02, 0x02, 0xF8, 0x01, // 220
|
||||||
0x08,0x00,0x10,0x00,0x20,0x00,0xC2,0x03,0x21,0x00,0x10,0x00,0x08, // 221
|
0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0xC2, 0x03, 0x21, 0x00, 0x10, 0x00, 0x08, // 221
|
||||||
0x00,0x00,0xF8,0x03,0x10,0x01,0x10,0x01,0x10,0x01,0xE0, // 222
|
0x00, 0x00, 0xF8, 0x03, 0x10, 0x01, 0x10, 0x01, 0x10, 0x01, 0xE0, // 222
|
||||||
0x00,0x00,0xF0,0x03,0x08,0x01,0x48,0x02,0xB0,0x02,0x80,0x01, // 223
|
0x00, 0x00, 0xF0, 0x03, 0x08, 0x01, 0x48, 0x02, 0xB0, 0x02, 0x80, 0x01, // 223
|
||||||
0x00,0x00,0x00,0x03,0xA4,0x02,0xA8,0x02,0xE0,0x03, // 224
|
0x00, 0x00, 0x00, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE0, 0x03, // 224
|
||||||
0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE0,0x03, // 225
|
0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE0, 0x03, // 225
|
||||||
0x00,0x00,0x00,0x03,0xA8,0x02,0xA4,0x02,0xE8,0x03, // 226
|
0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA4, 0x02, 0xE8, 0x03, // 226
|
||||||
0x00,0x00,0x08,0x03,0xA4,0x02,0xA8,0x02,0xE4,0x03, // 227
|
0x00, 0x00, 0x08, 0x03, 0xA4, 0x02, 0xA8, 0x02, 0xE4, 0x03, // 227
|
||||||
0x00,0x00,0x00,0x03,0xA8,0x02,0xA0,0x02,0xE8,0x03, // 228
|
0x00, 0x00, 0x00, 0x03, 0xA8, 0x02, 0xA0, 0x02, 0xE8, 0x03, // 228
|
||||||
0x00,0x00,0x00,0x03,0xAE,0x02,0xAA,0x02,0xEE,0x03, // 229
|
0x00, 0x00, 0x00, 0x03, 0xAE, 0x02, 0xAA, 0x02, 0xEE, 0x03, // 229
|
||||||
0x00,0x00,0x40,0x03,0xA0,0x02,0xA0,0x02,0xC0,0x01,0xA0,0x02,0xA0,0x02,0xC0,0x02, // 230
|
0x00, 0x00, 0x40, 0x03, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x01, 0xA0, 0x02, 0xA0, 0x02, 0xC0, 0x02, // 230
|
||||||
0x00,0x00,0xC0,0x01,0x20,0x16,0x20,0x1A,0x40,0x01, // 231
|
0x00, 0x00, 0xC0, 0x01, 0x20, 0x16, 0x20, 0x1A, 0x40, 0x01, // 231
|
||||||
0x00,0x00,0xC0,0x01,0xA4,0x02,0xA8,0x02,0xC0,0x02, // 232
|
0x00, 0x00, 0xC0, 0x01, 0xA4, 0x02, 0xA8, 0x02, 0xC0, 0x02, // 232
|
||||||
0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC0,0x02, // 233
|
0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC0, 0x02, // 233
|
||||||
0x00,0x00,0xC0,0x01,0xA8,0x02,0xA4,0x02,0xC8,0x02, // 234
|
0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA4, 0x02, 0xC8, 0x02, // 234
|
||||||
0x00,0x00,0xC0,0x01,0xA8,0x02,0xA0,0x02,0xC8,0x02, // 235
|
0x00, 0x00, 0xC0, 0x01, 0xA8, 0x02, 0xA0, 0x02, 0xC8, 0x02, // 235
|
||||||
0x00,0x00,0xE4,0x03,0x08, // 236
|
0x00, 0x00, 0xE4, 0x03, 0x08, // 236
|
||||||
0x08,0x00,0xE4,0x03, // 237
|
0x08, 0x00, 0xE4, 0x03, // 237
|
||||||
0x08,0x00,0xE4,0x03,0x08, // 238
|
0x08, 0x00, 0xE4, 0x03, 0x08, // 238
|
||||||
0x08,0x00,0xE0,0x03,0x08, // 239
|
0x08, 0x00, 0xE0, 0x03, 0x08, // 239
|
||||||
0x00,0x00,0xC0,0x01,0x28,0x02,0x38,0x02,0xE0,0x01, // 240
|
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x38, 0x02, 0xE0, 0x01, // 240
|
||||||
0x00,0x00,0xE8,0x03,0x24,0x00,0x28,0x00,0xC4,0x03, // 241
|
0x00, 0x00, 0xE8, 0x03, 0x24, 0x00, 0x28, 0x00, 0xC4, 0x03, // 241
|
||||||
0x00,0x00,0xC0,0x01,0x24,0x02,0x28,0x02,0xC0,0x01, // 242
|
0x00, 0x00, 0xC0, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC0, 0x01, // 242
|
||||||
0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC0,0x01, // 243
|
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC0, 0x01, // 243
|
||||||
0x00,0x00,0xC0,0x01,0x28,0x02,0x24,0x02,0xC8,0x01, // 244
|
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x24, 0x02, 0xC8, 0x01, // 244
|
||||||
0x00,0x00,0xC8,0x01,0x24,0x02,0x28,0x02,0xC4,0x01, // 245
|
0x00, 0x00, 0xC8, 0x01, 0x24, 0x02, 0x28, 0x02, 0xC4, 0x01, // 245
|
||||||
0x00,0x00,0xC0,0x01,0x28,0x02,0x20,0x02,0xC8,0x01, // 246
|
0x00, 0x00, 0xC0, 0x01, 0x28, 0x02, 0x20, 0x02, 0xC8, 0x01, // 246
|
||||||
0x40,0x00,0x40,0x00,0x50,0x01,0x40,0x00,0x40, // 247
|
0x40, 0x00, 0x40, 0x00, 0x50, 0x01, 0x40, 0x00, 0x40, // 247
|
||||||
0x00,0x00,0xC0,0x02,0xA0,0x03,0x60,0x02,0xA0,0x01, // 248
|
0x00, 0x00, 0xC0, 0x02, 0xA0, 0x03, 0x60, 0x02, 0xA0, 0x01, // 248
|
||||||
0x00,0x00,0xE0,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 249
|
0x00, 0x00, 0xE0, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 249
|
||||||
0x00,0x00,0xE0,0x01,0x08,0x02,0x04,0x02,0xE0,0x03, // 250
|
0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x04, 0x02, 0xE0, 0x03, // 250
|
||||||
0x00,0x00,0xE8,0x01,0x04,0x02,0x08,0x02,0xE0,0x03, // 251
|
0x00, 0x00, 0xE8, 0x01, 0x04, 0x02, 0x08, 0x02, 0xE0, 0x03, // 251
|
||||||
0x00,0x00,0xE0,0x01,0x08,0x02,0x00,0x02,0xE8,0x03, // 252
|
0x00, 0x00, 0xE0, 0x01, 0x08, 0x02, 0x00, 0x02, 0xE8, 0x03, // 252
|
||||||
0x20,0x00,0xC0,0x09,0x08,0x06,0xC4,0x01,0x20, // 253
|
0x20, 0x00, 0xC0, 0x09, 0x08, 0x06, 0xC4, 0x01, 0x20, // 253
|
||||||
0x00,0x00,0xF8,0x0F,0x20,0x02,0x20,0x02,0xC0,0x01, // 254
|
0x00, 0x00, 0xF8, 0x0F, 0x20, 0x02, 0x20, 0x02, 0xC0, 0x01, // 254
|
||||||
0x20,0x00,0xC8,0x09,0x00,0x06,0xC8,0x01,0x20 // 255
|
0x20, 0x00, 0xC8, 0x09, 0x00, 0x06, 0xC8, 0x01, 0x20 // 255
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
#define SATELLITE_IMAGE_WIDTH 16
|
#define SATELLITE_IMAGE_WIDTH 16
|
||||||
#define SATELLITE_IMAGE_HEIGHT 15
|
#define SATELLITE_IMAGE_HEIGHT 15
|
||||||
const uint8_t SATELLITE_IMAGE[] PROGMEM = {
|
const uint8_t SATELLITE_IMAGE[] PROGMEM = {0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02,
|
||||||
0x00, 0x08, 0x00, 0x1C, 0x00, 0x0E, 0x20, 0x07, 0x70, 0x02, 0xF8, 0x00,
|
0xF8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54,
|
||||||
0xF0, 0x01, 0xE0, 0x03, 0xC8, 0x01, 0x9C, 0x54, 0x0E, 0x52, 0x07, 0x48,
|
0x0E, 0x52, 0x07, 0x48, 0x02, 0x26, 0x00, 0x10, 0x00, 0x0E};
|
||||||
0x02, 0x26, 0x00, 0x10, 0x00, 0x0E
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const
|
const
|
||||||
#include "icon.xbm"
|
#include "icon.xbm"
|
||||||
|
|||||||
35
src/lock.cpp
Normal file
35
src/lock.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
#include "lock.h"
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
Lock::Lock()
|
||||||
|
{
|
||||||
|
handle = xSemaphoreCreateBinary();
|
||||||
|
assert(handle);
|
||||||
|
assert(xSemaphoreGive(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock::lock()
|
||||||
|
{
|
||||||
|
assert(xSemaphoreTake(handle, portMAX_DELAY));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lock::unlock()
|
||||||
|
{
|
||||||
|
assert(xSemaphoreGive(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
LockGuard::LockGuard(Lock *lock) : lock(lock)
|
||||||
|
{
|
||||||
|
lock->lock();
|
||||||
|
}
|
||||||
|
|
||||||
|
LockGuard::~LockGuard()
|
||||||
|
{
|
||||||
|
lock->unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
46
src/lock.h
Normal file
46
src/lock.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
|
||||||
|
namespace meshtastic
|
||||||
|
{
|
||||||
|
|
||||||
|
// Simple wrapper around FreeRTOS API for implementing a mutex lock.
|
||||||
|
class Lock
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Lock();
|
||||||
|
|
||||||
|
Lock(const Lock &) = delete;
|
||||||
|
Lock &operator=(const Lock &) = delete;
|
||||||
|
|
||||||
|
/// Locks the lock.
|
||||||
|
//
|
||||||
|
// Must not be called from an ISR.
|
||||||
|
void lock();
|
||||||
|
|
||||||
|
// Unlocks the lock.
|
||||||
|
//
|
||||||
|
// Must not be called from an ISR.
|
||||||
|
void unlock();
|
||||||
|
|
||||||
|
private:
|
||||||
|
SemaphoreHandle_t handle;
|
||||||
|
};
|
||||||
|
|
||||||
|
// RAII lock guard.
|
||||||
|
class LockGuard
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LockGuard(Lock *lock);
|
||||||
|
~LockGuard();
|
||||||
|
|
||||||
|
LockGuard(const LockGuard &) = delete;
|
||||||
|
LockGuard &operator=(const LockGuard &) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Lock *lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
429
src/main.cpp
429
src/main.cpp
@@ -21,23 +21,22 @@
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "rom/rtc.h"
|
|
||||||
#include <driver/rtc_io.h>
|
|
||||||
#include <TinyGPS++.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include "BluetoothUtil.h"
|
#include "BluetoothUtil.h"
|
||||||
#include "MeshBluetoothService.h"
|
|
||||||
#include "MeshService.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "screen.h"
|
#include "MeshBluetoothService.h"
|
||||||
|
#include "MeshRadio.h"
|
||||||
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
|
#include "PowerFSM.h"
|
||||||
|
#include "configuration.h"
|
||||||
#include "esp32/pm.h"
|
#include "esp32/pm.h"
|
||||||
#include "esp_pm.h"
|
#include "esp_pm.h"
|
||||||
#include "MeshRadio.h"
|
#include "rom/rtc.h"
|
||||||
|
#include "screen.h"
|
||||||
#include "sleep.h"
|
#include "sleep.h"
|
||||||
#include "PowerFSM.h"
|
#include <Wire.h>
|
||||||
|
#include <driver/rtc_io.h>
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
#include "axp20x.h"
|
#include "axp20x.h"
|
||||||
@@ -45,6 +44,14 @@ AXP20X_Class axp;
|
|||||||
bool pmu_irq = false;
|
bool pmu_irq = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Global Screen singleton
|
||||||
|
#ifdef I2C_SDA
|
||||||
|
meshtastic::Screen screen(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
|
||||||
|
#else
|
||||||
|
// Fake values for pins to keep build happy, we won't ever initialize it.
|
||||||
|
meshtastic::Screen screen(SSD1306_ADDRESS, 0, 0);
|
||||||
|
#endif
|
||||||
|
|
||||||
// these flags are all in bss so they default false
|
// these flags are all in bss so they default false
|
||||||
bool isCharging;
|
bool isCharging;
|
||||||
bool isUSBPowered;
|
bool isUSBPowered;
|
||||||
@@ -60,84 +67,74 @@ bool bluetoothOn;
|
|||||||
|
|
||||||
void scanI2Cdevice(void)
|
void scanI2Cdevice(void)
|
||||||
{
|
{
|
||||||
byte err, addr;
|
byte err, addr;
|
||||||
int nDevices = 0;
|
int nDevices = 0;
|
||||||
for (addr = 1; addr < 127; addr++)
|
for (addr = 1; addr < 127; addr++) {
|
||||||
{
|
Wire.beginTransmission(addr);
|
||||||
Wire.beginTransmission(addr);
|
err = Wire.endTransmission();
|
||||||
err = Wire.endTransmission();
|
if (err == 0) {
|
||||||
if (err == 0)
|
DEBUG_MSG("I2C device found at address 0x%x\n", addr);
|
||||||
{
|
|
||||||
DEBUG_MSG("I2C device found at address 0x%x\n", addr);
|
|
||||||
|
|
||||||
nDevices++;
|
nDevices++;
|
||||||
|
|
||||||
if (addr == SSD1306_ADDRESS)
|
if (addr == SSD1306_ADDRESS) {
|
||||||
{
|
ssd1306_found = true;
|
||||||
ssd1306_found = true;
|
DEBUG_MSG("ssd1306 display found\n");
|
||||||
DEBUG_MSG("ssd1306 display found\n");
|
}
|
||||||
}
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (addr == AXP192_SLAVE_ADDRESS)
|
if (addr == AXP192_SLAVE_ADDRESS) {
|
||||||
{
|
axp192_found = true;
|
||||||
axp192_found = true;
|
DEBUG_MSG("axp192 PMU found\n");
|
||||||
DEBUG_MSG("axp192 PMU found\n");
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
} else if (err == 4) {
|
||||||
|
DEBUG_MSG("Unknow error at address 0x%x\n", addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (err == 4)
|
if (nDevices == 0)
|
||||||
{
|
DEBUG_MSG("No I2C devices found\n");
|
||||||
DEBUG_MSG("Unknow error at address 0x%x\n", addr);
|
else
|
||||||
}
|
DEBUG_MSG("done\n");
|
||||||
}
|
|
||||||
if (nDevices == 0)
|
|
||||||
DEBUG_MSG("No I2C devices found\n");
|
|
||||||
else
|
|
||||||
DEBUG_MSG("done\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Init the power manager chip
|
* Init the power manager chip
|
||||||
*
|
*
|
||||||
* axp192 power
|
* axp192 power
|
||||||
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192 share the same i2c bus, instead use ssd1306 sleep mode
|
DCDC1 0.7-3.5V @ 1200mA max -> OLED // If you turn this off you'll lose comms to the axp192 because the OLED and the axp192
|
||||||
DCDC2 -> unused
|
share the same i2c bus, instead use ssd1306 sleep mode DCDC2 -> unused DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!) LDO1
|
||||||
DCDC3 0.7-3.5V @ 700mA max -> ESP32 (keep this on!)
|
30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can
|
||||||
LDO1 30mA -> charges GPS backup battery // charges the tiny J13 battery by the GPS to power the GPS ram (for a couple of days), can not be turned off
|
not be turned off LDO2 200mA -> LORA LDO3 200mA -> GPS
|
||||||
LDO2 200mA -> LORA
|
|
||||||
LDO3 200mA -> GPS
|
|
||||||
*/
|
*/
|
||||||
void axp192Init()
|
void axp192Init()
|
||||||
{
|
{
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (axp192_found)
|
if (axp192_found) {
|
||||||
{
|
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS)) {
|
||||||
if (!axp.begin(Wire, AXP192_SLAVE_ADDRESS))
|
DEBUG_MSG("AXP192 Begin PASS\n");
|
||||||
{
|
|
||||||
DEBUG_MSG("AXP192 Begin PASS\n");
|
|
||||||
|
|
||||||
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
// axp.setChgLEDMode(LED_BLINK_4HZ);
|
||||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("----------------------------------------\n");
|
DEBUG_MSG("----------------------------------------\n");
|
||||||
|
|
||||||
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
axp.setPowerOutPut(AXP192_LDO2, AXP202_ON); // LORA radio
|
||||||
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
axp.setPowerOutPut(AXP192_LDO3, AXP202_ON); // GPS main power
|
||||||
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
axp.setPowerOutPut(AXP192_DCDC2, AXP202_ON);
|
||||||
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
axp.setPowerOutPut(AXP192_EXTEN, AXP202_ON);
|
||||||
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
axp.setPowerOutPut(AXP192_DCDC1, AXP202_ON);
|
||||||
axp.setDCDC1Voltage(3300); // for the OLED power
|
axp.setDCDC1Voltage(3300); // for the OLED power
|
||||||
|
|
||||||
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("DCDC1: %s\n", axp.isDCDC1Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("DCDC2: %s\n", axp.isDCDC2Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("LDO2: %s\n", axp.isLDO2Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("LDO3: %s\n", axp.isLDO3Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
// cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms.
|
// cribbing from https://github.com/m5stack/M5StickC/blob/master/src/AXP192.cpp to fix charger to be more like 300ms.
|
||||||
@@ -158,155 +155,154 @@ void axp192Init()
|
|||||||
//val = 0x46;
|
//val = 0x46;
|
||||||
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
//axp._writeByte(AXP202_OFF_CTL, 1, &val); // enable bat detection
|
||||||
#endif
|
#endif
|
||||||
axp.debugCharging();
|
axp.debugCharging();
|
||||||
|
|
||||||
#ifdef PMU_IRQ
|
#ifdef PMU_IRQ
|
||||||
pinMode(PMU_IRQ, INPUT_PULLUP);
|
pinMode(PMU_IRQ, INPUT_PULLUP);
|
||||||
attachInterrupt(PMU_IRQ, [] {
|
attachInterrupt(
|
||||||
pmu_irq = true;
|
PMU_IRQ, [] { pmu_irq = true; }, RISING);
|
||||||
},
|
|
||||||
RISING);
|
|
||||||
|
|
||||||
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
axp.adc1Enable(AXP202_BATT_CUR_ADC1, 1);
|
||||||
axp.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ, 1);
|
axp.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ,
|
||||||
axp.clearIRQ();
|
1);
|
||||||
|
axp.clearIRQ();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
isCharging = axp.isChargeing() ? 1 : 0;
|
isCharging = axp.isChargeing() ? 1 : 0;
|
||||||
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("AXP192 Begin FAIL\n");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG_MSG("AXP192 not found\n");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
DEBUG_MSG("AXP192 Begin FAIL\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DEBUG_MSG("AXP192 not found\n");
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *getDeviceName()
|
const char *getDeviceName()
|
||||||
{
|
{
|
||||||
uint8_t dmac[6];
|
uint8_t dmac[6];
|
||||||
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
|
assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
|
||||||
|
|
||||||
// Meshtastic_ab3c
|
// Meshtastic_ab3c
|
||||||
static char name[20];
|
static char name[20];
|
||||||
sprintf(name, "Meshtastic_%02x%02x", dmac[4], dmac[5]);
|
sprintf(name, "Meshtastic_%02x%02x", dmac[4], dmac[5]);
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
// Debug
|
// Debug
|
||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
DEBUG_PORT.begin(SERIAL_BAUD);
|
DEBUG_PORT.begin(SERIAL_BAUD);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
initDeepSleep();
|
initDeepSleep();
|
||||||
|
|
||||||
#ifdef VEXT_ENABLE
|
#ifdef VEXT_ENABLE
|
||||||
pinMode(VEXT_ENABLE, OUTPUT);
|
pinMode(VEXT_ENABLE, OUTPUT);
|
||||||
digitalWrite(VEXT_ENABLE, 0); // turn on the display power
|
digitalWrite(VEXT_ENABLE, 0); // turn on the display power
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef RESET_OLED
|
#ifdef RESET_OLED
|
||||||
pinMode(RESET_OLED, OUTPUT);
|
pinMode(RESET_OLED, OUTPUT);
|
||||||
digitalWrite(RESET_OLED, 1);
|
digitalWrite(RESET_OLED, 1);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef I2C_SDA
|
#ifdef I2C_SDA
|
||||||
Wire.begin(I2C_SDA, I2C_SCL);
|
Wire.begin(I2C_SDA, I2C_SCL);
|
||||||
scanI2Cdevice();
|
scanI2Cdevice();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
axp192Init();
|
// Buttons & LED
|
||||||
|
|
||||||
// Buttons & LED
|
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||||
digitalWrite(BUTTON_PIN, 1);
|
digitalWrite(BUTTON_PIN, 1);
|
||||||
#endif
|
#endif
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
pinMode(LED_PIN, OUTPUT);
|
pinMode(LED_PIN, OUTPUT);
|
||||||
digitalWrite(LED_PIN, 1); // turn on for now
|
digitalWrite(LED_PIN, 1); // turn on for now
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Hello
|
// Hello
|
||||||
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION));
|
DEBUG_MSG("Meshtastic swver=%s, hwver=%s\n", xstr(APP_VERSION), xstr(HW_VERSION));
|
||||||
|
|
||||||
// Don't init display if we don't have one or we are waking headless due to a timer event
|
// Don't init display if we don't have one or we are waking headless due to a timer event
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
||||||
ssd1306_found = false; // forget we even have the hardware
|
ssd1306_found = false; // forget we even have the hardware
|
||||||
|
|
||||||
if (ssd1306_found)
|
// Initialize the screen first so we can show the logo while we start up everything else.
|
||||||
screen.setup();
|
if (ssd1306_found)
|
||||||
|
screen.setup();
|
||||||
|
|
||||||
// Init GPS
|
axp192Init();
|
||||||
gps.setup();
|
|
||||||
|
|
||||||
screen_print("Started...\n");
|
screen.print("Started...\n");
|
||||||
|
|
||||||
service.init();
|
// Init GPS
|
||||||
|
gps.setup();
|
||||||
|
|
||||||
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
|
service.init();
|
||||||
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
|
||||||
|
|
||||||
PowerFSM_setup();
|
// This must be _after_ service.init because we need our preferences loaded from flash to have proper timeout values
|
||||||
powerFSM.trigger(EVENT_BOOT); // transition to ON, FIXME, only do this for cold boots, not waking from SDS
|
PowerFSM_setup(); // we will transition to ON in a couple of seconds, FIXME, only do this for cold boots, not waking from SDS
|
||||||
|
|
||||||
|
// setBluetoothEnable(false); we now don't start bluetooth until we enter the proper state
|
||||||
|
setCPUFast(false); // 80MHz is fine for our slow peripherals
|
||||||
}
|
}
|
||||||
|
|
||||||
void initBluetooth()
|
void initBluetooth()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Starting bluetooth\n");
|
DEBUG_MSG("Starting bluetooth\n");
|
||||||
|
|
||||||
// FIXME - we are leaking like crazy
|
// FIXME - we are leaking like crazy
|
||||||
// AllocatorScope scope(btPool);
|
// AllocatorScope scope(btPool);
|
||||||
|
|
||||||
BLEServer *serve = initBLE(getDeviceName(), HW_VENDOR, xstr(APP_VERSION), xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr
|
// Note: these callbacks might be coming in from a different thread.
|
||||||
createMeshBluetoothService(serve);
|
BLEServer *serve = initBLE(
|
||||||
|
[](uint32_t pin) {
|
||||||
|
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
|
||||||
|
screen.startBluetoothPinScreen(pin);
|
||||||
|
},
|
||||||
|
[]() { screen.stopBluetoothPinScreen(); }, getDeviceName(), HW_VENDOR, xstr(APP_VERSION),
|
||||||
|
xstr(HW_VERSION)); // FIXME, use a real name based on the macaddr
|
||||||
|
createMeshBluetoothService(serve);
|
||||||
|
|
||||||
// Start advertising - this must be done _after_ creating all services
|
// Start advertising - this must be done _after_ creating all services
|
||||||
serve->getAdvertising()->start();
|
serve->getAdvertising()->start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBluetoothEnable(bool on)
|
void setBluetoothEnable(bool on)
|
||||||
{
|
{
|
||||||
if (on != bluetoothOn)
|
if (on != bluetoothOn) {
|
||||||
{
|
DEBUG_MSG("Setting bluetooth enable=%d\n", on);
|
||||||
DEBUG_MSG("Setting bluetooth enable=%d\n", on);
|
|
||||||
|
|
||||||
bluetoothOn = on;
|
bluetoothOn = on;
|
||||||
if (on)
|
if (on) {
|
||||||
{
|
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
|
||||||
Serial.printf("Pre BT: %u heap size\n", ESP.getFreeHeap());
|
// ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
|
||||||
//ESP_ERROR_CHECK( heap_trace_start(HEAP_TRACE_LEAKS) );
|
initBluetooth();
|
||||||
initBluetooth();
|
} else {
|
||||||
|
// We have to totally teardown our bluetooth objects to prevent leaks
|
||||||
|
stopMeshBluetoothService(); // Must do before shutting down bluetooth
|
||||||
|
deinitBLE();
|
||||||
|
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
|
||||||
|
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
|
||||||
|
// ESP_ERROR_CHECK( heap_trace_stop() );
|
||||||
|
// heap_trace_dump();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// We have to totally teardown our bluetooth objects to prevent leaks
|
|
||||||
stopMeshBluetoothService(); // Must do before shutting down bluetooth
|
|
||||||
deinitBLE();
|
|
||||||
destroyMeshBluetoothService(); // must do after deinit, because it frees our service
|
|
||||||
Serial.printf("Shutdown BT: %u heap size\n", ESP.getFreeHeap());
|
|
||||||
//ESP_ERROR_CHECK( heap_trace_stop() );
|
|
||||||
//heap_trace_dump();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t ledBlinker()
|
uint32_t ledBlinker()
|
||||||
{
|
{
|
||||||
static bool ledOn;
|
static bool ledOn;
|
||||||
ledOn ^= 1;
|
ledOn ^= 1;
|
||||||
|
|
||||||
setLed(ledOn);
|
setLed(ledOn);
|
||||||
|
|
||||||
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
// have a very sparse duty cycle of LED being on, unless charging, then blink 0.5Hz square wave rate to indicate that
|
||||||
return isCharging ? 1000 : (ledOn ? 2 : 1000);
|
return isCharging ? 1000 : (ledOn ? 2 : 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Periodic ledPeriodic(ledBlinker);
|
Periodic ledPeriodic(ledBlinker);
|
||||||
@@ -330,92 +326,77 @@ Periodic axpDebugOutput(axpReads);
|
|||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
|
uint32_t msecstosleep = 1000 * 30; // How long can we sleep before we again need to service the main loop?
|
||||||
|
|
||||||
powerFSM.run_machine();
|
powerFSM.run_machine();
|
||||||
gps.loop();
|
gps.loop();
|
||||||
screen.loop();
|
screen.loop();
|
||||||
service.loop();
|
service.loop();
|
||||||
|
|
||||||
ledPeriodic.loop();
|
ledPeriodic.loop();
|
||||||
// axpDebugOutput.loop();
|
// axpDebugOutput.loop();
|
||||||
loopBLE();
|
loopBLE();
|
||||||
|
|
||||||
// for debug printing
|
// for debug printing
|
||||||
// service.radio.rf95.canSleep();
|
// service.radio.rf95.canSleep();
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (axp192_found)
|
if (axp192_found) {
|
||||||
{
|
|
||||||
#ifdef PMU_IRQ
|
#ifdef PMU_IRQ
|
||||||
if (pmu_irq)
|
if (pmu_irq) {
|
||||||
{
|
pmu_irq = false;
|
||||||
pmu_irq = false;
|
axp.readIRQ();
|
||||||
axp.readIRQ();
|
|
||||||
|
|
||||||
DEBUG_MSG("pmu irq!\n");
|
DEBUG_MSG("pmu irq!\n");
|
||||||
|
|
||||||
isCharging = axp.isChargeing() ? 1 : 0;
|
isCharging = axp.isChargeing() ? 1 : 0;
|
||||||
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
||||||
|
|
||||||
axp.clearIRQ();
|
axp.clearIRQ();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME AXP192 interrupt is not firing, remove this temporary polling of battery state
|
// FIXME AXP192 interrupt is not firing, remove this temporary polling of battery state
|
||||||
isCharging = axp.isChargeing() ? 1 : 0;
|
isCharging = axp.isChargeing() ? 1 : 0;
|
||||||
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
isUSBPowered = axp.isVBUSPlug() ? 1 : 0;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of this boilerplate)
|
// if user presses button for more than 3 secs, discard our network prefs and reboot (FIXME, use a debounce lib instead of
|
||||||
static bool wasPressed = false;
|
// this boilerplate)
|
||||||
static uint32_t minPressMs; // what tick should we call this press long enough
|
static bool wasPressed = false;
|
||||||
static uint32_t lastPingMs;
|
|
||||||
|
|
||||||
if (!digitalRead(BUTTON_PIN))
|
if (!digitalRead(BUTTON_PIN)) {
|
||||||
{
|
if (!wasPressed) { // just started a new press
|
||||||
if (!wasPressed)
|
DEBUG_MSG("pressing\n");
|
||||||
{ // just started a new press
|
|
||||||
DEBUG_MSG("pressing\n");
|
|
||||||
|
|
||||||
//doLightSleep();
|
// doLightSleep();
|
||||||
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
|
// esp_pm_dump_locks(stdout); // FIXME, do this someplace better
|
||||||
wasPressed = true;
|
wasPressed = true;
|
||||||
|
|
||||||
uint32_t now = millis();
|
powerFSM.trigger(EVENT_PRESS);
|
||||||
minPressMs = now + 3000;
|
}
|
||||||
|
} else if (wasPressed) {
|
||||||
if (now - lastPingMs > 60 * 1000)
|
// we just did a release
|
||||||
{ // if more than a minute since our last press, ask other nodes to update their state
|
wasPressed = false;
|
||||||
service.sendNetworkPing();
|
|
||||||
lastPingMs = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
powerFSM.trigger(EVENT_PRESS);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (wasPressed)
|
|
||||||
{
|
|
||||||
// we just did a release
|
|
||||||
wasPressed = false;
|
|
||||||
if (millis() > minPressMs)
|
|
||||||
{
|
|
||||||
// held long enough
|
|
||||||
screen_print("Erasing prefs");
|
|
||||||
delay(5000); // Give some time to read the screen
|
|
||||||
// ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
// Show boot screen for first 3 seconds, then switch to normal operation.
|
||||||
// i.e. don't just keep spinning in loop as fast as we can.
|
static bool showingBootScreen = true;
|
||||||
//DEBUG_MSG("msecs %d\n", msecstosleep);
|
if (showingBootScreen && (millis() > 3000)) {
|
||||||
|
screen.stopBootScreen();
|
||||||
|
showingBootScreen = false;
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons feel slow
|
// No GPS lock yet, let the OS put the main CPU in low power mode for 100ms (or until another interrupt comes in)
|
||||||
msecstosleep = 10;
|
// i.e. don't just keep spinning in loop as fast as we can.
|
||||||
|
// DEBUG_MSG("msecs %d\n", msecstosleep);
|
||||||
|
|
||||||
delay(msecstosleep);
|
// FIXME - until button press handling is done by interrupt (see polling above) we can't sleep very long at all or buttons
|
||||||
|
// feel slow
|
||||||
|
msecstosleep = 10;
|
||||||
|
|
||||||
|
delay(msecstosleep);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
extern bool axp192_found;
|
extern bool axp192_found;
|
||||||
extern bool ssd1306_found;
|
extern bool ssd1306_found;
|
||||||
extern bool isCharging;
|
extern bool isCharging;
|
||||||
extern bool isUSBPowered;
|
extern bool isUSBPowered;
|
||||||
|
|
||||||
|
// Global Screen singleton.
|
||||||
|
extern meshtastic::Screen screen;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
#include "configuration.h"
|
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include <pb_encode.h>
|
|
||||||
#include <pb_decode.h>
|
|
||||||
#include <assert.h>
|
|
||||||
#include "FS.h"
|
#include "FS.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
|
||||||
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
||||||
/// returns the encoded packet size
|
/// returns the encoded packet size
|
||||||
@@ -12,42 +12,33 @@ size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc
|
|||||||
{
|
{
|
||||||
|
|
||||||
pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize);
|
pb_ostream_t stream = pb_ostream_from_buffer(destbuf, destbufsize);
|
||||||
if (!pb_encode(&stream, fields, src_struct))
|
if (!pb_encode(&stream, fields, src_struct)) {
|
||||||
{
|
|
||||||
DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
|
DEBUG_MSG("Error: can't encode protobuf %s\n", PB_GET_ERROR(&stream));
|
||||||
assert(0); // FIXME - panic
|
assert(0); // FIXME - panic
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return stream.bytes_written;
|
return stream.bytes_written;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// helper function for decoding a record as a protobuf, we will return false if the decoding failed
|
/// helper function for decoding a record as a protobuf, we will return false if the decoding failed
|
||||||
bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct)
|
bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msgdesc_t *fields, void *dest_struct)
|
||||||
{
|
{
|
||||||
pb_istream_t stream = pb_istream_from_buffer(srcbuf, srcbufsize);
|
pb_istream_t stream = pb_istream_from_buffer(srcbuf, srcbufsize);
|
||||||
if (!pb_decode(&stream, fields, dest_struct))
|
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||||
{
|
DEBUG_MSG("Error: can't decode protobuf %s, pb_msgdesc 0x%p\n", PB_GET_ERROR(&stream), fields);
|
||||||
DEBUG_MSG("Error: can't decode protobuf %s\n", PB_GET_ERROR(&stream));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Read from an Arduino File
|
/// Read from an Arduino File
|
||||||
bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
|
bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
|
||||||
{
|
{
|
||||||
File *file = (File *)stream->state;
|
File *file = (File *)stream->state;
|
||||||
bool status;
|
bool status;
|
||||||
|
|
||||||
if (buf == NULL)
|
if (buf == NULL) {
|
||||||
{
|
|
||||||
while (count-- && file->read() != EOF)
|
while (count-- && file->read() != EOF)
|
||||||
;
|
;
|
||||||
return count == 0;
|
return count == 0;
|
||||||
@@ -61,11 +52,10 @@ bool readcb(pb_istream_t *stream, uint8_t *buf, size_t count)
|
|||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Write to an arduino file
|
/// Write to an arduino file
|
||||||
bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count)
|
bool writecb(pb_ostream_t *stream, const uint8_t *buf, size_t count)
|
||||||
{
|
{
|
||||||
File *file = (File*) stream->state;
|
File *file = (File *)stream->state;
|
||||||
//DEBUG_MSG("writing %d bytes to protobuf file\n", count);
|
// DEBUG_MSG("writing %d bytes to protobuf file\n", count);
|
||||||
return file->write(buf, count) == count;
|
return file->write(buf, count) == count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
/// max number of nodes allowed in the mesh
|
/// max number of nodes allowed in the mesh
|
||||||
#define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0]))
|
#define MAX_NUM_NODES (member_size(DeviceState, node_db) / member_size(DeviceState, node_db[0]))
|
||||||
|
|
||||||
|
|
||||||
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
/// helper function for encoding a record as a protobuf, any failures to encode are fatal and we will panic
|
||||||
/// returns the encoded packet size
|
/// returns the encoded packet size
|
||||||
size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct);
|
size_t pb_encode_to_bytes(uint8_t *destbuf, size_t destbufsize, const pb_msgdesc_t *fields, const void *src_struct);
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ typedef enum _ChannelSettings_ModemConfig {
|
|||||||
/* Struct definitions */
|
/* Struct definitions */
|
||||||
typedef struct _ChannelSettings {
|
typedef struct _ChannelSettings {
|
||||||
int32_t tx_power;
|
int32_t tx_power;
|
||||||
uint32_t channel_num;
|
|
||||||
ChannelSettings_ModemConfig modem_config;
|
ChannelSettings_ModemConfig modem_config;
|
||||||
pb_byte_t psk[16];
|
pb_byte_t psk[16];
|
||||||
char name[12];
|
char name[12];
|
||||||
@@ -53,6 +52,9 @@ typedef struct _MyNodeInfo {
|
|||||||
char region[12];
|
char region[12];
|
||||||
char hw_model[12];
|
char hw_model[12];
|
||||||
char firmware_version[12];
|
char firmware_version[12];
|
||||||
|
uint32_t error_code;
|
||||||
|
uint32_t error_address;
|
||||||
|
uint32_t error_count;
|
||||||
} MyNodeInfo;
|
} MyNodeInfo;
|
||||||
|
|
||||||
typedef struct _Position {
|
typedef struct _Position {
|
||||||
@@ -173,11 +175,11 @@ typedef struct _ToRadio {
|
|||||||
#define User_init_default {"", "", "", {0}}
|
#define User_init_default {"", "", "", {0}}
|
||||||
#define SubPacket_init_default {0, {Position_init_default}, 0}
|
#define SubPacket_init_default {0, {Position_init_default}, 0}
|
||||||
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0}
|
#define MeshPacket_init_default {0, 0, false, SubPacket_init_default, 0}
|
||||||
#define ChannelSettings_init_default {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
|
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
|
||||||
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
||||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
||||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", ""}
|
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", 0, 0, 0}
|
||||||
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0}
|
#define DeviceState_init_default {false, RadioConfig_init_default, false, MyNodeInfo_init_default, false, User_init_default, 0, {NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default, NodeInfo_init_default}, 0, {MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default, MeshPacket_init_default}, false, MeshPacket_init_default, 0}
|
||||||
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
#define FromRadio_init_default {0, 0, {MeshPacket_init_default}}
|
||||||
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
#define ToRadio_init_default {0, {MeshPacket_init_default}}
|
||||||
@@ -186,18 +188,17 @@ typedef struct _ToRadio {
|
|||||||
#define User_init_zero {"", "", "", {0}}
|
#define User_init_zero {"", "", "", {0}}
|
||||||
#define SubPacket_init_zero {0, {Position_init_zero}, 0}
|
#define SubPacket_init_zero {0, {Position_init_zero}, 0}
|
||||||
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0}
|
#define MeshPacket_init_zero {0, 0, false, SubPacket_init_zero, 0}
|
||||||
#define ChannelSettings_init_zero {0, 0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
|
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0}, ""}
|
||||||
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
||||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
||||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", ""}
|
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", 0, 0, 0}
|
||||||
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0}
|
#define DeviceState_init_zero {false, RadioConfig_init_zero, false, MyNodeInfo_init_zero, false, User_init_zero, 0, {NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero, NodeInfo_init_zero}, 0, {MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero, MeshPacket_init_zero}, false, MeshPacket_init_zero, 0}
|
||||||
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
#define FromRadio_init_zero {0, 0, {MeshPacket_init_zero}}
|
||||||
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
#define ToRadio_init_zero {0, {MeshPacket_init_zero}}
|
||||||
|
|
||||||
/* Field tags (for use in manual encoding/decoding) */
|
/* Field tags (for use in manual encoding/decoding) */
|
||||||
#define ChannelSettings_tx_power_tag 1
|
#define ChannelSettings_tx_power_tag 1
|
||||||
#define ChannelSettings_channel_num_tag 2
|
|
||||||
#define ChannelSettings_modem_config_tag 3
|
#define ChannelSettings_modem_config_tag 3
|
||||||
#define ChannelSettings_psk_tag 4
|
#define ChannelSettings_psk_tag 4
|
||||||
#define ChannelSettings_name_tag 5
|
#define ChannelSettings_name_tag 5
|
||||||
@@ -209,6 +210,9 @@ typedef struct _ToRadio {
|
|||||||
#define MyNodeInfo_region_tag 4
|
#define MyNodeInfo_region_tag 4
|
||||||
#define MyNodeInfo_hw_model_tag 5
|
#define MyNodeInfo_hw_model_tag 5
|
||||||
#define MyNodeInfo_firmware_version_tag 6
|
#define MyNodeInfo_firmware_version_tag 6
|
||||||
|
#define MyNodeInfo_error_code_tag 7
|
||||||
|
#define MyNodeInfo_error_address_tag 8
|
||||||
|
#define MyNodeInfo_error_count_tag 9
|
||||||
#define Position_latitude_tag 1
|
#define Position_latitude_tag 1
|
||||||
#define Position_longitude_tag 2
|
#define Position_longitude_tag 2
|
||||||
#define Position_altitude_tag 3
|
#define Position_altitude_tag 3
|
||||||
@@ -303,7 +307,6 @@ X(a, STATIC, SINGULAR, UINT32, rx_time, 4)
|
|||||||
|
|
||||||
#define ChannelSettings_FIELDLIST(X, a) \
|
#define ChannelSettings_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
|
X(a, STATIC, SINGULAR, INT32, tx_power, 1) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, channel_num, 2) \
|
|
||||||
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
|
X(a, STATIC, SINGULAR, UENUM, modem_config, 3) \
|
||||||
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
|
X(a, STATIC, SINGULAR, FIXED_LENGTH_BYTES, psk, 4) \
|
||||||
X(a, STATIC, SINGULAR, STRING, name, 5)
|
X(a, STATIC, SINGULAR, STRING, name, 5)
|
||||||
@@ -352,7 +355,10 @@ X(a, STATIC, SINGULAR, BOOL, has_gps, 2) \
|
|||||||
X(a, STATIC, SINGULAR, INT32, num_channels, 3) \
|
X(a, STATIC, SINGULAR, INT32, num_channels, 3) \
|
||||||
X(a, STATIC, SINGULAR, STRING, region, 4) \
|
X(a, STATIC, SINGULAR, STRING, region, 4) \
|
||||||
X(a, STATIC, SINGULAR, STRING, hw_model, 5) \
|
X(a, STATIC, SINGULAR, STRING, hw_model, 5) \
|
||||||
X(a, STATIC, SINGULAR, STRING, firmware_version, 6)
|
X(a, STATIC, SINGULAR, STRING, firmware_version, 6) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, error_code, 7) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, error_address, 8) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, error_count, 9)
|
||||||
#define MyNodeInfo_CALLBACK NULL
|
#define MyNodeInfo_CALLBACK NULL
|
||||||
#define MyNodeInfo_DEFAULT NULL
|
#define MyNodeInfo_DEFAULT NULL
|
||||||
|
|
||||||
@@ -421,12 +427,12 @@ extern const pb_msgdesc_t ToRadio_msg;
|
|||||||
#define User_size 72
|
#define User_size 72
|
||||||
#define SubPacket_size 261
|
#define SubPacket_size 261
|
||||||
#define MeshPacket_size 292
|
#define MeshPacket_size 292
|
||||||
#define ChannelSettings_size 50
|
#define ChannelSettings_size 44
|
||||||
#define RadioConfig_size 126
|
#define RadioConfig_size 120
|
||||||
#define RadioConfig_UserPreferences_size 72
|
#define RadioConfig_UserPreferences_size 72
|
||||||
#define NodeInfo_size 155
|
#define NodeInfo_size 155
|
||||||
#define MyNodeInfo_size 63
|
#define MyNodeInfo_size 81
|
||||||
#define DeviceState_size 15064
|
#define DeviceState_size 15076
|
||||||
#define FromRadio_size 301
|
#define FromRadio_size 301
|
||||||
#define ToRadio_size 295
|
#define ToRadio_size 295
|
||||||
|
|
||||||
|
|||||||
527
src/screen.cpp
527
src/screen.cpp
@@ -20,54 +20,37 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <OLEDDisplay.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
#include "SSD1306Wire.h"
|
|
||||||
#include "OLEDDisplay.h"
|
|
||||||
#include "images.h"
|
|
||||||
#include "fonts.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "OLEDDisplayUi.h"
|
|
||||||
#include "screen.h"
|
|
||||||
#include "mesh-pb-constants.h"
|
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "fonts.h"
|
||||||
|
#include "images.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "screen.h"
|
||||||
|
|
||||||
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
#define FONT_HEIGHT 14 // actually 13 for "ariel 10" but want a little extra space
|
||||||
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
#define FONT_HEIGHT_16 (ArialMT_Plain_16[1] + 1)
|
||||||
#define SCREEN_WIDTH 128
|
#define SCREEN_WIDTH 128
|
||||||
#define SCREEN_HEIGHT 64
|
#define SCREEN_HEIGHT 64
|
||||||
|
#define TRANSITION_FRAMERATE 30 // fps
|
||||||
#ifdef I2C_SDA
|
#define IDLE_FRAMERATE 10 // in fps
|
||||||
SSD1306Wire dispdev(SSD1306_ADDRESS, I2C_SDA, I2C_SCL);
|
#define COMPASS_DIAM 44
|
||||||
#else
|
|
||||||
SSD1306Wire dispdev(SSD1306_ADDRESS, 0, 0); // fake values to keep build happy, we won't ever init
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool disp; // true if we are using display
|
|
||||||
bool screenOn; // true if the display is currently powered
|
|
||||||
|
|
||||||
OLEDDisplayUi ui(&dispdev);
|
|
||||||
|
|
||||||
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
#define NUM_EXTRA_FRAMES 2 // text message and debug frame
|
||||||
// A text message frame + debug frame + all the node infos
|
|
||||||
FrameCallback nonBootFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
|
||||||
|
|
||||||
Screen screen;
|
namespace meshtastic
|
||||||
static bool showingBluetooth;
|
|
||||||
|
|
||||||
/// If set to true (possibly from an ISR), we should turn on the screen the next time our idle loop runs.
|
|
||||||
static bool showingBootScreen = true; // start by showing the bootscreen
|
|
||||||
|
|
||||||
bool Screen::isOn() { return screenOn; }
|
|
||||||
|
|
||||||
void msOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
|
|
||||||
{
|
{
|
||||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
display->drawString(128, 0, String(millis()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
// A text message frame + debug frame + all the node infos
|
||||||
|
static FrameCallback normalFrames[MAX_NUM_NODES + NUM_EXTRA_FRAMES];
|
||||||
|
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
||||||
|
static char btPIN[16] = "888888";
|
||||||
|
|
||||||
|
static void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
// draw an xbm image.
|
// draw an xbm image.
|
||||||
// Please note that everything that should be transitioned
|
// Please note that everything that should be transitioned
|
||||||
@@ -78,16 +61,10 @@ void drawBootScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
|
|||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
display->drawString(64 + x, SCREEN_HEIGHT - FONT_HEIGHT_16, "meshtastic.org");
|
||||||
|
|
||||||
ui.disableIndicator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static char btPIN[16] = "888888";
|
static void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
|
|
||||||
void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
{
|
||||||
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
|
|
||||||
// Besides the default fonts there will be a program to convert TrueType fonts into this format
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
display->drawString(64 + x, 2 + y, "Bluetooth");
|
display->drawString(64 + x, 2 + y, "Bluetooth");
|
||||||
@@ -98,81 +75,44 @@ void drawFrameBluetooth(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
||||||
display->setFont(ArialMT_Plain_24);
|
display->setFont(ArialMT_Plain_24);
|
||||||
display->drawString(64 + x, 22 + y, btPIN);
|
display->drawString(64 + x, 22 + y, btPIN);
|
||||||
|
|
||||||
ui.disableIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawFrame2(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
// Demonstrates the 3 included default sizes. The fonts come from SSD1306Fonts.h file
|
|
||||||
// Besides the default fonts there will be a program to convert TrueType fonts into this format
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
display->drawString(0 + x, 10 + y, "Arial 10");
|
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_16);
|
|
||||||
display->drawString(0 + x, 20 + y, "Arial 16");
|
|
||||||
|
|
||||||
display->setFont(ArialMT_Plain_24);
|
|
||||||
display->drawString(0 + x, 34 + y, "Arial 24");
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawFrame3(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
|
||||||
{
|
|
||||||
// Text alignment demo
|
|
||||||
display->setFont(ArialMT_Plain_10);
|
|
||||||
|
|
||||||
// The coordinates define the left starting point of the text
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
|
||||||
display->drawString(0 + x, 11 + y, "Left aligned (0,10)");
|
|
||||||
|
|
||||||
// The coordinates define the center of the text
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_CENTER);
|
|
||||||
display->drawString(64 + x, 22 + y, "Center aligned (64,22)");
|
|
||||||
|
|
||||||
// The coordinates define the right end of the text
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_RIGHT);
|
|
||||||
display->drawString(128 + x, 33 + y, "Right aligned (128,33)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw the last text message we received
|
/// Draw the last text message we received
|
||||||
void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
MeshPacket &mp = devicestate.rx_text_message;
|
MeshPacket &mp = devicestate.rx_text_message;
|
||||||
NodeInfo *node = nodeDB.getNode(mp.from);
|
NodeInfo *node = nodeDB.getNode(mp.from);
|
||||||
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, mp.payload.variant.data.payload.bytes);
|
// DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from,
|
||||||
|
// mp.payload.variant.data.payload.bytes);
|
||||||
|
|
||||||
// Demo for drawStringMaxWidth:
|
// Demo for drawStringMaxWidth:
|
||||||
// with the third parameter you can define the width after which words will be wrapped.
|
// with the third parameter you can define the width after which words will
|
||||||
// Currently only spaces and "-" are allowed for wrapping
|
// be wrapped. Currently only spaces and "-" are allowed for wrapping
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(ArialMT_Plain_16);
|
display->setFont(ArialMT_Plain_16);
|
||||||
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
String sender = (node && node->has_user) ? node->user.short_name : "???";
|
||||||
display->drawString(0 + x, 0 + y, sender);
|
display->drawString(0 + x, 0 + y, sender);
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(ArialMT_Plain_10);
|
||||||
|
|
||||||
|
// the max length of this buffer is much longer than we can possibly print
|
||||||
static char tempBuf[96];
|
static char tempBuf[96];
|
||||||
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes); // the max length of this buffer is much longer than we can possibly print
|
snprintf(tempBuf, sizeof(tempBuf), " %s", mp.payload.variant.data.payload.bytes);
|
||||||
|
|
||||||
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
|
display->drawStringMaxWidth(4 + x, 10 + y, 128, tempBuf);
|
||||||
|
|
||||||
// ui.disableIndicator();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
/// Draw a series of fields in a column, wrapping to multiple colums if needed
|
||||||
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||||
{
|
{
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
const char **f = fields;
|
const char **f = fields;
|
||||||
int xo = x, yo = y;
|
int xo = x, yo = y;
|
||||||
while (*f)
|
while (*f) {
|
||||||
{
|
|
||||||
display->drawString(xo, yo, *f);
|
display->drawString(xo, yo, *f);
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT;
|
||||||
if (yo > SCREEN_HEIGHT - FONT_HEIGHT)
|
if (yo > SCREEN_HEIGHT - FONT_HEIGHT) {
|
||||||
{
|
|
||||||
xo += SCREEN_WIDTH / 2;
|
xo += SCREEN_WIDTH / 2;
|
||||||
yo = 0;
|
yo = 0;
|
||||||
}
|
}
|
||||||
@@ -182,21 +122,23 @@ void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields
|
|||||||
|
|
||||||
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
/// Draw a series of fields in a row, wrapping to multiple rows if needed
|
||||||
/// @return the max y we ended up printing to
|
/// @return the max y we ended up printing to
|
||||||
uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
static uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **fields)
|
||||||
{
|
{
|
||||||
// The coordinates define the left starting point of the text
|
// The coordinates define the left starting point of the text
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
|
|
||||||
const char **f = fields;
|
const char **f = fields;
|
||||||
int xo = x, yo = y;
|
int xo = x, yo = y;
|
||||||
while (*f)
|
const int COLUMNS = 2; // hardwired for two columns per row....
|
||||||
{
|
int col = 0; // track which column we are on
|
||||||
|
while (*f) {
|
||||||
display->drawString(xo, yo, *f);
|
display->drawString(xo, yo, *f);
|
||||||
xo += SCREEN_WIDTH / 2; // hardwired for two columns per row....
|
xo += SCREEN_WIDTH / COLUMNS;
|
||||||
if (xo >= SCREEN_WIDTH)
|
// Wrap to next row, if needed.
|
||||||
{
|
if (++col > COLUMNS) {
|
||||||
|
xo = x;
|
||||||
yo += FONT_HEIGHT;
|
yo += FONT_HEIGHT;
|
||||||
xo = 0;
|
col = 0;
|
||||||
}
|
}
|
||||||
f++;
|
f++;
|
||||||
}
|
}
|
||||||
@@ -206,8 +148,9 @@ uint32_t drawRows(OLEDDisplay *display, int16_t x, int16_t y, const char **field
|
|||||||
return yo;
|
return yo;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ported from my old java code, returns distance in meters along the globe surface (by magic?)
|
/// Ported from my old java code, returns distance in meters along the globe
|
||||||
float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
/// surface (by magic?)
|
||||||
|
static float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
||||||
{
|
{
|
||||||
double pk = (180 / 3.14169);
|
double pk = (180 / 3.14169);
|
||||||
double a1 = lat_a / pk;
|
double a1 = lat_a / pk;
|
||||||
@@ -216,10 +159,8 @@ float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
|||||||
double b2 = lng_b / pk;
|
double b2 = lng_b / pk;
|
||||||
double cos_b1 = cos(b1);
|
double cos_b1 = cos(b1);
|
||||||
double cos_a1 = cos(a1);
|
double cos_a1 = cos(a1);
|
||||||
double t1 =
|
double t1 = cos_a1 * cos(a2) * cos_b1 * cos(b2);
|
||||||
cos_a1 * cos(a2) * cos_b1 * cos(b2);
|
double t2 = cos_a1 * sin(a2) * cos_b1 * sin(b2);
|
||||||
double t2 =
|
|
||||||
cos_a1 * sin(a2) * cos_b1 * sin(b2);
|
|
||||||
double t3 = sin(a1) * sin(b1);
|
double t3 = sin(a1) * sin(b1);
|
||||||
double tt = acos(t1 + t2 + t3);
|
double tt = acos(t1 + t2 + t3);
|
||||||
if (isnan(tt))
|
if (isnan(tt))
|
||||||
@@ -228,31 +169,32 @@ float latLongToMeter(double lat_a, double lng_a, double lat_b, double lng_b)
|
|||||||
return (float)(6366000 * tt);
|
return (float)(6366000 * tt);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double toRadians(double deg)
|
static inline double toRadians(double deg)
|
||||||
{
|
{
|
||||||
return deg * PI / 180;
|
return deg * PI / 180;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double toDegrees(double r)
|
static inline double toDegrees(double r)
|
||||||
{
|
{
|
||||||
return r * 180 / PI;
|
return r * 180 / PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the bearing in degrees between two points on Earth. Ported from my old Gaggle android app.
|
* Computes the bearing in degrees between two points on Earth. Ported from my
|
||||||
*
|
* old Gaggle android app.
|
||||||
* @param lat1
|
*
|
||||||
* Latitude of the first point
|
* @param lat1
|
||||||
* @param lon1
|
* Latitude of the first point
|
||||||
* Longitude of the first point
|
* @param lon1
|
||||||
* @param lat2
|
* Longitude of the first point
|
||||||
* Latitude of the second point
|
* @param lat2
|
||||||
* @param lon2
|
* Latitude of the second point
|
||||||
* Longitude of the second point
|
* @param lon2
|
||||||
* @return Bearing between the two points in radians. A value of 0 means due
|
* Longitude of the second point
|
||||||
* north.
|
* @return Bearing between the two points in radians. A value of 0 means due
|
||||||
*/
|
* north.
|
||||||
float bearing(double lat1, double lon1, double lat2, double lon2)
|
*/
|
||||||
|
static float bearing(double lat1, double lon1, double lat2, double lon2)
|
||||||
{
|
{
|
||||||
double lat1Rad = toRadians(lat1);
|
double lat1Rad = toRadians(lat1);
|
||||||
double lat2Rad = toRadians(lat2);
|
double lat2Rad = toRadians(lat2);
|
||||||
@@ -262,10 +204,13 @@ float bearing(double lat1, double lon1, double lat2, double lon2)
|
|||||||
return atan2(y, x);
|
return atan2(y, x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
/// A basic 2D point class for drawing
|
/// A basic 2D point class for drawing
|
||||||
class Point
|
class Point
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
float x, y;
|
float x, y;
|
||||||
|
|
||||||
Point(float _x, float _y) : x(_x), y(_y) {}
|
Point(float _x, float _y) : x(_x), y(_y) {}
|
||||||
@@ -273,10 +218,8 @@ public:
|
|||||||
/// Apply a rotation around zero (standard rotation matrix math)
|
/// Apply a rotation around zero (standard rotation matrix math)
|
||||||
void rotate(float radian)
|
void rotate(float radian)
|
||||||
{
|
{
|
||||||
float cos = cosf(radian),
|
float cos = cosf(radian), sin = sinf(radian);
|
||||||
sin = sinf(radian);
|
float rx = x * cos - y * sin, ry = x * sin + y * cos;
|
||||||
float rx = x * cos - y * sin,
|
|
||||||
ry = x * sin + y * cos;
|
|
||||||
|
|
||||||
x = rx;
|
x = rx;
|
||||||
y = ry;
|
y = ry;
|
||||||
@@ -295,7 +238,9 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
} // namespace
|
||||||
|
|
||||||
|
static void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
||||||
{
|
{
|
||||||
d->drawLine(p1.x, p1.y, p2.x, p2.y);
|
d->drawLine(p1.x, p1.y, p2.x, p2.y);
|
||||||
}
|
}
|
||||||
@@ -303,15 +248,15 @@ void drawLine(OLEDDisplay *d, const Point &p1, const Point &p2)
|
|||||||
/**
|
/**
|
||||||
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
* Given a recent lat/lon return a guess of the heading the user is walking on.
|
||||||
*
|
*
|
||||||
* We keep a series of "after you've gone 10 meters, what is your heading since the last reference point?"
|
* We keep a series of "after you've gone 10 meters, what is your heading since
|
||||||
|
* the last reference point?"
|
||||||
*/
|
*/
|
||||||
float estimatedHeading(double lat, double lon)
|
static float estimatedHeading(double lat, double lon)
|
||||||
{
|
{
|
||||||
static double oldLat, oldLon;
|
static double oldLat, oldLon;
|
||||||
static float b;
|
static float b;
|
||||||
|
|
||||||
if (oldLat == 0)
|
if (oldLat == 0) {
|
||||||
{
|
|
||||||
// just prepare for next time
|
// just prepare for next time
|
||||||
oldLat = lat;
|
oldLat = lat;
|
||||||
oldLon = lon;
|
oldLon = lon;
|
||||||
@@ -330,28 +275,28 @@ float estimatedHeading(double lat, double lon)
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sometimes we will have Position objects that only have a time, so check for valid lat/lon
|
/// Sometimes we will have Position objects that only have a time, so check for
|
||||||
bool hasPosition(NodeInfo *n)
|
/// valid lat/lon
|
||||||
|
static bool hasPosition(NodeInfo *n)
|
||||||
{
|
{
|
||||||
return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
|
return n->has_position && (n->position.latitude != 0 || n->position.longitude != 0);
|
||||||
}
|
}
|
||||||
#define COMPASS_DIAM 44
|
|
||||||
|
|
||||||
/// We will skip one node - the one for us, so we just blindly loop over all nodes
|
/// We will skip one node - the one for us, so we just blindly loop over all
|
||||||
|
/// nodes
|
||||||
static size_t nodeIndex;
|
static size_t nodeIndex;
|
||||||
static int8_t prevFrame = -1;
|
static int8_t prevFrame = -1;
|
||||||
|
|
||||||
void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
// We only advance our nodeIndex if the frame # has changed - because drawNodeInfo will be called repeatedly while the frame is shown
|
// We only advance our nodeIndex if the frame # has changed - because
|
||||||
if (state->currentFrame != prevFrame)
|
// drawNodeInfo will be called repeatedly while the frame is shown
|
||||||
{
|
if (state->currentFrame != prevFrame) {
|
||||||
prevFrame = state->currentFrame;
|
prevFrame = state->currentFrame;
|
||||||
|
|
||||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
||||||
NodeInfo *n = nodeDB.getNodeByIndex(nodeIndex);
|
NodeInfo *n = nodeDB.getNodeByIndex(nodeIndex);
|
||||||
if (n->num == nodeDB.getNodeNum())
|
if (n->num == nodeDB.getNodeNum()) {
|
||||||
{
|
|
||||||
// Don't show our node, just skip to next
|
// Don't show our node, just skip to next
|
||||||
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
nodeIndex = (nodeIndex + 1) % nodeDB.getNumNodes();
|
||||||
}
|
}
|
||||||
@@ -379,14 +324,14 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60);
|
snprintf(lastStr, sizeof(lastStr), "%d hours ago", agoSecs / 60 / 60);
|
||||||
|
|
||||||
static float simRadian;
|
static float simRadian;
|
||||||
simRadian += 0.1; // For testing, have the compass spin unless both locations are valid
|
simRadian += 0.1; // For testing, have the compass spin unless both
|
||||||
|
// locations are valid
|
||||||
|
|
||||||
static char distStr[20];
|
static char distStr[20];
|
||||||
*distStr = 0; // might not have location data
|
*distStr = 0; // might not have location data
|
||||||
float headingRadian = simRadian;
|
float headingRadian = simRadian;
|
||||||
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
NodeInfo *ourNode = nodeDB.getNode(nodeDB.getNodeNum());
|
||||||
if (ourNode && hasPosition(ourNode) && hasPosition(node))
|
if (ourNode && hasPosition(ourNode) && hasPosition(node)) {
|
||||||
{
|
|
||||||
Position &op = ourNode->position, &p = node->position;
|
Position &op = ourNode->position, &p = node->position;
|
||||||
float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude);
|
float d = latLongToMeter(p.latitude, p.longitude, op.latitude, op.longitude);
|
||||||
if (d < 2000)
|
if (d < 2000)
|
||||||
@@ -394,23 +339,20 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
else
|
else
|
||||||
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
snprintf(distStr, sizeof(distStr), "%.1f km", d / 1000);
|
||||||
|
|
||||||
// FIXME, also keep the guess at the operators heading and add/substract it. currently we don't do this and instead draw north up only.
|
// FIXME, also keep the guess at the operators heading and add/substract
|
||||||
|
// it. currently we don't do this and instead draw north up only.
|
||||||
float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude);
|
float bearingToOther = bearing(p.latitude, p.longitude, op.latitude, op.longitude);
|
||||||
float myHeading = estimatedHeading(p.latitude, p.longitude);
|
float myHeading = estimatedHeading(p.latitude, p.longitude);
|
||||||
headingRadian = bearingToOther - myHeading;
|
headingRadian = bearingToOther - myHeading;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *fields[] = {
|
const char *fields[] = {username, distStr, signalStr, lastStr, NULL};
|
||||||
username,
|
|
||||||
distStr,
|
|
||||||
signalStr,
|
|
||||||
lastStr,
|
|
||||||
NULL};
|
|
||||||
drawColumns(display, x, y, fields);
|
drawColumns(display, x, y, fields);
|
||||||
|
|
||||||
// coordinates for the center of the compass
|
// coordinates for the center of the compass
|
||||||
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
int16_t compassX = x + SCREEN_WIDTH - COMPASS_DIAM / 2 - 1, compassY = y + SCREEN_HEIGHT / 2;
|
||||||
// display->drawXbm(compassX, compassY, compass_width, compass_height, (const uint8_t *)compass_bits);
|
// display->drawXbm(compassX, compassY, compass_width, compass_height,
|
||||||
|
// (const uint8_t *)compass_bits);
|
||||||
|
|
||||||
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
Point tip(0.0f, 0.5f), tail(0.0f, -0.5f); // pointing up initially
|
||||||
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
float arrowOffsetX = 0.2f, arrowOffsetY = 0.2f;
|
||||||
@@ -418,8 +360,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
|
|
||||||
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
Point *points[] = {&tip, &tail, &leftArrow, &rightArrow};
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
for (int i = 0; i < 4; i++) {
|
||||||
{
|
|
||||||
points[i]->rotate(headingRadian);
|
points[i]->rotate(headingRadian);
|
||||||
points[i]->scale(COMPASS_DIAM * 0.6);
|
points[i]->scale(COMPASS_DIAM * 0.6);
|
||||||
points[i]->translate(compassX, compassY);
|
points[i]->translate(compassX, compassY);
|
||||||
@@ -431,7 +372,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
|
|||||||
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
display->drawCircle(compassX, compassY, COMPASS_DIAM / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
static void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
display->setFont(ArialMT_Plain_10);
|
display->setFont(ArialMT_Plain_10);
|
||||||
|
|
||||||
@@ -450,32 +391,17 @@ void drawDebugInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
|
|||||||
|
|
||||||
static char gpsStr[20];
|
static char gpsStr[20];
|
||||||
if (myNodeInfo.has_gps)
|
if (myNodeInfo.has_gps)
|
||||||
snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%", 75); // FIXME, use something based on hdop
|
snprintf(gpsStr, sizeof(gpsStr), "GPS %d%%",
|
||||||
|
75); // FIXME, use something based on hdop
|
||||||
else
|
else
|
||||||
gpsStr[0] = '\0'; // Just show emptystring
|
gpsStr[0] = '\0'; // Just show emptystring
|
||||||
|
|
||||||
const char *fields[] = {
|
const char *fields[] = {batStr, gpsStr, usersStr, channelStr, NULL};
|
||||||
batStr,
|
|
||||||
gpsStr,
|
|
||||||
usersStr,
|
|
||||||
channelStr,
|
|
||||||
NULL};
|
|
||||||
uint32_t yo = drawRows(display, x, y, fields);
|
uint32_t yo = drawRows(display, x, y, fields);
|
||||||
|
|
||||||
display->drawLogBuffer(x, yo);
|
display->drawLogBuffer(x, yo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This array keeps function pointers to all frames
|
|
||||||
// frames are the single views that slide in
|
|
||||||
FrameCallback bootFrames[] = {drawBootScreen};
|
|
||||||
|
|
||||||
// Overlays are statically drawn on top of a frame eg. a clock
|
|
||||||
OverlayCallback overlays[] = {/* msOverlay */};
|
|
||||||
|
|
||||||
// how many frames are there?
|
|
||||||
const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
|
||||||
const int overlaysCount = sizeof(overlays) / sizeof(overlays[0]);
|
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
void _screen_header()
|
void _screen_header()
|
||||||
{
|
{
|
||||||
@@ -500,20 +426,21 @@ void _screen_header()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void Screen::setOn(bool on)
|
Screen::Screen(uint8_t address, uint8_t sda, uint8_t scl)
|
||||||
|
: cmdQueue(32), useDisplay(sda || scl), dispdev(address, sda, scl), ui(&dispdev)
|
||||||
{
|
{
|
||||||
if (!disp)
|
}
|
||||||
|
|
||||||
|
void Screen::handleSetOn(bool on)
|
||||||
|
{
|
||||||
|
if (!useDisplay)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (on != screenOn)
|
if (on != screenOn) {
|
||||||
{
|
if (on) {
|
||||||
if (on)
|
|
||||||
{
|
|
||||||
DEBUG_MSG("Turning on screen\n");
|
DEBUG_MSG("Turning on screen\n");
|
||||||
dispdev.displayOn();
|
dispdev.displayOn();
|
||||||
setPeriod(1); // redraw ASAP
|
} else {
|
||||||
}
|
|
||||||
else {
|
|
||||||
DEBUG_MSG("Turning off screen\n");
|
DEBUG_MSG("Turning off screen\n");
|
||||||
dispdev.displayOff();
|
dispdev.displayOff();
|
||||||
}
|
}
|
||||||
@@ -521,166 +448,133 @@ void Screen::setOn(bool on)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void screen_print(const char *text, uint8_t x, uint8_t y, uint8_t alignment)
|
|
||||||
{
|
|
||||||
DEBUG_MSG(text);
|
|
||||||
|
|
||||||
if (!disp)
|
|
||||||
return;
|
|
||||||
|
|
||||||
dispdev.setTextAlignment((OLEDDISPLAY_TEXT_ALIGNMENT)alignment);
|
|
||||||
dispdev.drawString(x, y, text);
|
|
||||||
}
|
|
||||||
|
|
||||||
void screen_print(const char *text)
|
|
||||||
{
|
|
||||||
DEBUG_MSG("Screen: %s", text);
|
|
||||||
if (!disp)
|
|
||||||
return;
|
|
||||||
|
|
||||||
dispdev.print(text);
|
|
||||||
// ui.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Screen::setup()
|
void Screen::setup()
|
||||||
{
|
{
|
||||||
#ifdef I2C_SDA
|
if (!useDisplay)
|
||||||
// Display instance
|
return;
|
||||||
disp = true;
|
|
||||||
|
|
||||||
// The ESP is capable of rendering 60fps in 80Mhz mode
|
dispdev.resetOrientation();
|
||||||
// but that won't give you much time for anything else
|
|
||||||
// run it in 160Mhz mode or just set it to 30 fps
|
|
||||||
// We do this now in loop()
|
|
||||||
// ui.setTargetFPS(30);
|
|
||||||
|
|
||||||
// Customize the active and inactive symbol
|
|
||||||
//ui.setActiveSymbol(activeSymbol);
|
|
||||||
//ui.setInactiveSymbol(inactiveSymbol);
|
|
||||||
|
|
||||||
ui.setTimePerTransition(300); // msecs
|
|
||||||
|
|
||||||
// You can change this to
|
|
||||||
// TOP, LEFT, BOTTOM, RIGHT
|
|
||||||
ui.setIndicatorPosition(BOTTOM);
|
|
||||||
|
|
||||||
// Defines where the first frame is located in the bar.
|
|
||||||
ui.setIndicatorDirection(LEFT_RIGHT);
|
|
||||||
|
|
||||||
// You can change the transition that is used
|
|
||||||
// SLIDE_LEFT, SLIDE_RIGHT, SLIDE_UP, SLIDE_DOWN
|
|
||||||
ui.setFrameAnimation(SLIDE_LEFT);
|
|
||||||
|
|
||||||
// Add frames - we subtract one from the framecount so there won't be a visual glitch when we take the boot screen out of the sequence.
|
|
||||||
ui.setFrames(bootFrames, bootFrameCount);
|
|
||||||
|
|
||||||
// Add overlays
|
|
||||||
ui.setOverlays(overlays, overlaysCount);
|
|
||||||
|
|
||||||
// Initialising the UI will init the display too.
|
// Initialising the UI will init the display too.
|
||||||
ui.init();
|
ui.init();
|
||||||
|
ui.setTimePerTransition(300); // msecs
|
||||||
|
ui.setIndicatorPosition(BOTTOM);
|
||||||
|
// Defines where the first frame is located in the bar.
|
||||||
|
ui.setIndicatorDirection(LEFT_RIGHT);
|
||||||
|
ui.setFrameAnimation(SLIDE_LEFT);
|
||||||
|
// Don't show the page swipe dots while in boot screen.
|
||||||
|
ui.disableAllIndicators();
|
||||||
|
|
||||||
// Scroll buffer
|
// Add frames.
|
||||||
|
static FrameCallback bootFrames[] = {drawBootScreen};
|
||||||
|
static const int bootFrameCount = sizeof(bootFrames) / sizeof(bootFrames[0]);
|
||||||
|
ui.setFrames(bootFrames, bootFrameCount);
|
||||||
|
// No overlays.
|
||||||
|
ui.setOverlays(nullptr, 0);
|
||||||
|
|
||||||
|
// Require presses to switch between frames.
|
||||||
|
ui.disableAutoTransition();
|
||||||
|
|
||||||
|
// Set up a log buffer with 3 lines, 32 chars each.
|
||||||
dispdev.setLogBuffer(3, 32);
|
dispdev.setLogBuffer(3, 32);
|
||||||
|
|
||||||
setOn(true); // update our screenOn bool
|
#ifdef FLIP_SCREEN_VERTICALLY
|
||||||
|
dispdev.flipScreenVertically();
|
||||||
#ifdef BICOLOR_DISPLAY
|
|
||||||
dispdev.flipScreenVertically(); // looks better without this on lora32
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// dispdev.setFont(Custom_ArialMT_Plain_10);
|
// Turn on the display.
|
||||||
|
handleSetOn(true);
|
||||||
|
|
||||||
ui.disableAutoTransition(); // we now require presses
|
// On some ssd1306 clones, the first draw command is discarded, so draw it
|
||||||
#endif
|
// twice initially.
|
||||||
|
ui.update();
|
||||||
|
ui.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
#define TRANSITION_FRAMERATE 30 // fps
|
|
||||||
#define IDLE_FRAMERATE 10 // in fps
|
|
||||||
|
|
||||||
static uint32_t targetFramerate = IDLE_FRAMERATE;
|
|
||||||
|
|
||||||
void Screen::doTask()
|
void Screen::doTask()
|
||||||
{
|
{
|
||||||
if (!disp)
|
// If we don't have a screen, don't ever spend any CPU for us.
|
||||||
{ // If we don't have a screen, don't ever spend any CPU for us
|
if (!useDisplay) {
|
||||||
setPeriod(0);
|
setPeriod(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!screenOn)
|
// Process incoming commands.
|
||||||
{ // If we didn't just wake and the screen is still off, then stop updating until it is on again
|
for (;;) {
|
||||||
|
CmdItem cmd;
|
||||||
|
if (!cmdQueue.dequeue(&cmd, 0)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (cmd.cmd) {
|
||||||
|
case Cmd::SET_ON:
|
||||||
|
handleSetOn(true);
|
||||||
|
break;
|
||||||
|
case Cmd::SET_OFF:
|
||||||
|
handleSetOn(false);
|
||||||
|
break;
|
||||||
|
case Cmd::ON_PRESS:
|
||||||
|
handleOnPress();
|
||||||
|
break;
|
||||||
|
case Cmd::START_BLUETOOTH_PIN_SCREEN:
|
||||||
|
handleStartBluetoothPinScreen(cmd.bluetooth_pin);
|
||||||
|
break;
|
||||||
|
case Cmd::STOP_BLUETOOTH_PIN_SCREEN:
|
||||||
|
case Cmd::STOP_BOOT_SCREEN:
|
||||||
|
setFrames();
|
||||||
|
break;
|
||||||
|
case Cmd::PRINT:
|
||||||
|
handlePrint(cmd.print_text);
|
||||||
|
free(cmd.print_text);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
DEBUG_MSG("BUG: invalid cmd");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!screenOn) { // If we didn't just wake and the screen is still off, then
|
||||||
|
// stop updating until it is on again
|
||||||
setPeriod(0);
|
setPeriod(0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Switch to a low framerate (to save CPU) when we are not in transition
|
// Switch to a low framerate (to save CPU) when we are not in transition
|
||||||
// but we should only call setTargetFPS when framestate changes, because otherwise that breaks
|
// but we should only call setTargetFPS when framestate changes, because
|
||||||
// animations.
|
// otherwise that breaks animations.
|
||||||
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED)
|
if (targetFramerate != IDLE_FRAMERATE && ui.getUiState()->frameState == FIXED) {
|
||||||
{
|
|
||||||
// oldFrameState = ui.getUiState()->frameState;
|
// oldFrameState = ui.getUiState()->frameState;
|
||||||
DEBUG_MSG("Setting idle framerate\n");
|
DEBUG_MSG("Setting idle framerate\n");
|
||||||
targetFramerate = IDLE_FRAMERATE;
|
targetFramerate = IDLE_FRAMERATE;
|
||||||
ui.setTargetFPS(targetFramerate);
|
ui.setTargetFPS(targetFramerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// While showing the bluetooth pair screen all of our standard screen switching is stopped
|
// While showing the bootscreen or Bluetooth pair screen all of our
|
||||||
if (!showingBluetooth)
|
// standard screen switching is stopped.
|
||||||
{
|
if (showingNormalScreen) {
|
||||||
// Once we finish showing the bootscreen, remove it from the loop
|
// TODO(girts): decouple nodeDB from screen.
|
||||||
if (showingBootScreen)
|
// standard screen loop handling ehre
|
||||||
{
|
// If the # nodes changes, we need to regen our list of screens
|
||||||
if (millis() > 3 * 1000) // we show the boot screen for a few seconds only
|
if (nodeDB.updateGUI || nodeDB.updateTextMessage) {
|
||||||
{
|
setFrames();
|
||||||
showingBootScreen = false;
|
nodeDB.updateGUI = false;
|
||||||
setFrames();
|
nodeDB.updateTextMessage = false;
|
||||||
}
|
|
||||||
}
|
|
||||||
else // standard screen loop handling ehre
|
|
||||||
{
|
|
||||||
// If the # nodes changes, we need to regen our list of screens
|
|
||||||
if (nodeDB.updateGUI || nodeDB.updateTextMessage)
|
|
||||||
{
|
|
||||||
setFrames();
|
|
||||||
nodeDB.updateGUI = false;
|
|
||||||
nodeDB.updateTextMessage = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This must be after we possibly do screen_set_frames() to ensure we draw the new data
|
|
||||||
ui.update();
|
ui.update();
|
||||||
|
|
||||||
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate, ui.getUiState()->frameState);
|
// DEBUG_MSG("want fps %d, fixed=%d\n", targetFramerate,
|
||||||
// If we are scrolling we need to be called soon, otherwise just 1 fps (to save CPU)
|
// ui.getUiState()->frameState); If we are scrolling we need to be called
|
||||||
// We also ask to be called twice as fast as we really need so that any rounding errors still result
|
// soon, otherwise just 1 fps (to save CPU) We also ask to be called twice
|
||||||
// with the correct framerate
|
// as fast as we really need so that any rounding errors still result with
|
||||||
|
// the correct framerate
|
||||||
setPeriod(1000 / targetFramerate);
|
setPeriod(1000 / targetFramerate);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "PowerFSM.h"
|
|
||||||
|
|
||||||
// Show the bluetooth PIN screen
|
|
||||||
void screen_start_bluetooth(uint32_t pin)
|
|
||||||
{
|
|
||||||
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
|
||||||
|
|
||||||
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
|
|
||||||
|
|
||||||
DEBUG_MSG("showing bluetooth screen\n");
|
|
||||||
showingBluetooth = true;
|
|
||||||
powerFSM.trigger(EVENT_BLUETOOTH_PAIR);
|
|
||||||
|
|
||||||
ui.setFrames(btFrames, 1); // Just show the bluetooth frame
|
|
||||||
// we rely on our main loop to show this screen (because we are invoked deep inside of bluetooth callbacks)
|
|
||||||
// ui.update(); // manually draw once, because I'm not sure if loop is getting called
|
|
||||||
}
|
|
||||||
|
|
||||||
// restore our regular frame list
|
// restore our regular frame list
|
||||||
void Screen::setFrames()
|
void Screen::setFrames()
|
||||||
{
|
{
|
||||||
DEBUG_MSG("showing standard frames\n");
|
DEBUG_MSG("showing standard frames\n");
|
||||||
|
showingNormalScreen = true;
|
||||||
|
|
||||||
size_t numnodes = nodeDB.getNumNodes();
|
size_t numnodes = nodeDB.getNumNodes();
|
||||||
// We don't show the node info our our node (if we have it yet - we should)
|
// We don't show the node info our our node (if we have it yet - we should)
|
||||||
@@ -691,28 +585,49 @@ void Screen::setFrames()
|
|||||||
|
|
||||||
// If we have a text message - show it first
|
// If we have a text message - show it first
|
||||||
if (devicestate.has_rx_text_message)
|
if (devicestate.has_rx_text_message)
|
||||||
nonBootFrames[numframes++] = drawTextMessageFrame;
|
normalFrames[numframes++] = drawTextMessageFrame;
|
||||||
|
|
||||||
// then all the nodes
|
// then all the nodes
|
||||||
for (size_t i = 0; i < numnodes; i++)
|
for (size_t i = 0; i < numnodes; i++)
|
||||||
nonBootFrames[numframes++] = drawNodeInfo;
|
normalFrames[numframes++] = drawNodeInfo;
|
||||||
|
|
||||||
// then the debug info
|
// then the debug info
|
||||||
nonBootFrames[numframes++] = drawDebugInfo;
|
normalFrames[numframes++] = drawDebugInfo;
|
||||||
|
|
||||||
ui.setFrames(nonBootFrames, numframes);
|
ui.setFrames(normalFrames, numframes);
|
||||||
showingBluetooth = false;
|
ui.enableAllIndicators();
|
||||||
|
|
||||||
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
|
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list
|
||||||
|
// just changed)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// handle press of the button
|
void Screen::handleStartBluetoothPinScreen(uint32_t pin)
|
||||||
void Screen::onPress()
|
{
|
||||||
|
DEBUG_MSG("showing bluetooth screen\n");
|
||||||
|
showingNormalScreen = false;
|
||||||
|
|
||||||
|
static FrameCallback btFrames[] = {drawFrameBluetooth};
|
||||||
|
|
||||||
|
snprintf(btPIN, sizeof(btPIN), "%06d", pin);
|
||||||
|
|
||||||
|
ui.disableAllIndicators();
|
||||||
|
ui.setFrames(btFrames, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::handlePrint(const char *text)
|
||||||
|
{
|
||||||
|
DEBUG_MSG("Screen: %s", text);
|
||||||
|
if (!useDisplay)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dispdev.print(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Screen::handleOnPress()
|
||||||
{
|
{
|
||||||
// If screen was off, just wake it, otherwise advance to next frame
|
// If screen was off, just wake it, otherwise advance to next frame
|
||||||
// If we are in a transition, the press must have bounced, drop it.
|
// If we are in a transition, the press must have bounced, drop it.
|
||||||
if (ui.getUiState()->frameState == FIXED)
|
if (ui.getUiState()->frameState == FIXED) {
|
||||||
{
|
|
||||||
setPeriod(1); // redraw ASAP
|
setPeriod(1); // redraw ASAP
|
||||||
ui.nextFrame();
|
ui.nextFrame();
|
||||||
|
|
||||||
@@ -723,3 +638,5 @@ void Screen::onPress()
|
|||||||
ui.setTargetFPS(targetFramerate);
|
ui.setTargetFPS(targetFramerate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace meshtastic
|
||||||
|
|||||||
135
src/screen.h
135
src/screen.h
@@ -1,44 +1,127 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <OLEDDisplayUi.h>
|
||||||
|
#include <SSD1306Wire.h>
|
||||||
|
|
||||||
#include "PeriodicTask.h"
|
#include "PeriodicTask.h"
|
||||||
|
#include "TypedQueue.h"
|
||||||
|
|
||||||
void screen_print(const char * text);
|
namespace meshtastic
|
||||||
void screen_print(const char * text, uint8_t x, uint8_t y, uint8_t alignment);
|
{
|
||||||
|
|
||||||
|
/// Deals with showing things on the screen of the device.
|
||||||
// Show the bluetooth PIN screen
|
//
|
||||||
void screen_start_bluetooth(uint32_t pin);
|
// Other than setup(), this class is thread-safe. All state-changing calls are
|
||||||
|
// queued and executed when the main loop calls us.
|
||||||
// restore our regular frame list
|
|
||||||
void screen_set_frames();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Slowly I'm moving screen crap into this class
|
|
||||||
*/
|
|
||||||
class Screen : public PeriodicTask
|
class Screen : public PeriodicTask
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Screen(uint8_t address, uint8_t sda, uint8_t scl);
|
||||||
|
|
||||||
|
Screen(const Screen &) = delete;
|
||||||
|
Screen &operator=(const Screen &) = delete;
|
||||||
|
|
||||||
|
/// Initializes the UI, turns on the display, starts showing boot screen.
|
||||||
|
//
|
||||||
|
// Not thread safe - must be called before any other methods are called.
|
||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
virtual void doTask();
|
/// Turns the screen on/off.
|
||||||
|
void setOn(bool on) { enqueueCmd(CmdItem{.cmd = on ? Cmd::SET_ON : Cmd::SET_OFF}); }
|
||||||
|
|
||||||
/// Turn on the screen asap
|
/// Handles a button press.
|
||||||
void doWakeScreen();
|
void onPress() { enqueueCmd(CmdItem{.cmd = Cmd::ON_PRESS}); }
|
||||||
|
|
||||||
/// Is the screen currently on
|
/// Starts showing the Bluetooth PIN screen.
|
||||||
bool isOn();
|
//
|
||||||
|
// Switches over to a static frame showing the Bluetooth pairing screen
|
||||||
|
// with the PIN.
|
||||||
|
void startBluetoothPinScreen(uint32_t pin)
|
||||||
|
{
|
||||||
|
CmdItem cmd;
|
||||||
|
cmd.cmd = Cmd::START_BLUETOOTH_PIN_SCREEN;
|
||||||
|
cmd.bluetooth_pin = pin;
|
||||||
|
enqueueCmd(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
/// Turn the screen on/off
|
/// Stops showing the bluetooth PIN screen.
|
||||||
void setOn(bool on);
|
void stopBluetoothPinScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BLUETOOTH_PIN_SCREEN}); }
|
||||||
|
|
||||||
/// Handle a button press
|
/// Stops showing the boot screen.
|
||||||
void onPress();
|
void stopBootScreen() { enqueueCmd(CmdItem{.cmd = Cmd::STOP_BOOT_SCREEN}); }
|
||||||
|
|
||||||
/// Rebuilt our list of screens
|
/// Writes a string to the screen.
|
||||||
|
void print(const char *text)
|
||||||
|
{
|
||||||
|
CmdItem cmd;
|
||||||
|
cmd.cmd = Cmd::PRINT;
|
||||||
|
// TODO(girts): strdup() here is scary, but we can't use std::string as
|
||||||
|
// FreeRTOS queue is just dumbly copying memory contents. It would be
|
||||||
|
// nice if we had a queue that could copy objects by value.
|
||||||
|
cmd.print_text = strdup(text);
|
||||||
|
if (!enqueueCmd(cmd)) {
|
||||||
|
free(cmd.print_text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// Updates the UI.
|
||||||
|
//
|
||||||
|
// Called periodically from the main loop.
|
||||||
|
void doTask() final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Cmd {
|
||||||
|
INVALID,
|
||||||
|
SET_ON,
|
||||||
|
SET_OFF,
|
||||||
|
ON_PRESS,
|
||||||
|
START_BLUETOOTH_PIN_SCREEN,
|
||||||
|
STOP_BLUETOOTH_PIN_SCREEN,
|
||||||
|
STOP_BOOT_SCREEN,
|
||||||
|
PRINT,
|
||||||
|
};
|
||||||
|
struct CmdItem {
|
||||||
|
Cmd cmd;
|
||||||
|
union {
|
||||||
|
uint32_t bluetooth_pin;
|
||||||
|
char *print_text;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Enques given command item to be processed by main loop().
|
||||||
|
bool enqueueCmd(const CmdItem &cmd)
|
||||||
|
{
|
||||||
|
bool success = cmdQueue.enqueue(cmd, 0);
|
||||||
|
setPeriod(1); // handle ASAP
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implementations of various commands, called from doTask().
|
||||||
|
void handleSetOn(bool on);
|
||||||
|
void handleOnPress();
|
||||||
|
void handleStartBluetoothPinScreen(uint32_t pin);
|
||||||
|
void handlePrint(const char *text);
|
||||||
|
|
||||||
|
/// Rebuilds our list of frames (screens) to default ones.
|
||||||
void setFrames();
|
void setFrames();
|
||||||
private:
|
|
||||||
|
private:
|
||||||
|
/// Queue of commands to execute in doTask.
|
||||||
|
TypedQueue<CmdItem> cmdQueue;
|
||||||
|
/// Whether we are using a display
|
||||||
|
bool useDisplay = false;
|
||||||
|
/// Whether the display is currently powered
|
||||||
|
bool screenOn = false;
|
||||||
|
// Whether we are showing the regular screen (as opposed to booth screen or
|
||||||
|
// Bluetooth PIN screen)
|
||||||
|
bool showingNormalScreen = false;
|
||||||
|
/// Display device
|
||||||
|
SSD1306Wire dispdev;
|
||||||
|
/// UI helper for rendering to frames and switching between them
|
||||||
|
OLEDDisplayUi ui;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Screen screen;
|
} // namespace meshtastic
|
||||||
|
|||||||
225
src/sleep.cpp
225
src/sleep.cpp
@@ -1,20 +1,18 @@
|
|||||||
#include "configuration.h"
|
#include "sleep.h"
|
||||||
#include "rom/rtc.h"
|
|
||||||
#include <driver/rtc_io.h>
|
|
||||||
#include <TinyGPS++.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include "BluetoothUtil.h"
|
#include "BluetoothUtil.h"
|
||||||
#include "MeshBluetoothService.h"
|
|
||||||
#include "MeshService.h"
|
|
||||||
#include "GPS.h"
|
#include "GPS.h"
|
||||||
#include "screen.h"
|
#include "MeshBluetoothService.h"
|
||||||
|
#include "MeshRadio.h"
|
||||||
|
#include "MeshService.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "Periodic.h"
|
#include "Periodic.h"
|
||||||
|
#include "configuration.h"
|
||||||
#include "esp32/pm.h"
|
#include "esp32/pm.h"
|
||||||
#include "esp_pm.h"
|
#include "esp_pm.h"
|
||||||
#include "MeshRadio.h"
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "sleep.h"
|
#include "rom/rtc.h"
|
||||||
|
#include <Wire.h>
|
||||||
|
#include <driver/rtc_io.h>
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
#include "axp20x.h"
|
#include "axp20x.h"
|
||||||
@@ -40,156 +38,162 @@ esp_sleep_source_t wakeCause; // the reason we booted this time
|
|||||||
*/
|
*/
|
||||||
void setCPUFast(bool on)
|
void setCPUFast(bool on)
|
||||||
{
|
{
|
||||||
setCpuFrequencyMhz(on ? 240 : 80);
|
setCpuFrequencyMhz(on ? 240 : 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLed(bool ledOn)
|
void setLed(bool ledOn)
|
||||||
{
|
{
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
// toggle the led so we can get some rough sense of how often loop is pausing
|
// toggle the led so we can get some rough sense of how often loop is pausing
|
||||||
digitalWrite(LED_PIN, ledOn);
|
digitalWrite(LED_PIN, ledOn);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (axp192_found)
|
if (axp192_found) {
|
||||||
{
|
// blink the axp led
|
||||||
// blink the axp led
|
axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
|
||||||
axp.setChgLEDMode(ledOn ? AXP20X_LED_LOW_LEVEL : AXP20X_LED_OFF);
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void setGPSPower(bool on)
|
void setGPSPower(bool on)
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Setting GPS power=%d\n", on);
|
DEBUG_MSG("Setting GPS power=%d\n", on);
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (axp192_found)
|
if (axp192_found)
|
||||||
axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power
|
axp.setPowerOutPut(AXP192_LDO3, on ? AXP202_ON : AXP202_OFF); // GPS main power
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform power on init that we do on each wake from deep sleep
|
// Perform power on init that we do on each wake from deep sleep
|
||||||
void initDeepSleep()
|
void initDeepSleep()
|
||||||
{
|
{
|
||||||
bootCount++;
|
bootCount++;
|
||||||
wakeCause = esp_sleep_get_wakeup_cause();
|
wakeCause = esp_sleep_get_wakeup_cause();
|
||||||
/*
|
/*
|
||||||
Not using yet because we are using wake on all buttons being low
|
Not using yet because we are using wake on all buttons being low
|
||||||
|
|
||||||
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
|
wakeButtons = esp_sleep_get_ext1_wakeup_status(); // If one of these buttons is set it was the reason we woke
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to support busted boards, assume button one was pressed
|
if (wakeCause == ESP_SLEEP_WAKEUP_EXT1 && !wakeButtons) // we must have been using the 'all buttons rule for waking' to
|
||||||
wakeButtons = ((uint64_t)1) << buttons.gpios[0];
|
support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0];
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// If we booted because our timer ran out or the user pressed reset, send those as fake events
|
// If we booted because our timer ran out or the user pressed reset, send those as fake events
|
||||||
const char *reason = "reset"; // our best guess
|
const char *reason = "reset"; // our best guess
|
||||||
RESET_REASON hwReason = rtc_get_reset_reason(0);
|
RESET_REASON hwReason = rtc_get_reset_reason(0);
|
||||||
|
|
||||||
if (hwReason == RTCWDT_BROWN_OUT_RESET)
|
if (hwReason == RTCWDT_BROWN_OUT_RESET)
|
||||||
reason = "brownout";
|
reason = "brownout";
|
||||||
|
|
||||||
if (hwReason == TG0WDT_SYS_RESET)
|
if (hwReason == TG0WDT_SYS_RESET)
|
||||||
reason = "taskWatchdog";
|
reason = "taskWatchdog";
|
||||||
|
|
||||||
if (hwReason == TG1WDT_SYS_RESET)
|
if (hwReason == TG1WDT_SYS_RESET)
|
||||||
reason = "intWatchdog";
|
reason = "intWatchdog";
|
||||||
|
|
||||||
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
if (wakeCause == ESP_SLEEP_WAKEUP_TIMER)
|
||||||
reason = "timeout";
|
reason = "timeout";
|
||||||
|
|
||||||
DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason);
|
DEBUG_MSG("booted, wake cause %d (boot count %d), reset_reason=%s\n", wakeCause, bootCount, reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void doDeepSleep(uint64_t msecToWake)
|
void doDeepSleep(uint64_t msecToWake)
|
||||||
{
|
{
|
||||||
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
|
DEBUG_MSG("Entering deep sleep for %llu seconds\n", msecToWake / 1000);
|
||||||
|
|
||||||
// not using wifi yet, but once we are this is needed to shutoff the radio hw
|
// not using wifi yet, but once we are this is needed to shutoff the radio hw
|
||||||
// esp_wifi_stop();
|
// esp_wifi_stop();
|
||||||
|
|
||||||
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
|
BLEDevice::deinit(false); // We are required to shutdown bluetooth before deep or light sleep
|
||||||
|
|
||||||
screen.setOn(false); // datasheet says this will draw only 10ua
|
screen.setOn(false); // datasheet says this will draw only 10ua
|
||||||
|
|
||||||
// Put radio in sleep mode (will still draw power but only 0.2uA)
|
// Put radio in sleep mode (will still draw power but only 0.2uA)
|
||||||
service.radio.rf95.sleep();
|
service.radio.rf95.sleep();
|
||||||
|
|
||||||
nodeDB.saveToDisk();
|
nodeDB.saveToDisk();
|
||||||
|
|
||||||
#ifdef RESET_OLED
|
#ifdef RESET_OLED
|
||||||
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
|
digitalWrite(RESET_OLED, 1); // put the display in reset before killing its power
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef VEXT_ENABLE
|
#ifdef VEXT_ENABLE
|
||||||
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
|
digitalWrite(VEXT_ENABLE, 1); // turn off the display power
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
setLed(false);
|
setLed(false);
|
||||||
|
|
||||||
#ifdef T_BEAM_V10
|
#ifdef T_BEAM_V10
|
||||||
if (axp192_found)
|
if (axp192_found) {
|
||||||
{
|
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
|
||||||
// No need to turn this off if the power draw in sleep mode really is just 0.2uA and turning it off would
|
// leave floating input for the IRQ line
|
||||||
// leave floating input for the IRQ line
|
|
||||||
|
|
||||||
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
|
// If we want to leave the radio receving in would be 11.5mA current draw, but most of the time it is just waiting
|
||||||
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
|
// in its sequencer (true?) so the average power draw should be much lower even if we were listinging for packets
|
||||||
// all the time.
|
// all the time.
|
||||||
|
|
||||||
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
|
// axp.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LORA radio
|
||||||
|
|
||||||
setGPSPower(false);
|
setGPSPower(false);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
|
Some ESP32 IOs have internal pullups or pulldowns, which are enabled by default.
|
||||||
If an external circuit drives this pin in deep sleep mode, current consumption may
|
If an external circuit drives this pin in deep sleep mode, current consumption may
|
||||||
increase due to current flowing through these pullups and pulldowns.
|
increase due to current flowing through these pullups and pulldowns.
|
||||||
|
|
||||||
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
|
To isolate a pin, preventing extra current draw, call rtc_gpio_isolate() function.
|
||||||
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
|
For example, on ESP32-WROVER module, GPIO12 is pulled up externally.
|
||||||
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
|
GPIO12 also has an internal pulldown in the ESP32 chip. This means that in deep sleep,
|
||||||
some current will flow through these external and internal resistors, increasing deep
|
some current will flow through these external and internal resistors, increasing deep
|
||||||
sleep current above the minimal possible value.
|
sleep current above the minimal possible value.
|
||||||
|
|
||||||
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
|
Note: we don't isolate pins that are used for the LORA, LED, i2c, spi or the wake button
|
||||||
*/
|
*/
|
||||||
static const uint8_t rtcGpios[] = {/* 0, */ 2,
|
static const uint8_t rtcGpios[] = {/* 0, */ 2,
|
||||||
/* 4, */
|
/* 4, */
|
||||||
#ifndef USE_JTAG
|
#ifndef USE_JTAG
|
||||||
12, 13, /* 14, */ /* 15, */
|
12,
|
||||||
|
13,
|
||||||
|
/* 14, */ /* 15, */
|
||||||
#endif
|
#endif
|
||||||
/* 25, */ 26, /* 27, */
|
/* 25, */ 26, /* 27, */
|
||||||
32, 33, 34, 35, 36, 37, /* 38, */ 39};
|
32,
|
||||||
|
33,
|
||||||
|
34,
|
||||||
|
35,
|
||||||
|
36,
|
||||||
|
37,
|
||||||
|
/* 38, */ 39};
|
||||||
|
|
||||||
for (int i = 0; i < sizeof(rtcGpios); i++)
|
for (int i = 0; i < sizeof(rtcGpios); i++)
|
||||||
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
|
rtc_gpio_isolate((gpio_num_t)rtcGpios[i]);
|
||||||
|
|
||||||
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
|
// FIXME, disable internal rtc pullups/pulldowns on the non isolated pins. for inputs that we aren't using
|
||||||
// to detect wake and in normal operation the external part drives them hard.
|
// to detect wake and in normal operation the external part drives them hard.
|
||||||
|
|
||||||
// We want RTC peripherals to stay on
|
// We want RTC peripherals to stay on
|
||||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||||
|
|
||||||
#ifdef BUTTON_PIN
|
#ifdef BUTTON_PIN
|
||||||
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
|
// Only GPIOs which are have RTC functionality can be used in this bit map: 0,2,4,12-15,25-27,32-39.
|
||||||
uint64_t gpioMask = (1ULL << BUTTON_PIN);
|
uint64_t gpioMask = (1ULL << BUTTON_PIN);
|
||||||
|
|
||||||
#ifdef BUTTON_NEED_PULLUP
|
#ifdef BUTTON_NEED_PULLUP
|
||||||
gpio_pullup_en((gpio_num_t) BUTTON_PIN);
|
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Not needed because both of the current boards have external pullups
|
// Not needed because both of the current boards have external pullups
|
||||||
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of just the first)
|
// FIXME change polarity in hw so we can wake on ANY_HIGH instead - that would allow us to use all three buttons (instead of
|
||||||
// gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
// just the first) gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||||
|
|
||||||
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
|
esp_sleep_enable_ext1_wakeup(gpioMask, ESP_EXT1_WAKEUP_ALL_LOW);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
|
esp_sleep_enable_timer_wakeup(msecToWake * 1000ULL); // call expects usecs
|
||||||
esp_deep_sleep_start(); // TBD mA sleep current (battery)
|
esp_deep_sleep_start(); // TBD mA sleep current (battery)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -199,32 +203,33 @@ void doDeepSleep(uint64_t msecToWake)
|
|||||||
*/
|
*/
|
||||||
esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
|
esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more reasonable default
|
||||||
{
|
{
|
||||||
//DEBUG_MSG("Enter light sleep\n");
|
// DEBUG_MSG("Enter light sleep\n");
|
||||||
uint64_t sleepUsec = sleepMsec * 1000LL;
|
uint64_t sleepUsec = sleepMsec * 1000LL;
|
||||||
|
|
||||||
Serial.flush(); // send all our characters before we stop cpu clock
|
Serial.flush(); // send all our characters before we stop cpu clock
|
||||||
setBluetoothEnable(false); // has to be off before calling light sleep
|
setBluetoothEnable(false); // has to be off before calling light sleep
|
||||||
|
|
||||||
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
|
// NOTE! ESP docs say we must disable bluetooth and wifi before light sleep
|
||||||
|
|
||||||
// We want RTC peripherals to stay on
|
// We want RTC peripherals to stay on
|
||||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||||
|
|
||||||
#ifdef BUTTON_NEED_PULLUP
|
#ifdef BUTTON_NEED_PULLUP
|
||||||
gpio_pullup_en((gpio_num_t) BUTTON_PIN);
|
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low
|
gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_LOW_LEVEL); // when user presses, this button goes low
|
||||||
gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high
|
gpio_wakeup_enable((gpio_num_t)DIO0_GPIO, GPIO_INTR_HIGH_LEVEL); // RF95 interrupt, active high
|
||||||
#ifdef PMU_IRQ
|
#ifdef PMU_IRQ
|
||||||
// FIXME, disable wake due to PMU because it seems to fire all the time?
|
// FIXME, disable wake due to PMU because it seems to fire all the time?
|
||||||
// gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq
|
// gpio_wakeup_enable((gpio_num_t)PMU_IRQ, GPIO_INTR_HIGH_LEVEL); // pmu irq
|
||||||
#endif
|
#endif
|
||||||
assert(esp_sleep_enable_gpio_wakeup() == ESP_OK);
|
assert(esp_sleep_enable_gpio_wakeup() == ESP_OK);
|
||||||
assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK);
|
assert(esp_sleep_enable_timer_wakeup(sleepUsec) == ESP_OK);
|
||||||
assert(esp_light_sleep_start() == ESP_OK);
|
assert(esp_light_sleep_start() == ESP_OK);
|
||||||
//DEBUG_MSG("Exit light sleep b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(DIO0_GPIO), digitalRead(PMU_IRQ));
|
// DEBUG_MSG("Exit light sleep b=%d, rf95=%d, pmu=%d\n", digitalRead(BUTTON_PIN), digitalRead(DIO0_GPIO),
|
||||||
return esp_sleep_get_wakeup_cause();
|
// digitalRead(PMU_IRQ));
|
||||||
|
return esp_sleep_get_wakeup_cause();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
|||||||
Reference in New Issue
Block a user