Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
hpanther
2020-11-05 14:47:18 +01:00
21 changed files with 191 additions and 1331 deletions

View File

@@ -1,11 +1,14 @@
# Changelog # Changelog
### Added ### Added
- function keys in editor: cursor, del, pos1, end. F1=help, F2=show, F10=report
### Fixed ### Fixed
- mixer IPM pumpstatus
### Changed ### Changed
- optimized MQTT for HA to reduce mem fragmentation issues
### Removed ### Removed
- old scripts

View File

@@ -124,9 +124,9 @@ class MqttSettingsForm extends React.Component<MqttSettingsFormProps> {
variant="outlined" variant="outlined"
onChange={handleValueChange('mqtt_qos')} onChange={handleValueChange('mqtt_qos')}
margin="normal"> margin="normal">
<MenuItem value={0}>0 - At most once</MenuItem> <MenuItem value={0}>0</MenuItem>
<MenuItem value={1}>1 - At least once</MenuItem> <MenuItem value={1}>1</MenuItem>
<MenuItem value={2}>2 - Exactly once</MenuItem> <MenuItem value={2}>2</MenuItem>
</SelectValidator> </SelectValidator>
<BlockFormControlLabel <BlockFormControlLabel
control={ control={

View File

@@ -3,8 +3,8 @@ import { Link, withRouter, RouteComponentProps } from "react-router-dom";
import { List, ListItem, ListItemIcon, ListItemText } from "@material-ui/core"; import { List, ListItem, ListItemIcon, ListItemText } from "@material-ui/core";
import SettingsIcon from '@material-ui/icons/Settings'; import TuneIcon from '@material-ui/icons/Tune';
import SettingsRemoteIcon from "@material-ui/icons/SettingsRemote"; import DashboardIcon from "@material-ui/icons/Dashboard";
import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication'; import { withAuthenticatedContext, AuthenticatedContextProps } from '../authentication';
@@ -18,13 +18,13 @@ class ProjectMenu extends Component<ProjectProps> {
<List> <List>
<ListItem to='/ems-esp/' selected={path.startsWith('/ems-esp/status') || path.startsWith('/ems-esp/devices') || path.startsWith('/ems-esp/help')} button component={Link}> <ListItem to='/ems-esp/' selected={path.startsWith('/ems-esp/status') || path.startsWith('/ems-esp/devices') || path.startsWith('/ems-esp/help')} button component={Link}>
<ListItemIcon> <ListItemIcon>
<SettingsRemoteIcon /> <DashboardIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Dashboard" /> <ListItemText primary="Dashboard" />
</ListItem> </ListItem>
<ListItem to='/ems-esp/settings' selected={path.startsWith('/ems-esp/settings')} button component={Link} disabled={!authenticatedContext.me.admin}> <ListItem to='/ems-esp/settings' selected={path.startsWith('/ems-esp/settings')} button component={Link} disabled={!authenticatedContext.me.admin}>
<ListItemIcon> <ListItemIcon>
<SettingsIcon /> <TuneIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary="Settings" /> <ListItemText primary="Settings" />
</ListItem> </ListItem>

View File

@@ -65,8 +65,8 @@ void Shell::start() {
#endif #endif
line_buffer_.reserve(maximum_command_line_length_); line_buffer_.reserve(maximum_command_line_length_);
oldline_.reserve(maximum_command_line_length_); line_old_.reserve(maximum_command_line_length_);
oldline_.clear(); line_old_.clear();
display_banner(); display_banner();
display_prompt(); display_prompt();
shells_.insert(shared_from_this()); shells_.insert(shared_from_this());
@@ -133,6 +133,14 @@ void Shell::loop_one() {
} }
} }
void Shell::set_command_str(const __FlashStringHelper * str) {
line_buffer_ = read_flash_string(str);
erase_current_line();
prompt_displayed_ = false;
display_prompt();
process_command();
}
void Shell::loop_normal() { void Shell::loop_normal() {
const int input = read_one_char(); const int input = read_one_char();
@@ -148,8 +156,7 @@ void Shell::loop_normal() {
// Interrupt (^C) // Interrupt (^C)
line_buffer_.clear(); line_buffer_.clear();
println(); println();
prompt_displayed_ = false; cursor_ = 0;
display_prompt();
break; break;
case '\x04': case '\x04':
@@ -160,51 +167,38 @@ void Shell::loop_normal() {
break; break;
case '\x08': case '\x08':
case '\x7F':
// Backspace (^H) // Backspace (^H)
// Delete (^?) case '\x7F':
if (!line_buffer_.empty()) { // Del/Backspace (^?)
erase_characters(1); if (line_buffer_.length() > cursor_) {
line_buffer_.pop_back(); line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
} }
break; break;
case '\x09': case '\x09':
// Tab (^I) // Tab (^I)
process_completion(); process_completion();
cursor_ = 0;
break; break;
case '\x0A': case '\x0A':
// Line feed (^J) // Line feed (^J)
if (previous_ != '\x0D') { if (previous_ != '\x0D') {
if (!line_buffer_.empty()) {
oldline_ = line_buffer_;
}
process_command(); process_command();
} }
break; break;
case '\x0C':
// New page (^L)
erase_current_line();
prompt_displayed_ = false;
display_prompt();
break;
case '\x0D': case '\x0D':
if (!line_buffer_.empty()) {
oldline_ = line_buffer_;
}
// Carriage return (^M) // Carriage return (^M)
process_command(); process_command();
break; break;
case '\x0C':
// New page (^L)
case '\x15': case '\x15':
// Delete line (^U) // Delete line (^U)
erase_current_line();
prompt_displayed_ = false;
line_buffer_.clear(); line_buffer_.clear();
display_prompt(); cursor_ = 0;
break; break;
case '\x17': case '\x17':
@@ -212,51 +206,104 @@ void Shell::loop_normal() {
delete_buffer_word(true); delete_buffer_word(true);
break; break;
case '\033':
// esc
esc_ = 0x80;
break;
default: default:
if (c >= '\x20' && c <= '\x7E') { if (esc_) {
if ((c == '[') || (c == 'O')) {
// start of sequence
} else if (c >= '0' && (c <= '9')) {
// numbers
esc_ = (esc_ & 0x7F) * 10 + c - '0';
} else if (c == 'A') {
// cursor up
line_buffer_ = line_old_;
cursor_ = 0;
esc_ = 0;
} else if (c == 'B') {
// cursor down
line_buffer_.clear();
cursor_ = 0;
esc_ = 0;
} else if (c == 'C') {
// cursor right
if (cursor_) {
cursor_--;
}
esc_ = 0;
} else if (c == 'D') {
// cursor left
if (cursor_ < line_buffer_.length()) {
cursor_++;
}
esc_ = 0;
} else if (c == 'H') {
// Home
cursor_ = line_buffer_.length();
esc_ = 0;
} else if (c == 'F') {
// End
cursor_ = 0;
esc_ = 0;
} else if (c == 'P') {
// F1
set_command_str(F("help"));
esc_ = 0;
} else if (c == 'Q') {
// F2
set_command_str(F("show"));
esc_ = 0;
} else if (c == '~') {
// function keys with number
if ((esc_ == 3) && cursor_) {
// del
cursor_--;
line_buffer_.erase(line_buffer_.length() - cursor_ - 1, 1);
} else if (esc_ == 4) {
// end
cursor_ = 0;
} else if (esc_ == 1) {
// pos1
cursor_ = line_buffer_.length();
} else if (esc_ == 11) {
// F1 and F10
set_command_str(F("help"));
} else if (esc_ == 12) {
// F2
set_command_str(F("show"));
} else if (esc_ == 20) {
// F9
line_buffer_ = read_flash_string(F("send telegram \"0B \""));
cursor_ = 1;
} else if (esc_ == 15) {
// F5
set_command_str(F("call system report"));
}
esc_ = 0;
} else {
// all other chars end sequence
esc_ = 0;
}
} else if (c >= '\x20' && c <= '\x7E') {
// ASCII text // ASCII text
if (line_buffer_.length() < maximum_command_line_length_) { if (line_buffer_.length() < maximum_command_line_length_) {
line_buffer_.push_back(c); line_buffer_.insert(line_buffer_.length() - cursor_, 1, c);
write((uint8_t)c);
}
// cursor up, get last command
if ((c == 'A') && (previous_ == '[')) {
erase_current_line();
prompt_displayed_ = false;
line_buffer_ = oldline_;
display_prompt();
}
// cursor back, delete cursor chars
if ((c == 'D') && (previous_ == '[')) {
line_buffer_.pop_back();
line_buffer_.pop_back();
// alternative work as backspace
// if (line_buffer_.length() > 0) {
// line_buffer_.pop_back();
// }
erase_current_line();
prompt_displayed_ = false;
display_prompt();
}
// cursor forward, only delete cursor chars
if ((c == 'C') && (previous_ == '[')) {
line_buffer_.pop_back();
line_buffer_.pop_back();
erase_current_line();
prompt_displayed_ = false;
display_prompt();
}
// cursor down(B): Delete line
if ((c == 'B') && (previous_ == '[')) {
erase_current_line();
prompt_displayed_ = false;
line_buffer_.clear();
display_prompt();
} }
} }
break; break;
} }
// common for all, display the complete line
erase_current_line();
prompt_displayed_ = false;
display_prompt();
if (cursor_) {
printf(F("\033[%dD"), cursor_);
}
previous_ = c; previous_ = c;
// This is a hack to let TelnetStream know that command // This is a hack to let TelnetStream know that command
@@ -428,16 +475,24 @@ void Shell::delete_buffer_word(bool display) {
if (pos == std::string::npos) { if (pos == std::string::npos) {
line_buffer_.clear(); line_buffer_.clear();
if (display) { cursor_ = 0;
erase_current_line();
prompt_displayed_ = false;
display_prompt();
}
} else { } else {
if (display) { if (display) {
erase_characters(line_buffer_.length() - pos); size_t pos1 = 0;
pos = 0;
while (pos1 < line_buffer_.length() - cursor_) {
pos = pos1;
pos1 = line_buffer_.find(' ', pos + 1);
}
line_buffer_.erase(pos, pos1 - pos);
if (line_buffer_.find(' ') == 0) {
line_buffer_.erase(0, 1);
}
cursor_ = line_buffer_.length() - pos;
} else {
line_buffer_.resize(pos);
cursor_ = 0;
} }
line_buffer_.resize(pos);
} }
} }
@@ -453,7 +508,6 @@ void Shell::maximum_command_line_length(size_t length) {
void Shell::process_command() { void Shell::process_command() {
CommandLine command_line{line_buffer_}; CommandLine command_line{line_buffer_};
line_buffer_.clear();
println(); println();
prompt_displayed_ = false; prompt_displayed_ = false;
@@ -467,8 +521,12 @@ void Shell::process_command() {
} else { } else {
println(F("No commands configured")); println(F("No commands configured"));
} }
line_old_ = line_buffer_;
} }
cursor_ = 0;
line_buffer_.clear();
if (running()) { if (running()) {
display_prompt(); display_prompt();
} }

View File

@@ -892,6 +892,7 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
* @since 0.1.0 * @since 0.1.0
*/ */
size_t vprintf(const __FlashStringHelper * format, va_list ap); size_t vprintf(const __FlashStringHelper * format, va_list ap);
void set_command_str(const __FlashStringHelper * str);
static const uuid::log::Logger logger_; /*!< uuid::log::Logger instance for shells. @since 0.1.0 */ static const uuid::log::Logger logger_; /*!< uuid::log::Logger instance for shells. @since 0.1.0 */
static std::set<std::shared_ptr<Shell>> shells_; /*!< Registered running shells to be executed. @since 0.1.0 */ static std::set<std::shared_ptr<Shell>> shells_; /*!< Registered running shells to be executed. @since 0.1.0 */
@@ -903,9 +904,11 @@ class Shell : public std::enable_shared_from_this<Shell>, public uuid::log::Hand
std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */ std::list<QueuedLogMessage> log_messages_; /*!< Queued log messages, in the order they were received. @since 0.1.0 */
size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */ size_t maximum_log_messages_ = MAX_LOG_MESSAGES; /*!< Maximum command line length in bytes. @since 0.6.0 */
std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */ std::string line_buffer_; /*!< Command line buffer. Limited to maximum_command_line_length() bytes. @since 0.1.0 */
std::string line_old_; /*!< old Command line buffer.*/
size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */ size_t maximum_command_line_length_ = MAX_COMMAND_LINE_LENGTH; /*!< Maximum command line length in bytes. @since 0.6.0 */
unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */ unsigned char previous_ = 0; /*!< Previous character that was entered on the command line. Used to detect CRLF line endings. @since 0.1.0 */
std::string oldline_; /*!< old Command line buffer.*/ uint8_t cursor_ = 0; /*!< cursor position from end of line */
uint8_t esc_ = 0; /*!< esc sequence running */
Mode mode_ = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */ Mode mode_ = Mode::NORMAL; /*!< Current execution mode. @since 0.1.0 */
std::unique_ptr<ModeData> mode_data_ = nullptr; /*!< Data associated with the current execution mode. @since 0.1.0 */ std::unique_ptr<ModeData> mode_data_ = nullptr; /*!< Data associated with the current execution mode. @since 0.1.0 */
bool stopped_ = false; /*!< Indicates that the shell has been stopped. @since 0.1.0 */ bool stopped_ = false; /*!< Indicates that the shell has been stopped. @since 0.1.0 */

View File

@@ -1,26 +0,0 @@
#!/usr/bin/env python
from subprocess import call
import os
# example stackdmp.txt would contain text like below copied & pasted from a 'crash dump' command:
# >>>stack>>>
# 3fffff20: 3fff32f0 00000003 3fff3028 402101b2
# 3fffff30: 3fffdad0 3fff3280 0000000d 402148aa
# 3fffff40: 3fffdad0 3fff3280 3fff326c 3fff32f0
# 3fffff50: 0000000d 3fff326c 3fff3028 402103bd
# 3fffff60: 0000000d 3fff34cc 40211de4 3fff34cc
# 3fffff70: 3fff3028 3fff14c4 3fff301c 3fff34cc
# 3fffff80: 3fffdad0 3fff14c4 3fff3028 40210493
# 3fffff90: 3fffdad0 00000000 3fff14c4 4020a738
# 3fffffa0: 3fffdad0 00000000 3fff349c 40211e90
# 3fffffb0: feefeffe feefeffe 3ffe8558 40100b01
# <<<stack<<<
call(['python', 'scripts/decoder.py ', '-s', '-e', os.getcwd()+"/.pio/build/esp12e/firmware.elf", 'scripts/stackdmp.txt'])
# example for linux:
# % cd EMS-ESP
# % python scripts/decoder_linux.py -s -e .pio/build/esp12e/firmware.elf scripts/stackdmp.txt
# python decoder_linux.py -s -e ../.pio/build/esp8266-debug/firmware.elf stackdmp.txt

View File

@@ -1,169 +0,0 @@
#!/bin/bash
set -e
### Functions
is_git() {
command -v git >/dev/null 2>&1 || return 1
command git rev-parse >/dev/null 2>&1 || return 1
return 0
}
stat_bytes() {
filesize=`du -k "$1" | cut -f1;`
echo 'size:' $filesize 'bytes'
}
# Available environments
list_envs() {
grep env: platformio.ini | sed 's/\[env:\(.*\)\]/\1/g'
}
print_available() {
echo "--------------------------------------------------------------"
echo "Available environments:"
for environment in $available; do
echo "-> $environment"
done
}
print_environments() {
echo "--------------------------------------------------------------"
echo "Current environments:"
for environment in $environments; do
echo "-> $environment"
done
}
set_default_environments() {
# Hook to build in parallel when using travis
if [[ "${TRAVIS_BUILD_STAGE_NAME}" = "Release" ]] && ${par_build}; then
environments=$(echo ${available} | \
awk -v par_thread=${par_thread} -v par_total_threads=${par_total_threads} \
'{ for (i = 1; i <= NF; i++) if (++j % par_total_threads == par_thread ) print $i; }')
return
fi
# Only build travis target
if [[ "${TRAVIS_BUILD_STAGE_NAME}" = "Test" ]]; then
environments=$travis
return
fi
# Fallback to all available environments
environments=$available
}
build_environments() {
echo "--------------------------------------------------------------"
echo "Building firmware images..."
# don't move to firmware folder until Travis fixed (see https://github.com/travis-ci/dpl/issues/846#issuecomment-547157406)
# mkdir -p $destination
for environment in $environments; do
echo "* EMS-ESP-$version-$environment.bin"
platformio run --silent --environment $environment || exit 1
stat_bytes .pio/build/$environment/firmware.bin
# mv .pio/build/$environment/firmware.bin $destination/EMS-ESP-$version-$environment.bin
# mv .pio/build/$environment/firmware.bin EMS-ESP-$version-$environment.bin
mv .pio/build/$environment/firmware.bin EMS-ESP-dev-$environment.bin
done
echo "--------------------------------------------------------------"
}
####### MAIN
destination=firmware
version_file=./src/version.h
version=$(grep -E '^#define EMSESP_APP_VERSION' $version_file | awk '{print $3}' | sed 's/"//g')
if ${TRAVIS:-false}; then
git_revision=${TRAVIS_COMMIT::7}
git_tag=${TRAVIS_TAG}
elif is_git; then
git_revision=$(git rev-parse --short HEAD)
git_tag=$(git tag --contains HEAD)
else
git_revision=unknown
git_tag=
fi
echo $git_tag
if [[ -n $git_tag ]]; then
new_version=${version/-*}
sed -i -e "s@$version@$new_version@" $version_file
version=$new_version
trap "git checkout -- $version_file" EXIT
fi
par_build=false
par_thread=${BUILDER_THREAD:-0}
par_total_threads=${BUILDER_TOTAL_THREADS:-4}
if [ ${par_thread} -ne ${par_thread} -o \
${par_total_threads} -ne ${par_total_threads} ]; then
echo "Parallel threads should be a number."
exit
fi
if [ ${par_thread} -ge ${par_total_threads} ]; then
echo "Current thread is greater than total threads. Doesn't make sense"
exit
fi
# travis platformio target is used for nightly Test
travis=$(list_envs | grep travis | sort)
# get all taregts, excluding travis and debug
available=$(list_envs | grep -Ev -- 'travis|debug|release' | sort)
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS}"
# get command line Parameters
# l prints environments
# 2 does parallel builds
# d uses next arg as destination folder
while getopts "lpd:" opt; do
case $opt in
l)
print_available
exit
;;
p)
par_build=true
;;
d)
destination=$OPTARG
;;
esac
done
shift $((OPTIND-1))
# Welcome message
echo "--------------------------------------------------------------"
echo "EMS-ESP FIRMWARE BUILDER"
echo "Building for version ${version}" ${git_revision:+($git_revision)}
# Environments to build
environments=$@
if [ $# -eq 0 ]; then
set_default_environments
fi
if ${CI:-false}; then
print_environments
fi
# for debugging
echo "* git_revision = $git_revision"
echo "* git_tag = $git_tag"
echo "* TRAVIS_COMMIT = $TRAVIS_COMMIT"
echo "* TRAVIS_TAG = $TRAVIS_TAG"
echo "* TRAVIS_BRANCH = $TRAVIS_BRANCH"
echo "* TRAVIS_BUILD_STAGE_NAME = $TRAVIS_BUILD_STAGE_NAME"
build_environments

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env python
from subprocess import call
import os
Import("env")
def clean(source, target, env):
print("\n** Starting clean...")
call(["pio", "run", "-t", "erase"])
call(["esptool.py", "-p COM6", "write_flash 0x00000", os.getcwd()+"../firmware/*.bin"])
print("\n** Finished clean.")
# built in targets: (buildprog, size, upload, program, buildfs, uploadfs, uploadfsota)
env.AddPreAction("buildprog", clean)

View File

@@ -1,307 +0,0 @@
#!/usr/bin/env python3
"""ESP Exception Decoder
github: https://github.com/janLo/EspArduinoExceptionDecoder
license: GPL v3
author: Jan Losinski
"""
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"
}
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_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, stack_only=False):
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, full=True, stack_only=False):
if 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="ESP8266")
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
default="~/.platformio/packages/toolchain-xtensa/")
parser.add_argument("-e", "--elf", help="path to elf file", required=True)
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.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.stack_only)
resolver.fill(parser)
print_result(parser, resolver, args.full, args.stack_only)

View File

@@ -1,307 +0,0 @@
#!/usr/bin/env python3
"""ESP Exception Decoder
github: https://github.com/janLo/EspArduinoExceptionDecoder
license: GPL v3
author: Jan Losinski
"""
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"
}
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_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, stack_only=False):
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, full=True, stack_only=False):
if 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="ESP8266")
parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain",
default="~/.platformio/packages/toolchain-xtensa/")
parser.add_argument("-e", "--elf", help="path to elf file", required=True)
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 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.stack_only)
resolver.fill(parser)
print_result(parser, resolver, args.full, args.stack_only)

View File

@@ -1,383 +0,0 @@
#!/usr/bin/env python3
# pylint: disable=C0301,C0114,C0116,W0511
# coding=utf-8
# -------------------------------------------------------------------------------
# based on ESPurna module memory analyser by xose.perez@gmail.com
#
# Rewritten for python-3 and changed to use "size" instead of "objdump"
# Based on https://github.com/esp8266/Arduino/pull/6525
# by Maxim Prokhorov <prokhorov.max@outlook.com>
#
# Based on:
# https://github.com/letscontrolit/ESPEasy/blob/mega/memanalyzer.py
# by psy0rz <edwin@datux.nl>
# https://raw.githubusercontent.com/SmingHub/Sming/develop/tools/memanalyzer.py
# by Slavey Karadzhov <slav@attachix.com>
# https://github.com/Sermus/ESP8266_memory_analyzer
# by Andrey Filimonov
#
# -------------------------------------------------------------------------------
#
# When using Windows with non-default installation at the C:\.platformio,
# you would need to specify toolchain path manually. For example:
#
# $ py -3 scripts\memanalyzer.py --toolchain-prefix C:\.platformio\packages\toolchain-xtensa\bin\xtensa-lx106-elf- <args>
#
# You could also change the path to platformio binary in a similar fashion:
# $ py -3 scripts\memanalyzer.py --platformio-prefix C:\Users\Max\platformio-penv\Scripts\
#
# -------------------------------------------------------------------------------
import argparse
import os
import re
import subprocess
import sys
from collections import OrderedDict
from subprocess import getstatusoutput
__version__ = (0, 3)
# -------------------------------------------------------------------------------
TOTAL_IRAM = 32786
TOTAL_DRAM = 81920
DEFAULT_ENV = "esp12e"
TOOLCHAIN_PREFIX = "~/.platformio/packages/toolchain-xtensa/bin/xtensa-lx106-elf-"
PLATFORMIO_PREFIX = ""
SECTIONS = OrderedDict(
[
(".data", "Initialized Data (RAM)"),
(".rodata", "ReadOnly Data (RAM)"),
(".bss", "Uninitialized Data (RAM)"),
(".text", "Cached Code (IRAM)"),
(".irom0.text", "Uncached Code (SPI)"),
]
)
DESCRIPTION = "Memory Analyzer v{}".format(
".".join(str(x) for x in __version__)
)
# -------------------------------------------------------------------------------
def size_binary_path(prefix):
return "{}size".format(os.path.expanduser(prefix))
def file_size(file):
try:
return os.stat(file).st_size
except OSError:
return 0
def analyse_memory(size, elf_file):
proc = subprocess.Popen(
[size, "-A", elf_file], stdout=subprocess.PIPE, universal_newlines=True
)
lines = proc.stdout.readlines()
values = {}
for line in lines:
words = line.split()
for name in SECTIONS.keys():
if line.startswith(name):
value = values.setdefault(name, 0)
value += int(words[1])
values[name] = value
break
return values
def run(prefix, env, modules, debug):
flags = " ".join("-D{}_SUPPORT={:d}".format(k, v) for k, v in modules.items())
os_env = os.environ.copy()
os_env["PLATFORMIO_SRC_BUILD_FLAGS"] = flags
os_env["PLATFORMIO_BUILD_CACHE_DIR"] = "test/pio_cache"
if debug:
print("Selected flags: {}".format(flags))
command = [os.path.join(prefix, "platformio"), "run"]
if not debug:
command.append("--silent")
command.extend(["--environment", env])
output = None if debug else subprocess.DEVNULL
try:
subprocess.check_call(
command, shell=False, env=os_env, stdout=output, stderr=output
)
except subprocess.CalledProcessError:
print(" - Command failed: {}".format(command))
print(" - Selected flags: {}".format(flags))
sys.exit(1)
def get_available_modules():
modules = []
for line in open("lib/framework/services.h"):
match = re.search(r"(\w*)_SUPPORT", line)
if match:
modules.append((match.group(1), 0))
modules.sort(key=lambda item: item[0])
return OrderedDict(modules)
def parse_commandline_args():
parser = argparse.ArgumentParser(
description=DESCRIPTION, formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument(
"-e", "--environment", help="platformio environment to use", default=DEFAULT_ENV
)
parser.add_argument(
"--toolchain-prefix",
help="where to find the xtensa toolchain binaries",
default=TOOLCHAIN_PREFIX,
)
parser.add_argument(
"--platformio-prefix",
help="where to find the platformio executable",
default=PLATFORMIO_PREFIX,
)
parser.add_argument(
"-c",
"--core",
help="use core as base configuration instead of default",
action="store_true",
default=False,
)
parser.add_argument(
"-l",
"--list",
help="list available modules",
action="store_true",
default=False,
)
parser.add_argument("-d", "--debug", action="store_true", default=False)
parser.add_argument(
"modules", nargs="*", help="Modules to test (use ALL to test them all)"
)
return parser.parse_args()
def size_binary_exists(args):
status, _ = getstatusoutput(size_binary_path(args.toolchain_prefix))
if status != 1:
print("size not found, please check that the --toolchain-prefix is correct")
sys.exit(1)
def get_modules(args):
# Load list of all modules
available_modules = get_available_modules()
if args.list:
print("List of available modules:\n")
for module in available_modules:
print("* " + module)
print()
sys.exit(0)
modules = []
if args.modules:
if "ALL" in args.modules:
modules.extend(available_modules.keys())
else:
modules.extend(args.modules)
modules.sort()
# Check test modules exist
for module in modules:
if module not in available_modules:
print("Module {} not found".format(module))
sys.exit(2)
# Either use all of modules or specified subset
if args.core:
modules = available_modules
else:
modules = OrderedDict((x, 0) for x in modules)
configuration = "CORE" if args.core else "DEFAULT"
return configuration, modules
# -------------------------------------------------------------------------------
class Analyser:
"""Run platformio and print info about the resulting binary."""
OUTPUT_FORMAT = "{:<20}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}|{:<15}"
DELIMETERS = OUTPUT_FORMAT.format(
"-" * 20, "-" * 15, "-" * 15, "-" * 15, "-" * 15, "-" * 15, "-" * 15, "-" * 15
)
FIRMWARE_FORMAT = ".pio/build/{env}/firmware.{suffix}"
class _Enable:
def __init__(self, analyser, module=None):
self.analyser = analyser
self.module = module
def __enter__(self):
if not self.module:
for name in self.analyser.modules:
self.analyser.modules[name] = 1
else:
self.analyser.modules[self.module] = 1
return self.analyser
def __exit__(self, *args, **kwargs):
if not self.module:
for name in self.analyser.modules:
self.analyser.modules[name] = 0
else:
self.analyser.modules[self.module] = 0
analyser = None
module = None
def __init__(self, args, modules):
self._debug = args.debug
self._platformio_prefix = args.platformio_prefix
self._toolchain_prefix = args.toolchain_prefix
self._environment = args.environment
self.modules = modules
self.baseline = None
def enable(self, module=None):
return self._Enable(self, module)
def print(self, *args):
print(self.OUTPUT_FORMAT.format(*args))
def print_delimiters(self):
print(self.DELIMETERS)
def begin(self, name):
self.print(
"Module",
"Cache IRAM",
"Init RAM",
"R.O. RAM",
"Uninit RAM",
"Available RAM",
"Flash ROM",
"Binary size",
)
self.print(
"",
".text + .text1",
".data",
".rodata",
".bss",
"heap + stack",
".irom0.text",
"",
)
self.print_delimiters()
self.baseline = self.run()
self.print_values(name, self.baseline)
def print_values(self, header, values):
self.print(
header,
values[".text"],
values[".data"],
values[".rodata"],
values[".bss"],
values["free"],
values[".irom0.text"],
values["size"],
)
def print_compare(self, header, values):
self.print(
header,
values[".text"] - self.baseline[".text"],
values[".data"] - self.baseline[".data"],
values[".rodata"] - self.baseline[".rodata"],
values[".bss"] - self.baseline[".bss"],
values["free"] - self.baseline["free"],
values[".irom0.text"] - self.baseline[".irom0.text"],
values["size"] - self.baseline["size"],
)
def run(self):
run(self._platformio_prefix, self._environment, self.modules, self._debug)
elf_path = self.FIRMWARE_FORMAT.format(env=self._environment, suffix="elf")
bin_path = self.FIRMWARE_FORMAT.format(env=self._environment, suffix="bin")
values = analyse_memory(
size_binary_path(self._toolchain_prefix), elf_path
)
free = 80 * 1024 - values[".data"] - values[".rodata"] - values[".bss"]
free = free + (16 - free % 16)
values["free"] = free
values["size"] = file_size(bin_path)
return values
def main(args):
# Check xtensa-lx106-elf-size is in the path
size_binary_exists(args)
# Which modules to test?
configuration, modules = get_modules(args)
# print_values init message
print('Selected environment "{}"'.format(args.environment), end="")
if modules:
print(" with modules: {}".format(" ".join(modules.keys())))
else:
print()
print()
print("Analyzing {} configuration".format(configuration))
print()
# Build the core without any modules to get base memory usage
analyser = Analyser(args, modules)
analyser.begin(configuration)
# Test each module separately
results = {}
for module in analyser.modules:
with analyser.enable(module):
results[module] = analyser.run()
analyser.print_compare(module, results[module])
# Test all modules
if analyser.modules:
with analyser.enable():
total = analyser.run()
analyser.print_delimiters()
if len(analyser.modules) > 1:
analyser.print_compare("ALL MODULES", total)
analyser.print_values("TOTAL", total)
if __name__ == "__main__":
main(parse_commandline_args())

View File

@@ -1202,7 +1202,7 @@ bool Boiler::set_pump_delay(const char * value, const int8_t id) {
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent // on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3) 1=hot, 2=eco, 3=intelligent
bool Boiler::set_warmwater_mode(const char * value, const int8_t id) { bool Boiler::set_warmwater_mode(const char * value, const int8_t id) {
uint8_t set; uint8_t set;
if (!Helpers::value2enum(value, set, {"hot", "eco", "intelligent"})) { if (!Helpers::value2enum(value, set, {F("hot"), F("eco"), F("intelligent")})) {
LOG_WARNING(F("Set boiler warm water mode: Invalid value")); LOG_WARNING(F("Set boiler warm water mode: Invalid value"));
return false; return false;
} }

View File

@@ -224,7 +224,7 @@ bool Mixer::export_values(JsonObject & json) {
// returns false if empty // returns false if empty
bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) { bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) {
// check if there is data for the mixer unit // check if there is data for the mixer unit
if (!Helpers::hasValue(status_)) { if (this->type() == Type::NONE) {
return 0; return 0;
} }
@@ -249,7 +249,7 @@ bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) {
json_hc["flowSetTemp"] = flowSetTemp_; json_hc["flowSetTemp"] = flowSetTemp_;
} }
if (Helpers::hasValue(pumpStatus_)) { if (Helpers::hasValue(pumpStatus_)) {
char s[5]; char s[7];
json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL); json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(status_)) { if (Helpers::hasValue(status_)) {
@@ -274,7 +274,7 @@ bool Mixer::export_values_format(uint8_t mqtt_format, JsonObject & json) {
json_hc["wwTemp"] = (float)flowTemp_ / 10; json_hc["wwTemp"] = (float)flowTemp_ / 10;
} }
if (Helpers::hasValue(pumpStatus_)) { if (Helpers::hasValue(pumpStatus_)) {
char s[5]; char s[7];
json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL); json_hc["pumpStatus"] = Helpers::render_value(s, pumpStatus_, EMS_VALUE_BOOL);
} }
if (Helpers::hasValue(status_)) { if (Helpers::hasValue(status_)) {
@@ -316,7 +316,7 @@ void Mixer::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
// check if circuit is active, 0-off, 1-unmixed, 2-mixed // check if circuit is active, 0-off, 1-unmixed, 2-mixed
uint8_t ismixed = 0; uint8_t ismixed = 0;
changed_ |= telegram->read_value(ismixed, 0); telegram->read_value(ismixed, 0);
if (ismixed == 0) { if (ismixed == 0) {
return; return;
} }

View File

@@ -419,7 +419,7 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
// Floordry // Floordry
if (Helpers::hasValue(floordrystatus_) && Helpers::hasValue(floordrytemp_) && (floordrytemp_ > 0)) { if (Helpers::hasValue(floordrystatus_) && Helpers::hasValue(floordrytemp_) && (floordrytemp_ > 0)) {
char s[10]; char s[10];
rootThermostat["floordry"] = Helpers::render_enum(s, {"off", "start", "heat", "hold", "cool", "end"}, floordrystatus_); rootThermostat["floordry"] = Helpers::render_enum(s, {F("off"), F("start"), F("heat"), F("hold"), F("cool"), F("end")}, floordrystatus_);
rootThermostat["floordrytemp"] = floordrytemp_; rootThermostat["floordrytemp"] = floordrytemp_;
} }
@@ -447,9 +447,9 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
if (Helpers::hasValue(ibaBuildingType_)) { if (Helpers::hasValue(ibaBuildingType_)) {
char s[10]; char s[10];
if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) {
rootThermostat["building"] = Helpers::render_enum(s, {"light", "medium", "heavy"}, ibaBuildingType_ - 1); rootThermostat["building"] = Helpers::render_enum(s, {F("light"), F("medium"), F("heavy")}, ibaBuildingType_ - 1);
} else { } else {
rootThermostat["building"] = Helpers::render_enum(s, {"light", "medium", "heavy"}, ibaBuildingType_); rootThermostat["building"] = Helpers::render_enum(s, {F("light"), F("medium"), F("heavy")}, ibaBuildingType_);
} }
} }
@@ -457,9 +457,9 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
if (Helpers::hasValue(wwMode_)) { if (Helpers::hasValue(wwMode_)) {
char s[10]; char s[10];
if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) { if (model == EMS_DEVICE_FLAG_RC300 || model == EMS_DEVICE_FLAG_RC100) {
rootThermostat["wwmode"] = Helpers::render_enum(s, {"off", "low", "high", "auto", "own_prog"}, wwMode_); rootThermostat["wwmode"] = Helpers::render_enum(s, {F("off"), F("low"), F("high"), F("auto"), F("own_prog")}, wwMode_);
} else { } else {
rootThermostat["wwmode"] = Helpers::render_enum(s, {"off", "on", "auto"}, wwMode_); rootThermostat["wwmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwMode_);
} }
} }
@@ -476,7 +476,7 @@ bool Thermostat::export_values_main(JsonObject & rootThermostat) {
// Warm Water circulation mode // Warm Water circulation mode
if (Helpers::hasValue(wwCircMode_)) { if (Helpers::hasValue(wwCircMode_)) {
char s[7]; char s[7];
rootThermostat["wwcircmode"] = Helpers::render_enum(s, {"off", "on", "auto"}, wwCircMode_); rootThermostat["wwcircmode"] = Helpers::render_enum(s, {F("off"), F("on"), F("auto")}, wwCircMode_);
} }
return (rootThermostat.size()); return (rootThermostat.size());
@@ -608,7 +608,7 @@ bool Thermostat::export_values_hc(uint8_t mqtt_format, JsonObject & rootThermost
// Summer mode // Summer mode
if (Helpers::hasValue(hc->summer_setmode)) { if (Helpers::hasValue(hc->summer_setmode)) {
char s[7]; char s[7];
dataThermostat["summermode"] = Helpers::render_enum(s, {"summer", "auto", "winter"}, hc->summer_setmode); dataThermostat["summermode"] = Helpers::render_enum(s, {F("summer"), F("auto"), F("winter")}, hc->summer_setmode);
} }
// mode - always force showing this when in HA so not to break HA's climate component // mode - always force showing this when in HA so not to break HA's climate component
@@ -1514,7 +1514,7 @@ bool Thermostat::set_remotetemp(const char * value, const int8_t id) {
// 0xA5 - Set the building settings // 0xA5 - Set the building settings
bool Thermostat::set_building(const char * value, const int8_t id) { bool Thermostat::set_building(const char * value, const int8_t id) {
uint8_t bd = 0; uint8_t bd = 0;
if (!Helpers::value2enum(value, bd, {"light", "medium", "heavy"})) { if (!Helpers::value2enum(value, bd, {F("light"), F("medium"), F("heavy")})) {
LOG_WARNING(F("Set building: Invalid value")); LOG_WARNING(F("Set building: Invalid value"));
return false; return false;
} }
@@ -1533,7 +1533,7 @@ bool Thermostat::set_building(const char * value, const int8_t id) {
// 0xA5 Set the language settings // 0xA5 Set the language settings
bool Thermostat::set_language(const char * value, const int8_t id) { bool Thermostat::set_language(const char * value, const int8_t id) {
uint8_t lg = 0; uint8_t lg = 0;
if (!Helpers::value2enum(value, lg, {"german", "dutch", "french", "italian"})) { if (!Helpers::value2enum(value, lg, {F("german"), F("dutch"), F("french"), F("italian")})) {
LOG_WARNING(F("Set language: Invalid value")); LOG_WARNING(F("Set language: Invalid value"));
return false; return false;
} }
@@ -1586,14 +1586,14 @@ bool Thermostat::set_roominfluence(const char * value, const int8_t id) {
bool Thermostat::set_wwmode(const char * value, const int8_t id) { bool Thermostat::set_wwmode(const char * value, const int8_t id) {
uint8_t set = 0xFF; uint8_t set = 0xFF;
if ((this->model() == EMS_DEVICE_FLAG_RC300) || (this->model() == EMS_DEVICE_FLAG_RC100)) { if ((this->model() == EMS_DEVICE_FLAG_RC300) || (this->model() == EMS_DEVICE_FLAG_RC100)) {
if (!Helpers::value2enum(value, set, {"off", "low", "high", "auto", "own"})) { if (!Helpers::value2enum(value, set, {F("off"), F("low"), F("high"), F("auto"), F("own")})) {
LOG_WARNING(F("Set warm water mode: Invalid mode")); LOG_WARNING(F("Set warm water mode: Invalid mode"));
return false; return false;
} }
LOG_INFO(F("Setting warm water mode to %s"), value); LOG_INFO(F("Setting warm water mode to %s"), value);
write_command(0x02F5, 2, set, 0x02F5); write_command(0x02F5, 2, set, 0x02F5);
} else { } else {
if (!Helpers::value2enum(value, set, {"off", "on", "auto"})) { if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) {
LOG_WARNING(F("Set warm water mode: Invalid mode")); LOG_WARNING(F("Set warm water mode: Invalid mode"));
return false; return false;
} }
@@ -1631,7 +1631,7 @@ bool Thermostat::set_wwtemplow(const char * value, const int8_t id) {
// sets the thermostat ww circulation working mode, where mode is a string // sets the thermostat ww circulation working mode, where mode is a string
bool Thermostat::set_wwcircmode(const char * value, const int8_t id) { bool Thermostat::set_wwcircmode(const char * value, const int8_t id) {
uint8_t set = 0xFF; uint8_t set = 0xFF;
if (!Helpers::value2enum(value, set, {"off", "on", "auto"})) { if (!Helpers::value2enum(value, set, {F("off"), F("on"), F("auto")})) {
LOG_WARNING(F("Set warm water circulation mode: Invalid mode")); LOG_WARNING(F("Set warm water circulation mode: Invalid mode"));
return false; return false;
} }
@@ -1909,7 +1909,7 @@ bool Thermostat::set_summermode(const char * value, const int8_t id) {
return false; return false;
} }
uint8_t set = 0xFF; uint8_t set = 0xFF;
if (!Helpers::value2enum(value, set, {"summer", "auto", "winter"})) { if (!Helpers::value2enum(value, set, {F("summer"), F("auto"), F("winter")})) {
LOG_WARNING(F("Setting summer mode: Invalid mode")); LOG_WARNING(F("Setting summer mode: Invalid mode"));
return false; return false;
} }

View File

@@ -133,7 +133,7 @@ class Thermostat : public EMSdevice {
std::vector<uint16_t> summer_typeids; std::vector<uint16_t> summer_typeids;
std::string datetime_; // date and time stamp std::string datetime_; // date and time stamp
std::string errorCode_; // code as string i.e. "A22(816)" std::string errorCode_; // code from 0xA2 as string i.e. "A22(816)"
bool changed_ = false; bool changed_ = false;
bool ha_registered_ = false; bool ha_registered_ = false;

View File

@@ -917,7 +917,7 @@ void EMSESP::start() {
emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem emsdevices.reserve(5); // reserve space for initially 5 devices to avoid mem
LOG_INFO("EMS Device library loaded with %d records", device_library_.size()); LOG_INFO(F("EMS Device library loaded with %d records"), device_library_.size());
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
mqtt_.on_connect(); // simulate an MQTT connection mqtt_.on_connect(); // simulate an MQTT connection

View File

@@ -136,21 +136,18 @@ char * Helpers::render_boolean(char * result, bool value) {
} }
// depending on format render a number or a string // depending on format render a number or a string
char * Helpers::render_enum(char * result, const std::vector<std::string> & value, const uint8_t no) { char * Helpers::render_enum(char * result, const std::vector<const __FlashStringHelper *> value, const uint8_t no) {
if (no >= value.size()) { if (no >= value.size()) {
return nullptr; // out of bounds return nullptr; // out of bounds
} }
if (bool_format() == BOOL_FORMAT_ONOFF) { strcpy(result, uuid::read_flash_string(value[no]).c_str());
strcpy(result, value[no].c_str()); if (bool_format() == BOOL_FORMAT_TRUEFALSE) {
} else if (bool_format() == BOOL_FORMAT_TRUEFALSE) { if (no == 0 && uuid::read_flash_string(value[0]) == "off") {
if (no == 0 && value[0] == "off") {
strlcpy(result, "false", 7); strlcpy(result, "false", 7);
} else if (no == 1 && value[1] == "on") { } else if (no == 1 && uuid::read_flash_string(value[1]) == "on") {
strlcpy(result, "true", 6); strlcpy(result, "true", 6);
} else {
strcpy(result, value[no].c_str());
} }
} else { } else if (bool_format() == BOOL_FORMAT_NUMBERS) {
itoa(result, no); itoa(result, no);
} }
return result; return result;
@@ -462,13 +459,14 @@ bool Helpers::value2bool(const char * v, bool & value) {
} }
// checks to see if a string is member of a vector and return the index, also allow true/false for on/off // checks to see if a string is member of a vector and return the index, also allow true/false for on/off
bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector<std::string> & strs) { bool Helpers::value2enum(const char * v, uint8_t & value, const std::vector<const __FlashStringHelper *> strs) {
if ((v == nullptr) || (strlen(v) == 0)) { if ((v == nullptr) || (strlen(v) == 0)) {
return false; return false;
} }
std::string str = toLower(v); std::string str = toLower(v);
for (value = 0; value < strs.size(); value++) { for (value = 0; value < strs.size(); value++) {
if ((strs[value] == "off" && str == "false") || (strs[value] == "on" && str == "true") || (str == strs[value]) || (v[0] == '0' + value)) { std::string str1 = uuid::read_flash_string(strs[value]);
if ((str1 == "off" && str == "false") || (str1 == "on" && str == "true") || (str == str1) || (v[0] == '0' + value)) {
return true; return true;
} }
} }

View File

@@ -26,6 +26,7 @@
#define BOOL_FORMAT_ONOFF 1 #define BOOL_FORMAT_ONOFF 1
#define BOOL_FORMAT_TRUEFALSE 2 #define BOOL_FORMAT_TRUEFALSE 2
#define BOOL_FORMAT_NUMBERS 3
namespace emsesp { namespace emsesp {
@@ -39,7 +40,7 @@ class Helpers {
static char * render_value(char * result, const int16_t value, const uint8_t format); static char * render_value(char * result, const int16_t value, const uint8_t format);
static char * render_value(char * result, const char * value, uint8_t format); static char * render_value(char * result, const char * value, uint8_t format);
static char * render_boolean(char * result, bool value); static char * render_boolean(char * result, bool value);
static char * render_enum(char * result, const std::vector<std::string> & value, const uint8_t no); static char * render_enum(char * result, const std::vector<const __FlashStringHelper *> value, const uint8_t no);
static char * hextoa(char * result, const uint8_t value); static char * hextoa(char * result, const uint8_t value);
static std::string data_to_hex(const uint8_t * data, const uint8_t length); static std::string data_to_hex(const uint8_t * data, const uint8_t length);
@@ -62,7 +63,7 @@ class Helpers {
static bool value2float(const char * v, float & value); static bool value2float(const char * v, float & value);
static bool value2bool(const char * v, bool & value); static bool value2bool(const char * v, bool & value);
static bool value2string(const char * v, std::string & value); static bool value2string(const char * v, std::string & value);
static bool value2enum(const char * v, uint8_t & value, const std::vector<std::string> & strs); static bool value2enum(const char * v, uint8_t & value, const std::vector<const __FlashStringHelper *> strs);
static void bool_format(uint8_t bool_format) { static void bool_format(uint8_t bool_format) {
bool_format_ = bool_format; bool_format_ = bool_format;

View File

@@ -486,7 +486,7 @@ void Mqtt::on_connect() {
// homeassistant/sensor/ems-esp/status/config // homeassistant/sensor/ems-esp/status/config
// all the values from the heartbeat payload will be added as attributes to the entity state // all the values from the heartbeat payload will be added as attributes to the entity state
void Mqtt::ha_status() { void Mqtt::ha_status() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = F("EMS-ESP status"); doc["name"] = F("EMS-ESP status");
doc["uniq_id"] = F("status"); doc["uniq_id"] = F("status");
@@ -697,7 +697,9 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
return; return;
} }
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc; return; // TODO
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = name; doc["name"] = name;
doc["uniq_id"] = entity; doc["uniq_id"] = entity;
@@ -729,9 +731,9 @@ void Mqtt::register_mqtt_ha_binary_sensor(const __FlashStringHelper * name, cons
snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity); snprintf_P(topic, sizeof(topic), PSTR("homeassistant/binary_sensor/ems-esp/%s/config"), entity);
// convert json to string and publish immediately with retain forced to true // convert json to string and publish immediately with retain forced to true
std::string payload_text; char payload_text[300];
serializeJson(doc, payload_text); // convert json to string serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text.c_str(), payload_text.size()); uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text);
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing topic %s"), topic); LOG_DEBUG(F("Publishing topic %s"), topic);
#else #else
@@ -802,7 +804,7 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
} }
new_name[0] = toupper(new_name[0]); // capitalize first letter new_name[0] = toupper(new_name[0]); // capitalize first letter
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM); StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["name"] = new_name; doc["name"] = new_name;
doc["uniq_id"] = uniq; doc["uniq_id"] = uniq;
if (uom != nullptr) { if (uom != nullptr) {
@@ -818,21 +820,22 @@ void Mqtt::register_mqtt_ha_sensor(const char * prefix,
ids.add(ha_device); ids.add(ha_device);
// convert json to string and publish immediately with retain forced to true // convert json to string and publish immediately with retain forced to true
doc.shrinkToFit(); // std::string payload_text;
std::string payload_text; char payload_text[300];
serializeJson(doc, payload_text); // convert json to string serializeJson(doc, payload_text); // convert json to string
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text.c_str(), payload_text.size());
uint16_t packet_id = mqttClient_->publish(topic, 0, true, payload_text);
if (!packet_id) { if (!packet_id) {
LOG_ERROR(F("Failed to publish topic %s"), topic); LOG_ERROR(F("Failed to publish topic %s"), topic);
} else { } else {
#if defined(EMSESP_STANDALONE) #if defined(EMSESP_STANDALONE)
LOG_DEBUG(F("Publishing topic=%s, payload=%s"), topic, payload_text.c_str()); LOG_DEBUG(F("Publishing topic=%s, payload=%s"), topic, payload_text);
#else #else
LOG_DEBUG(F("Publishing topic %s"), topic); LOG_DEBUG(F("Publishing topic %s"), topic);
#endif #endif
} }
delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp // delay(MQTT_PUBLISH_WAIT); // don't flood asynctcp
} }
} // namespace emsesp } // namespace emsesp

View File

@@ -61,7 +61,7 @@ bool System::command_send(const char * value, const int8_t id) {
// restart EMS-ESP // restart EMS-ESP
void System::restart() { void System::restart() {
LOG_NOTICE("Restarting system..."); LOG_NOTICE(F("Restarting system..."));
Shell::loop_all(); Shell::loop_all();
delay(1000); // wait a second delay(1000); // wait a second
#if defined(ESP8266) #if defined(ESP8266)
@@ -73,7 +73,7 @@ void System::restart() {
// saves all settings // saves all settings
void System::wifi_reconnect() { void System::wifi_reconnect() {
LOG_NOTICE("The wifi will reconnect..."); LOG_NOTICE(F("The wifi will reconnect..."));
Shell::loop_all(); Shell::loop_all();
delay(1000); // wait a second delay(1000); // wait a second
EMSESP::webSettingsService.save(); // local settings EMSESP::webSettingsService.save(); // local settings

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.1.1b0" #define EMSESP_APP_VERSION "2.1.1b1"