Compare commits

...

33 Commits
0.1.6 ... 0.1.7

Author SHA1 Message Date
Kevin Hester
2ce1b4bb2c Merge pull request #42 from geeksville/master
release 0.1.7
2020-03-17 20:02:01 -07:00
geeksville
f002bee4f4 release 0.1.7 2020-03-17 19:50:26 -07:00
Kevin Hester
9c69326ed9 Merge pull request #39 from geeksville/bug17
Bug17
2020-03-17 19:42:44 -07:00
Kevin Hester
7a617d5378 Merge branch 'master' into bug17 2020-03-17 19:40:25 -07:00
Kevin Hester
90edae1ce0 Merge pull request #41 from geeksville/new-oled
fix #40 force an extra redraw for the bootscreen,
2020-03-17 19:39:32 -07:00
geeksville
2134b4db9b fix #40 force an extra redraw for the bootscreen,
some clones drop the first draw cmd
2020-03-17 16:36:48 -07:00
geeksville
7b1ffb5c09 #17 WIP add tool to capture the system portion of flash
based on initial encouraging feedback from @illperipherals

if more people report success, we'll need to change the install instructions
to:

esptool.py --port COM10 --baud 921600 erase_flash
esptool.py --port COM10 --baud 921600 write_flash 0x1000 system-info.bin
esptool.py --port COM10 --baud 921600 write_flash 0x10000 firmware-HELTEC-US-0.1.6.bin
2020-03-17 11:17:58 -07:00
geeksville
535f07d927 add a tbeam program script 2020-03-17 11:16:22 -07:00
Kevin Hester
bfad136137 Merge pull request #31 from geeksville/master
@girtsf can you review these changes and blessmotize if they seem good?
2020-03-16 09:37:31 -07:00
geeksville
36bee8fa53 Merge remote-tracking branch 'root/master' 2020-03-16 09:30:24 -07:00
Kevin Hester
99cb0b3855 Merge pull request #37 from girtsf/fix-build-2
fix build: add missing include to screen.cpp
2020-03-16 09:30:14 -07:00
Girts Folkmanis
41c95eaff7 fix build: add missing include to screen.cpp 2020-03-16 09:26:40 -07:00
geeksville
ca8a25f585 Merge remote-tracking branch 'root/master' 2020-03-16 09:03:21 -07:00
Kevin Hester
51a8700391 Merge pull request #35 from girtsf/underp-lock-paths
underp include paths in lock.h
2020-03-16 09:02:54 -07:00
Kevin Hester
f099a31a29 Merge pull request #34 from girtsf/patch-1
run CI on pull requests as well
2020-03-16 09:01:28 -07:00
Kevin Hester
52dbc4e15d Merge pull request #36 from girtsf/various-cleanups
Various cleanups
2020-03-16 09:00:42 -07:00
Girts Folkmanis
8cabb3ea3d add .clang-format file
Tried to infer the style from existing files.
2020-03-15 19:29:55 -07:00
Girts Folkmanis
7a4a1af332 TypedQueue: make functions return bools instead of BaseType_t
Minor cleanup to hide away some FreeRTOS bits.

Note: I believe src/CustomRF95.cpp:62 had a bug where it had the
condition inverted.
2020-03-15 19:29:04 -07:00
Girts Folkmanis
90ecdf229e add locks to PeriodicTask 2020-03-15 19:29:00 -07:00
geeksville
24ac907780 auto generate channel numbers from name
NOTE: All radios on a channel will need to be updated to this release
before they can talk together again.
2020-03-15 17:51:57 -07:00
geeksville
5037fb830e fix build (and autoformat in visual studio code) 2020-03-15 17:50:48 -07:00
Girts Folkmanis
79f1346359 underp include paths in lock.h
Had the casing wrong, but could get away with it on a mac.
2020-03-15 17:43:42 -07:00
Girts
35dada683a run CI on pull requests as well 2020-03-15 17:42:48 -07:00
geeksville
30a431788d we now do bidirectional comms to GPS at startup, so we can always trust isConnected 2020-03-15 16:57:21 -07:00
geeksville
d0b8adab75 In my work for #11 I accidentially created a serious bug on Heltec...
devices.  It caused bogus i2c transactions when device would go to sleep.
Fixed now, also, I now treat GPS usage uniformly between TBEAM and HELTEC
we always probe for and use the GPS if we find it.

Which means for the extra nerds
(someone requested this, I'm sorry - I don't remember who) you can now
optionally attach an external GPS to HELTECs if you want.  The pins are:

 #define GPS_RX_PIN 34
 #define GPS_TX_PIN 12

(@girtsf, sorry about including formatting changes in this PR, apparently
I had my IDE set to not autoreformat until just now
2020-03-15 16:57:21 -07:00
geeksville
74f7b7b622 print extra info the next time this error occurs 2020-03-15 16:57:21 -07:00
geeksville
9ec8562ce7 fix old geeksville link (though github provides redirects) 2020-03-15 16:57:21 -07:00
Kevin Hester
0cbcb7a9bd Merge pull request #32 from girtsf/add-lock-etc
add a Lock, LockGuard and printThreadInfo
2020-03-15 16:55:48 -07:00
Girts Folkmanis
2874b22d6c add a Lock, LockGuard and printThreadInfo
* `Lock`: trivial wrapper for FreeRTOS binary semaphores
* `LockGuard`: RAII wrapper for using `Lock`
* `printThreadInfo`: helper for showing which core/FreeRTOS task we are
  running under
2020-03-15 16:52:19 -07:00
Kevin Hester
3c9be48445 Merge pull request #30 from girtsf/fix-build
fix the build: remove includes for TinyGPS that's not longer used or …
2020-03-15 14:30:04 -07:00
Girts Folkmanis
b5201f928b fix the build: remove includes for TinyGPS that's not longer used or in deps 2020-03-15 13:27:00 -07:00
Kevin Hester
2591859df5 Merge pull request #29 from girtsf/exception-decoder
check in script to decode backtraces
2020-03-15 12:38:44 -07:00
Girts Folkmanis
c411db111b check in script to decode backtraces 2020-03-15 12:29:15 -07:00
30 changed files with 599 additions and 119 deletions

6
.clang-format Normal file
View File

@@ -0,0 +1,6 @@
Language: Cpp
IndentWidth: 4
ColumnLimit: 130
PointerAlignment: Right
BreakBeforeBraces: Linux
AllowShortFunctionsOnASingleLine: Inline

View File

@@ -1,5 +1,7 @@
name: Continuous Integration name: Continuous Integration
on: push on:
- push
- pull_request
jobs: jobs:
main: main:

329
bin/exception_decoder.py Executable file
View 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
View 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
View File

@@ -0,0 +1 @@
esptool.py --baud 921600 read_flash 0x1000 0xf000 system-info.img

View File

@@ -1,3 +1,3 @@
export VERSION=0.1.6 export VERSION=0.1.7

View File

@@ -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

Binary file not shown.

View File

@@ -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

Submodule proto updated: 66e926740a...398fdf3625

View File

@@ -5,5 +5,5 @@ release. It is used by the android app for forcing software updates. Do not ed
Generated by bin/buildall.sh --> Generated by bin/buildall.sh -->
<resources> <resources>
<string name="cur_firmware_version">0.1.6</string> <string name="cur_firmware_version">0.1.7</string>
</resources> </resources>

View File

@@ -126,8 +126,7 @@ void CustomRF95::handleInterrupt()
// 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

View File

@@ -137,12 +137,15 @@ 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()
{ {
if (isConnected)
ublox.powerOff(); ublox.powerOff();
} }
void GPS::doTask() void GPS::doTask()
{ {
#ifdef GPS_RX_PIN #ifdef GPS_RX_PIN
if (isConnected)
{
// Consume all characters that have arrived // Consume all characters that have arrived
// getPVT automatically calls checkUblox // getPVT automatically calls checkUblox
@@ -160,8 +163,6 @@ void GPS::doTask()
{ {
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).
*/ */
@@ -185,8 +186,6 @@ 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
@@ -202,7 +201,7 @@ void GPS::doTask()
} }
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

View File

@@ -66,16 +66,14 @@ 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 && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool assert(p >= buf && (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 && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool assert(p >= buf && (p - buf) < maxElements); // sanity check to make sure a programmer didn't free something that didn't come from this pool
} }
}; };

View File

@@ -9,8 +9,6 @@
#include "configuration.h" #include "configuration.h"
#include "NodeDB.h" #include "NodeDB.h"
#define DEFAULT_CHANNEL_NUM 3 // we randomly pick one
/// 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};
@@ -39,7 +37,6 @@ MeshRadio::MeshRadio(MemoryPool<MeshPacket> &_pool, PointerQueue<MeshPacket> &_r
channelSettings.modem_config = ChannelSettings_ModemConfig_Bw125Cr48Sf4096; // slow and long range 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
@@ -81,6 +78,22 @@ bool MeshRadio::init()
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
@@ -91,10 +104,9 @@ void MeshRadio::reloadConfig()
// setModemConfig(Bw125Cr48Sf4096); // slow and reliable? // setModemConfig(Bw125Cr48Sf4096); // slow and reliable?
// rf95.setPreambleLength(8); // Default is 8 // 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 // Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM
float center_freq = CH0 + CH_SPACING * channelSettings.channel_num; int channel_num = hash(channelSettings.name) % NUM_CHANNELS;
float center_freq = CH0 + CH_SPACING * channel_num;
if (!rf95.setFrequency(center_freq)) if (!rf95.setFrequency(center_freq))
{ {
DEBUG_MSG("setFrequency failed\n"); DEBUG_MSG("setFrequency failed\n");
@@ -109,7 +121,7 @@ void MeshRadio::reloadConfig()
// FIXME - can we do this? It seems to be in the Heltec board. // FIXME - can we do this? It seems to be in the Heltec board.
rf95.setTxPower(channelSettings.tx_power, false); 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); DEBUG_MSG("Set radio: name=%s. config=%u, ch=%d, txpower=%d\n", channelSettings.name, channelSettings.modem_config, channel_num, channelSettings.tx_power);
// Done with init tell radio to start receiving // Done with init tell radio to start receiving
rf95.setModeRx(); rf95.setModeRx();

View File

@@ -173,7 +173,7 @@ void MeshService::handleFromRadio(MeshPacket *mp)
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);
@@ -255,6 +255,9 @@ void MeshService::handleToRadio(std::string s)
break; break;
} }
} }
else {
DEBUG_MSG("Error: ignoring malformed toradio\n");
}
} }
void MeshService::sendToMesh(MeshPacket *p) void MeshService::sendToMesh(MeshPacket *p)

View File

@@ -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)
{ {
lastMsec = now; meshtastic::LockGuard lg(&lock);
doTask(); uint32_t now = millis();
if (!period || (now - lastMsec) < period) {
return;
} }
lastMsec = now;
}
// Release the lock in case the task wants to change the period.
doTask();
} }
void Periodic::doTask() void Periodic::doTask()

View File

@@ -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,7 +28,11 @@ 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;

View File

@@ -18,7 +18,7 @@ public:
{ {
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 +26,6 @@ public:
{ {
T *p; T *p;
return this->dequeueFromISR(&p, higherPriWoken) == pdTRUE ? p : NULL; return this->dequeueFromISR(&p, higherPriWoken) ? p : nullptr;
} }
}; };

View File

@@ -1,15 +1,19 @@
#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:
@@ -34,24 +38,22 @@ public:
return uxQueueMessagesWaiting(h) == 0; return uxQueueMessagesWaiting(h) == 0;
} }
// pdTRUE for success else failure bool enqueue(T x, TickType_t maxWait = portMAX_DELAY)
BaseType_t enqueue(T x, TickType_t maxWait = portMAX_DELAY)
{ {
return xQueueSendToBack(h, &x, maxWait); return xQueueSendToBack(h, &x, maxWait) == pdTRUE;
} }
BaseType_t enqueueFromISR(T x, BaseType_t *higherPriWoken) bool enqueueFromISR(T x, BaseType_t *higherPriWoken)
{ {
return xQueueSendToBackFromISR(h, &x, higherPriWoken); return xQueueSendToBackFromISR(h, &x, higherPriWoken) == pdTRUE;
} }
// pdTRUE for success else failure bool dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
BaseType_t dequeue(T *p, TickType_t maxWait = portMAX_DELAY)
{ {
return xQueueReceive(h, p, maxWait); return xQueueReceive(h, p, maxWait) == pdTRUE;
} }
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);
} }

View File

@@ -41,8 +41,8 @@ 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
@@ -84,14 +84,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

20
src/debug.cpp Normal file
View 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
View 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

35
src/lock.cpp Normal file
View 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
View 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

View File

@@ -24,7 +24,6 @@
#include "configuration.h" #include "configuration.h"
#include "rom/rtc.h" #include "rom/rtc.h"
#include <driver/rtc_io.h> #include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h> #include <Wire.h>
#include "BluetoothUtil.h" #include "BluetoothUtil.h"
#include "MeshBluetoothService.h" #include "MeshBluetoothService.h"

View File

@@ -30,7 +30,7 @@ bool pb_decode_from_bytes(const uint8_t *srcbuf, size_t srcbufsize, const pb_msg
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\n", PB_GET_ERROR(&stream)); DEBUG_MSG("Error: can't decode protobuf %s, pb_msgdesc 0x%p\n", PB_GET_ERROR(&stream), fields);
return false; return false;
} }
else else

View File

@@ -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];
@@ -173,7 +172,7 @@ 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}
@@ -186,7 +185,7 @@ 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}
@@ -197,7 +196,6 @@ typedef struct _ToRadio {
/* 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
@@ -303,7 +301,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)
@@ -421,12 +418,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 63
#define DeviceState_size 15064 #define DeviceState_size 15058
#define FromRadio_size 301 #define FromRadio_size 301
#define ToRadio_size 295 #define ToRadio_size 295

View File

@@ -31,6 +31,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "mesh-pb-constants.h" #include "mesh-pb-constants.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "main.h" #include "main.h"
#include "configuration.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)
@@ -592,6 +593,7 @@ void Screen::setup()
// dispdev.setFont(Custom_ArialMT_Plain_10); // dispdev.setFont(Custom_ArialMT_Plain_10);
ui.disableAutoTransition(); // we now require presses ui.disableAutoTransition(); // we now require presses
ui.update(); // force an immediate draw of the bootscreen, because on some ssd1306 clones, the first draw command is discarded
#endif #endif
} }

View File

@@ -1,7 +1,6 @@
#include "configuration.h" #include "configuration.h"
#include "rom/rtc.h" #include "rom/rtc.h"
#include <driver/rtc_io.h> #include <driver/rtc_io.h>
#include <TinyGPS++.h>
#include <Wire.h> #include <Wire.h>
#include "BluetoothUtil.h" #include "BluetoothUtil.h"
#include "MeshBluetoothService.h" #include "MeshBluetoothService.h"