diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..f74d4a187 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with UTF-8 encoding for every file +[*] +end_of_line = lf +charset = utf-8 + +# For all source code +[*.{c,cpp,h,ino,sh}] +insert_final_newline = true +indent_style = tab +indent_size = 2 + +# Tab indentation also for makefiles +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes index 463573798..415abee25 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ # Set default behaviour, in case users don't have core.autocrlf set. -* text=auto +* text eol=lf # Explicitly declare text files we want to always be normalized and converted # to native line endings on checkout. diff --git a/.gitignore b/.gitignore index 980b4fce7..c99392e32 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ stino.settings MyPrivateConfig.h Documentation/html doxygen_sqlite3.db +Makefile.inc +build +bin +*.sublime-workspace diff --git a/.mystools/.bundle_runtime.sh b/.mystools/.bundle_runtime.sh new file mode 100755 index 000000000..ebf09b7be --- /dev/null +++ b/.mystools/.bundle_runtime.sh @@ -0,0 +1,96 @@ +#!/bin/bash +### +# +# Tool runtime shim that provides a simple API for running +# tool bundles and convenience functions for other tool developers. +# +## +log() { IFS=" "; >&2 printf "%s\n" "$*"; } +info() { IFS=" "; >&2 printf "\e[92m%s\e[m\n" "$*"; } +warn() { IFS=" "; >&2 printf "\e[93m%s\e[m\n" "$*"; } +err() { IFS=" "; >&2 printf "\e[91m%s\e[m\n" "$*"; exit 1; } + +is_supported_os() +{ + [[ ${1} == darwin* ]] || [[ ${1} == linux-gnu* ]] || [[ ${1} == freebsd ]] || [[ ${1} == msys ]] || [[ ${1} == cygwin ]] +} + +is_installed() +{ + which "${1}" > /dev/null 2>&1 +} + +tools_dir() +{ + git_config_tools_dir='git config mysensors.toolsdir' + + # Set mysensors.toolsdir in git config if it hasn't been set + if [[ ! $($git_config_tools_dir) || ! -d "$($git_config_tools_dir)" ]]; then + $git_config_tools_dir "$( cd "$(dirname "${BASH_SOURCE[0]}")"; git rev-parse --show-prefix )" + git config alias.mystoolspath '![ -z "${GIT_PREFIX}" ] || cd ${GIT_PREFIX}; echo $(git rev-parse --show-cdup)$(git config mysensors.toolsdir)' + fi + + $git_config_tools_dir +} + +configure_git_tool_aliases() +{ + find "$(git mystoolspath)" -name run.sh -print0 | while IFS= read -r -d '' bundle; do + local tool="$(basename "$(dirname "${bundle}")")" + git config alias.${tool} '!f() { $(git mystoolspath)'${tool}'/run.sh $@; }; f' + done +} + +bootstrap_cksum() +{ + echo $(git ls-files -s -- $(git mystoolspath)bootstrap-dev.sh | cut -d' ' -f2) +} + +bootstrap_version() +{ + git_config_bootstrap_version='git config mysensors.bootstrap-cksum' + if [[ $1 == --set ]]; then + $git_config_bootstrap_version $(bootstrap_cksum) + else + echo $($git_config_bootstrap_version) + fi +} + +environment_outdated() +{ + [[ $(bootstrap_version) != $(bootstrap_cksum) ]] +} + +modifiedSourceFiles() +{ + against=$(git rev-parse --verify HEAD >/dev/null 2>&1 && echo HEAD || echo 4b825dc642cb6eb9a060e54bf8d69288fbee4904) + git diff $1 --diff-filter=AM --name-only $against | grep -E '.*\.(c|cpp|h|hpp|ino)$' +} + +stagedSourceFiles() +{ + modifiedSourceFiles '--cached' +} + +runBundle() +{ + local bundle="$(dirname "${0}")" + local tool=$(basename "${bundle}") + + local CMD_OPTIONS=$( TOOLCONFIG="${bundle}/config" "${bundle}/options.sh" ) + + $tool $CMD_OPTIONS "$@" +} + +### +# Common environment variables +# +$(git rev-parse --is-inside-work-tree --quiet >/dev/null 2>&1) || err "Working directory is not a git repository. aborting..." + +if [[ $(basename "${0}") != *bootstrap* ]] && environment_outdated ; then + err "Your environment is out of date... Re-run $(git mystoolspath)bootstrap-dev.sh and try again" +fi + +GITREPO=$(git rev-parse --show-toplevel) +GITDIR=$(git rev-parse --git-dir) +TOOLSDIR="$(tools_dir)" diff --git a/.mystools/README.md b/.mystools/README.md new file mode 100644 index 000000000..19a674251 --- /dev/null +++ b/.mystools/README.md @@ -0,0 +1,260 @@ +# Development Tools + +### Overview + +This directory hosts MySensors development tools. The following +conventions are employed to facilitate consistent re-use/invocation +across modalitiies (e.g. local development, continuous integration, +editor linters, etc.) + +1. All common tools are hosted and managed in + the tools directory (used for both local development + and continuous integration) +2. Each tool comprises a directory, akin to a bundle, + that encapsulates declarative command-line options, + configuration files and a run script +3. A single bootstrap script configures a + development environment +4. A lightweight runtime provides a common set of + convenience functions and variables to all scripts +5. Support for all MySensors development environments + +### Usage + +The [boostrap-dev](bootstrap-dev.sh) script completely configures a +development environment. The boostrap-dev script validates development +prerequisites such as verifying the git repo ```origin``` & +```upstream``` remotes, tools needed for the git client hooks, +installing local commit hooks, and creating git aliases, etc. + +```shell-script +$ cd MySensors # git repo +$ .mystools/bootstrap-dev.sh +Checking operating system support: darwin16... +Checking github 'origin' & 'upstream' remotes... +Checking tool/utility prerequisites... +Installing git client-side hooks... +Configuring git aliases for running mysensor tool bundles... +Successfully configured your repo for MySensors development... Thanks for your support! +$ +``` +**Note:** If the bootstrap can not find required command-line +utilities, you will be requested to install the required tools and +re-run bootstrap.sh. See [Installation instructions for prerequisite +tools](#installtools). + +Once the bootstrapping process is complete, a git alias for each tool +is available to conveniently run the tool with the pre-configured +mysensors options and settings (no need to remember all those +command-line options or where the configuration files reside). +Furthermore, you can run the command against staged files, unstaged +modified files or a list of files common to most git commands. + +```shell-script +$ # Run cppcheck on an individual file +$ git cppcheck core/MyMessage.cpp +$ +$ # Run cppcheck on all changed files +$ git cppcheck +$ +$ # Run astyle on staged files +$ git astyle --cached +``` + +Available tool aliases: + +* git cppcheck [--cached] [files] +* git astyle [--cached] [files] + +Finally, to maintain code quality and a legible git revision history, +two git hooks are installed that will trigger whenever you commit +changes to your local repo. The hooks will examine your changes to +ensure they satisfy the [MySensors Code Contribution +Guidelines](https://www.mysensors.org/download/contributing) before +you push your changes to GitHub for merging by the core MySensors +team. Gitler will also enforce the coding guidelines so the hooks are +provided to reduce development cycle times by detecting standards +variances locally before pushing to GitHub. + +### Installation instructions for prerequisite tools + +This first time you run the bootstrap script, it may inform you that +certain development tools are missing from your path or system: + +``` +Checking operating system support: darwin16... +Checking github 'origin' & 'upstream' remotes... +Checking tool/utility prerequisites... +astyle not installed or not in current path. +cppcheck not installed or not in current path. +One or more required tools not found. Install required tools and re-run .mystools/bootstrap-dev.sh +``` + +To finish the bootstrap process, you will need to install the required +tools for your specific operating system as follows. Currently we use +Astyle 2.0.5 or later and Cppcheck 1.76 or later. Once you have +installed AStyle and Cppcheck, re-run bootstrap-dev.sh to finish +configuring your development environment. + +##### macOS + +```brew install astyle cppcheck``` + +#### Linux Xenial or later + +```apt-get install astyle cppcheck``` + +#### Linux Trusty or earlier + +##### Note: The apt versions are too old on Trusty so follow the [Linux - Build and Install from Source](#buildFromSource) instructions + +##### Windows - GitHub Git Shell + +###### *IMPORTANT: Be sure to launch PowerShell As Administrator* + +``` +### Install AStyle + +# Download +iwr 'https://sourceforge.net/projects/astyle/files/astyle/astyle%202.05.1/AStyle_2.05.1_windows.zip/download' -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox -OutFile astyle.2.05.zip + +# Unzip the filed & move the C:\Program Files +expand-archive astyle.2.05.zip +mv .\astyle.2.05 'C:\Program Files\AStyle\' + +# Add AStyle to your path +[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\AStyle\bin", [EnvironmentVariableTarget]::Machine) + +### Install Cppcheck (either 64-bit or 32-bit depending upon your version of Windows - pick one below) + +# 64-bit +iwr 'http://github.com/danmar/cppcheck/releases/download/1.76.1/cppcheck-1.76.1-x64-Setup.msi' -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox -OutFile cppcheck-1.76.1-Setup.msi + +# 32-bit +iwr 'http://github.com/danmar/cppcheck/releases/download/1.76.1/cppcheck-1.76.1-x86-Setup.msi' -UserAgent [Microsoft.PowerShell.Commands.PSUserAgent]::FireFox -OutFile cppcheck-1.76.1-Setup.msi + +# Launch installer to install Cppcheck +& .\cppcheck-1.76.1-Setup.msi + +### Add Cppcheck to your path +[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\Program Files\Cppcheck", [EnvironmentVariableTarget]::Machine) + +### At this point you need to reboot for the path changes to take effect +``` + +##### Windows - bash + +###### NOTE: At the time of this writing, the apt vresions of cppcheck and astyle are too old. Follow the [Linux - Build and Install from Source](#buildFromSource) instructions + +##### Windows - Cygwin +``` +Run Either Cygwin Setup-x86-64.exe or Setup-x86.exe depending upon your OS. Select and install astyle and cppcheck +``` + +##### Linux - Build and Install from Source + +``` +### Install AStyle + +# Download +curl -L 'https://sourceforge.net/projects/astyle/files/astyle/astyle%202.05.1/astyle_2.05.1_linux.tar.gz/download' | tar xvz + +# Compile and install +cd astyle/build/gcc && sudo make shared release shared static install + +### Install Cppcheck + +# Download +curl -L 'https://sourceforge.net/projects/cppcheck/files/cppcheck/1.76.1/cppcheck-1.76.1.tar.gz/download' | tar xvz + +# Compile and install +cd cppcheck-1.76.1 +sudo apt-get install libpcre++-dev +sudo make SRCDIR=build CFGDIR=/usr/share/cppcheck HAVE_RULES=yes CXXFLAGS="-O2 -DNDEBUG -Wall -Wno-sign-compare -Wno-unused-function" install + +``` + +### Implementation Details + +*This section is intended for developers curious about the +implementation details or interested in extending the development +environment to support additional tools, aliases, etc.* + +A lightweight [runtime](.bundle_runtime.sh) (less than 70 lines of +code) provides a simple API to consistently run the MySensors toolset +across modalities including, but not limited to, local development and +continuous integration. The core concept is that each tool +encapsulates all runtime configuration settings in a simple, +stand-alone bundle, that can be executed by the bundle runtime. The +runtime provides an API to execute a tool bundle along with +convenience functions and environment variables common to all tools. + +Each tool bundle is laid out as follows: + +``` +${TOOLSDIR} [e.g. ${GITREPO}/.mystools}] +.../tool [e.g. cppcheck, astyle] +....../run.sh [tool run script] +....../options.sh [command-line options] +....../config [supporting config files] +........./some.cfg +``` + +All bundle runtime dependencies are defined withing the namespace / +context of a git repo so users are free to rename or move repos or +even use multiple copies of the same repo without encountering +intra-repo tool settings/configuration conflicts. + +During the bootstrap process, certain keys and aliases are defined +that the runtime leverages. + +#####git config keys + +* mysensors.toolsdir = .mystools (defined as a repo-relative path - location agnostic) +* mysensors.bootstrap-cksum = \ + +#####git aliases + +* mystoolspath = *returns the absolute path to the tools dir* +* \ = *(e.g. cppcheck) runs the tool bundle* + + NOTE: The \ aliases are auto-generated by enumerating the bundles located +in *mystoolspath*. + +While the ```git args``` API is designed to cover many the +common use cases, tool and hook authors may need to source the +[runtime](.bundle_runtime.sh) into their script context in order to +use the convenience functions and environment variables as follows: + +```shell-script +. "$(git config mystoolspath).bundle_runtime.sh" +``` + +The runtime shim will instantiate the following environment variables +and convenience functions in the script context for use within a tool +or script. + +``` +TOOLSDIR - absolute path to repo tools dir. Maintained in + a location-agnostic fashion (e.g. if the user + moves the repo, changes tools directory within + repo, etc. +GITREPO - absolute path to the git repo +GITDIR - absolute path to the repo .git directory + +runBundle() - Run a given tool / bundle +is_installed() - true if a given utility is installed +supported_os() - true if running on a supported os +log() - log a message in default color to console +warn() - log a message in yellow to console +err() - log a message in red and exit with non-zero status +``` + +Many of the aforementioned details can be safely ignored if you want +to add a new development tool to the toolset. Simply create a bundle +that follows the layout above, declare the tool command-line options +in the [bundle]/options.sh and any configuration files in +[bundle]/config/. While it should not be necessary to alter the +bundle execution behavior beyond declaring command-line options and +configuration files, you can override it by modifing the [bundle] +run.sh script. diff --git a/.mystools/astyle/config/style.cfg b/.mystools/astyle/config/style.cfg new file mode 100644 index 000000000..363270ef4 --- /dev/null +++ b/.mystools/astyle/config/style.cfg @@ -0,0 +1,23 @@ +# Coding standard for MySensors core library (see https://www.mysensors.org/download/contributing) + +# Curly braces should follow "One True Brace Style" format +style=1tbs + +# All control statements ('if', 'for', 'while' etc.) should include curly braces {}. Even one-liners. +add-brackets + +# Tab-character for white space/indentation +indent=tab + +# All lines should be kept at 100 character width or less +max-code-length=100 +break-after-logical + +# Preprocessor blocks should be indented +#indent-preproc-block + +# Preprocessor definitions should be indented if linebroken +indent-preproc-define + +# C++ comments should be indented to source level, and not be kept in first column +indent-col1-comments diff --git a/.mystools/astyle/options.sh b/.mystools/astyle/options.sh new file mode 100755 index 000000000..64f922d51 --- /dev/null +++ b/.mystools/astyle/options.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +OPTIONS="--options="${TOOLCONFIG}"/style.cfg -n" + +echo $OPTIONS diff --git a/.mystools/astyle/run.sh b/.mystools/astyle/run.sh new file mode 100755 index 000000000..81d18f68e --- /dev/null +++ b/.mystools/astyle/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Instantiate the bundle runtime shim +. "$(git mystoolspath).bundle_runtime.sh" + +# Astyle takes one or more files as an argument. +# --cached or for changed +if [[ $# = 0 ]]; then + for file in $(modifiedSourceFiles); do + runBundle $file + done +elif [[ $1 = '--cached' ]]; then + for file in $(stagedSourceFiles); do + runBundle $file + done +else + eval "set -- $(git rev-parse --sq --prefix "$GIT_PREFIX" "$@")" + runBundle "$@" +fi diff --git a/.mystools/bootstrap-dev.sh b/.mystools/bootstrap-dev.sh new file mode 100755 index 000000000..33c689dd7 --- /dev/null +++ b/.mystools/bootstrap-dev.sh @@ -0,0 +1,90 @@ +#!/bin/bash +### +# OVERVIEW: +# This script bootstraps the MySensors development environment. +# +# USAGE: +# git clone https://github.com/mysensors/MySensors.git +# cd MySensors +# .mystools/bootstrap-dev.sh +## + +## +# Import common utility functions and environment variables +. "$(cd "$(dirname "${BASH_SOURCE[0]}")"; pwd)/.bundle_runtime.sh" + +check_tool_prerequisite() +{ + function ver { printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' ' '); } + + if is_installed ${1} ; then + #local version=$(${1} --version 2>&1 | sed -e 's/[[:alpha:]|(|[:space:]]//g') + local version=$(${1} --version 2>&1 | sed -ne 's/[^0-9]*\(\([0-9]\.\)\{0,4\}[0-9][^.]\).*/\1/p') + if [ $(ver ${version}) -lt $(ver ${2}) ]; then + warn "Found ${1} ${version} however, version ${2} or greater is required..." + return 1 + fi + else + warn "${1} not installed or not in current path." + return 1 + fi +} + +check_git_remote() +{ + local url=$( git config --get remote.${1}.url ) + [[ $url == ${2} ]] +} + +install_hooks() +{ + for hook in "${1}"/*.sh; do + local hookname=$(basename $hook) + $(cd "${GITDIR}/hooks"; ln -s -f "../../${TOOLSDIR}hooks/${hookname}" "${hookname%.sh}") + done +} + +### +# Main entry +# +# 1. Check that we are bootstrapping a supported OS/environment +# 2. Validate github remotes include valid upstream and origin +# 3. Check for client commit hook prerequisites +# 4. Install client commit hook prerequisites +# 5. Define aliases for conveniently running tool bundles +## + +mysrepo="https://github.com/mysensors/MySensors.git" + +#1 +log "Checking operating system support: ${OSTYPE}..." +is_supported_os ${OSTYPE} || { + err "OS ${OSTYPE} is unknown/unsupported, won't install anything" +} + +#2 +log "Checking github 'origin' & 'upstream' remotes..." +check_git_remote "origin" "${mysrepo}" && warn "Git \"origin\" should point to your github fork, not the github MySensors repo ${mysrep}" + +check_git_remote "upstream" "${mysrepo}" || { + warn "Git \"upstream\" remote not found or incorrectly defined. Configuring remote upstream --> ${mysrepo}..." + git remote add upstream "${mysrepo}" || err "git remote add ${mysrep} failed due to error $?" +} + +#3 +log "Checking tool/utility prerequisites..." +check_tool_prerequisite "astyle" "2.0.5" || err "Install AStyle 2.0.5 or greater and re-run ${0}" +check_tool_prerequisite "cppcheck" "1.76" || err "Install Cppcheck 1.76 or greater and re-run ${0}" +check_tool_prerequisite "git" "2.0" || err "Install git 2.0 or greater and re-run ${0}" + +#4 +log "Installing client-side git hooks..." +install_hooks "$(git mystoolspath)hooks" || err "Failed to install git hooks due to error $?..." + +#5 +log "Configuring git aliases for running mysensor tools..." +configure_git_tool_aliases || err "Failed to create git aliases due to error $?..." + +bootstrap_version "--set" + +info "Successfully configured your repo for MySensors development... Thanks for your support!" diff --git a/.mystools/cppcheck/config/avr.xml b/.mystools/cppcheck/config/avr.xml new file mode 100644 index 000000000..f2ba74526 --- /dev/null +++ b/.mystools/cppcheck/config/avr.xml @@ -0,0 +1,17 @@ + + + 8 + signed + + 2 + 2 + 4 + 8 + 4 + 4 + 8 + 2 + 2 + 2 + + \ No newline at end of file diff --git a/.mystools/cppcheck/config/includes.cfg b/.mystools/cppcheck/config/includes.cfg new file mode 100644 index 000000000..a57a7f30a --- /dev/null +++ b/.mystools/cppcheck/config/includes.cfg @@ -0,0 +1,4 @@ +. +drivers/Linux +drivers/AES +drivers/RPi diff --git a/.mystools/cppcheck/config/suppressions.cfg b/.mystools/cppcheck/config/suppressions.cfg new file mode 100644 index 000000000..57465da1e --- /dev/null +++ b/.mystools/cppcheck/config/suppressions.cfg @@ -0,0 +1,5 @@ +toomanyconfigs +missingInclude +missingIncludeSystem +ConfigurationNotChecked +unmatchedSuppression diff --git a/.mystools/cppcheck/config/unix32.xml b/.mystools/cppcheck/config/unix32.xml new file mode 100644 index 000000000..bc4ca0e89 --- /dev/null +++ b/.mystools/cppcheck/config/unix32.xml @@ -0,0 +1,17 @@ + + + 8 + unsigned + + 2 + 4 + 4 + 8 + 4 + 8 + 12 + 4 + 4 + 2 + + diff --git a/.mystools/cppcheck/options.sh b/.mystools/cppcheck/options.sh new file mode 100755 index 000000000..e6b40235c --- /dev/null +++ b/.mystools/cppcheck/options.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +OPTIONS="--quiet \ + --error-exitcode=1 \ + --force \ + --enable=style,information \ + --library=${LIBRARY:-avr} \ + --platform="${TOOLCONFIG}"/${PLATFORM:-avr.xml} \ + --includes-file="${TOOLCONFIG}"/includes.cfg \ + --suppressions-list="${TOOLCONFIG}"/suppressions.cfg" + +echo $OPTIONS diff --git a/.mystools/cppcheck/run.sh b/.mystools/cppcheck/run.sh new file mode 100755 index 000000000..f674d4586 --- /dev/null +++ b/.mystools/cppcheck/run.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Instantiate the tool runtime shim +. "$(git mystoolspath).bundle_runtime.sh" + +# cppcheck takes one or more files as an argument. +# --cached or for changed +if [[ $# = 0 ]]; then + for file in $(modifiedSourceFiles); do + runBundle $file + done +elif [[ $1 = '--cached' ]]; then + for file in $(stagedSourceFiles); do + runBundle $file + done +else + eval "set -- $(git rev-parse --sq --prefix "$GIT_PREFIX" "$@")" + runBundle "$@" +fi diff --git a/.mystools/hooks/pre-commit.sh b/.mystools/hooks/pre-commit.sh new file mode 100755 index 000000000..146d85f2d --- /dev/null +++ b/.mystools/hooks/pre-commit.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# instantiate mysensors tool runtime shim +. "$(git mystoolspath).bundle_runtime.sh" + +### +# astyle +# +errorsDetected=0 +for file in $(stagedSourceFiles); do + git astyle $file >/dev/null + if ! git diff-files --quiet $file >&2; then + warn "$file has been updated to match the MySensors core coding style." + errorsDetected=1 + fi +done + +[ $errorsDetected == 1 ] && err "Styling updates applied. Review the changes and use 'git add' to update your staged files." + +### +# cppcheck +# +git cppcheck --cached || err "Correct the errors until cppcheck passes using 'git cppcheck --cached'." + +exit 0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1617fd8f..ca7cd1614 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -To get started, sign the Contributor License Agreement. \ No newline at end of file +To get started, sign the Contributor License Agreement. diff --git a/Documentation/Doxyfile b/Doxyfile similarity index 100% rename from Documentation/Doxyfile rename to Doxyfile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..742dd82d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,154 @@ +############################################################################# +# +# Makefile for MySensors +# +# +# The arduino library build part were inspired by +# Arduino-Makefile project, Copyright (C) 2012 Sudar +# +# Description: +# ------------ +# use make all and make install to install the gateway +# + +CONFIG_FILE=Makefile.inc + +include $(CONFIG_FILE) + +CPPFLAGS+=-Ofast -g -Wall -Wextra +DEPFLAGS=-MT $@ -MMD -MP + +GATEWAY_BIN=mysgw +GATEWAY=$(BINDIR)/$(GATEWAY_BIN) +GATEWAY_C_SOURCES=$(wildcard drivers/Linux/*.c) +GATEWAY_CPP_SOURCES=$(wildcard drivers/Linux/*.cpp) examples_linux/mysgw.cpp +GATEWAY_OBJECTS=$(patsubst %.c,$(BUILDDIR)/%.o,$(GATEWAY_C_SOURCES)) $(patsubst %.cpp,$(BUILDDIR)/%.o,$(GATEWAY_CPP_SOURCES)) + +INCLUDES=-I. -I./core -I./drivers/Linux + +ifeq ($(SOC),$(filter $(SOC),BCM2835 BCM2836)) +RPI_C_SOURCES=$(wildcard drivers/RPi/*.c) +RPI_CPP_SOURCES=$(wildcard drivers/RPi/*.cpp) +GATEWAY_OBJECTS+=$(patsubst %.c,$(BUILDDIR)/%.o,$(RPI_C_SOURCES)) $(patsubst %.cpp,$(BUILDDIR)/%.o,$(RPI_CPP_SOURCES)) + +INCLUDES+=-I./drivers/RPi +endif + +# Gets include flags for library +get_library_includes = $(if $(and $(wildcard $(1)/src), $(wildcard $(1)/library.properties)), \ + -I$(1)/src, \ + $(addprefix -I,$(1) $(wildcard $(1)/utility))) + +# Gets all sources with given extension (param2) for library (path = param1) +# for old (1.0.x) layout looks in . and "utility" directories +# for new (1.5.x) layout looks in src and recursively its subdirectories +get_library_files = $(if $(and $(wildcard $(1)/src), $(wildcard $(1)/library.properties)), \ + $(call rwildcard,$(1)/src/,*.$(2)), \ + $(wildcard $(1)/*.$(2) $(1)/utility/*.$(2))) + +ifdef ARDUINO_LIB_DIR +ARDUINO=arduino +ARDUINO_LIBS:=$(shell find $(ARDUINO_LIB_DIR) -mindepth 1 -maxdepth 1 -type d) +ARDUINO_INCLUDES:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_includes,$(lib))) +ARDUINO_LIB_CPP_SRCS:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_files,$(lib),cpp)) +ARDUINO_LIB_C_SRCS:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_files,$(lib),c)) +ARDUINO_LIB_AS_SRCS:=$(foreach lib, $(ARDUINO_LIBS), $(call get_library_files,$(lib),S)) +ARDUINO_LIB_OBJS=$(patsubst $(ARDUINO_LIB_DIR)/%.cpp,$(BUILDDIR)/arduinolibs/%.cpp.o,$(ARDUINO_LIB_CPP_SRCS)) \ + $(patsubst $(ARDUINO_LIB_DIR)/%.c,$(BUILDDIR)/arduinolibs/%.c.o,$(ARDUINO_LIB_C_SRCS)) \ + $(patsubst $(ARDUINO_LIB_DIR)/%.S,$(BUILDDIR)/arduinolibs/%.S.o,$(ARDUINO_LIB_AS_SRCS)) + +INCLUDES+=$(ARDUINO_INCLUDES) +DEPS+=$(ARDUINO_LIB_OBJS:.o=.d) +endif + +DEPS+=$(GATEWAY_OBJECTS:.o=.d) + +.PHONY: all createdir cleanconfig clean install uninstall + +all: createdir $(ARDUINO) $(GATEWAY) + +createdir: + @mkdir -p $(BUILDDIR) $(BINDIR) + +# Arduino libraries Build +$(ARDUINO): CPPFLAGS+=-DARDUINO=100 +$(ARDUINO): $(ARDUINO_LIB_OBJS) + @printf "[Done building Arduino Libraries]\n" + +# Gateway Build +$(GATEWAY): $(GATEWAY_OBJECTS) $(ARDUINO_LIB_OBJS) + $(CXX) $(LDFLAGS) -o $@ $(GATEWAY_OBJECTS) $(ARDUINO_LIB_OBJS) + +# Include all .d files +-include $(DEPS) + +$(BUILDDIR)/arduinolibs/%.cpp.o: $(ARDUINO_LIB_DIR)/%.cpp + @mkdir -p $(dir $@) + $(CXX) $(DEPFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILDDIR)/arduinolibs/%.c.o: $(ARDUINO_LIB_DIR)/%.c + @mkdir -p $(dir $@) + $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILDDIR)/arduinolibs/%.S.o: $(ARDUINO_LIB_DIR)/%.S + @mkdir -p $(dir $@) + $(CC) $(DEPFLAGS) $(CPPFLAGS) $(ASFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILDDIR)/%.o: %.cpp + @mkdir -p $(dir $@) + $(CXX) $(DEPFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(INCLUDES) -c $< -o $@ + +$(BUILDDIR)/%.o: %.c + @mkdir -p $(dir $@) + $(CC) $(DEPFLAGS) $(CPPFLAGS) $(CFLAGS) $(INCLUDES) -c $< -o $@ + +# clear configuration files +cleanconfig: + @echo "[Cleaning configuration]" + rm -rf $(CONFIG_FILE) + +# clear build files +clean: + @echo "[Cleaning]" + rm -rf $(BUILDDIR) $(BINDIR) + +$(CONFIG_FILE): + @echo "[Running configure]" + @./configure --no-clean + +install: all install-gateway install-initscripts + +install-gateway: + @echo "Installing $(GATEWAY) to ${DESTDIR}$(GATEWAY_DIR)" + @install -m 0755 $(GATEWAY) ${DESTDIR}$(GATEWAY_DIR) + +install-initscripts: +ifeq ($(INIT_SYSTEM), systemd) + install -m0644 initscripts/mysgw.systemd ${DESTDIR}/etc/systemd/system/mysgw.service + @sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" ${DESTDIR}/etc/systemd/system/mysgw.service + systemctl daemon-reload + @echo "MySensors gateway has been installed, to add to the boot run:" + @echo " sudo systemctl enable mysgw.service" + @echo "To start the gateway run:" + @echo " sudo systemctl start mysgw.service" +else ifeq ($(INIT_SYSTEM), sysvinit) + install -m0755 initscripts/mysgw.sysvinit ${DESTDIR}/etc/init.d/mysgw + @sed -i -e "s|%gateway_dir%|${GATEWAY_DIR}|g" ${DESTDIR}/etc/init.d/mysgw + @echo "MySensors gateway has been installed, to add to the boot run:" + @echo " sudo update-rc.d mysgw defaults" + @echo "To start the gateway run:" + @echo " sudo service mysgw start" +endif + +uninstall: +ifeq ($(INIT_SYSTEM), systemd) + @echo "Stopping daemon mysgw (ignore errors)" + -@systemctl stop mysgw.service + @echo "removing files" + rm /etc/systemd/system/mysgw.service $(GATEWAY_DIR)/$(GATEWAY_BIN) +else ifeq ($(INIT_SYSTEM), sysvinit) + @echo "Stopping daemon mysgw (ignore errors)" + -@service mysgw stop + @echo "removing files" + rm /etc/init.d/mysgw $(GATEWAY_DIR)/$(GATEWAY_BIN) +endif diff --git a/MyConfig.h b/MyConfig.h index 2a9c5da74..16f6da722 100644 --- a/MyConfig.h +++ b/MyConfig.h @@ -2,7 +2,7 @@ * The MySensors Arduino library handles the wireless radio link and protocol * between your home built sensors/actuators and HA controller of choice. * The sensors forms a self healing radio network with optional repeaters. Each - * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * repeater and gateway builds a routing tables in RAM or EEPROM which keeps track of the * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad @@ -67,6 +67,15 @@ #ifndef MY_BAUD_RATE #define MY_BAUD_RATE 115200 #endif +/** +* @def MY_SERIAL_OUTPUT_SIZE +* @brief Max. characters for serial output. +*/ +#ifndef MY_SERIAL_OUTPUT_SIZE +#define MY_SERIAL_OUTPUT_SIZE (120u) +#endif + + // Disables over-the-air reset of node //#define MY_DISABLE_REMOTE_RESET @@ -79,54 +88,98 @@ //#define MY_RADIO_NRF24 //#define MY_RADIO_RFM69 +//#define MY_RADIO_RFM95 //#define MY_RS485 +/** +* @def MY_RAM_ROUTING_TABLE_FEATURE +* @brief If enabled, the routing table is kept in RAM (if memory allows) and saved in regular intervals. +* note: AVR has limited memory, use with care +*/ +#define MY_RAM_ROUTING_TABLE_FEATURE + +/** +* @def MY_ROUTING_TABLE_SAVE_INTERVAL_MS +* @brief Interval to dump content of routing table to eeprom +*/ +#ifndef MY_ROUTING_TABLE_SAVE_INTERVAL_MS +#define MY_ROUTING_TABLE_SAVE_INTERVAL_MS (30*60*1000ul) +#endif /** * @def MY_TRANSPORT_SANITY_CHECK -* @brief If enabled, node will check transport in regular intervals to detect HW issues and re-initialize in case of failure. This feature is enabled for all repeater nodes (incl. GW) +* @brief If enabled, node will check transport in regular intervals to detect HW issues and re-initialize in case of failure. +* This feature is enabled for all repeater nodes (incl. GW) */ //#define MY_TRANSPORT_SANITY_CHECK + +/** +* @def MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS +* @brief Interval (in ms) for transport sanity checks +*/ +#ifndef MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS +#define MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS (15*60*1000ul) +#endif /** -* @def MY_TRANSPORT_SANITY_CHECK_INTERVAL -* @brief Interval (in ms) of transport sanity checks +* @def MY_TRANSPORT_DISCOVERY_INTERVAL_MS +* @brief This is a gateway-only feature: Interval (in ms) to issue network discovery checks */ -#ifndef MY_TRANSPORT_SANITY_CHECK_INTERVAL -#define MY_TRANSPORT_SANITY_CHECK_INTERVAL ((uint32_t)60000) +#ifndef MY_TRANSPORT_DISCOVERY_INTERVAL_MS +#define MY_TRANSPORT_DISCOVERY_INTERVAL_MS (20*60*1000ul) #endif + +/** + *@def MY_TRANSPORT_UPLINK_CHECK_DISABLED + *@brief If set, uplink check to GW is disabled during transport initialisation + */ +//#define MY_TRANSPORT_UPLINK_CHECK_DISABLED + +/** + *@def MY_TRANSPORT_MAX_TX_FAILURES + *@brief Set to override max. consecutive TX failures until SNP is initiated + */ +//#define MY_TRANSPORT_MAX_TX_FAILURES (10u) + /** * @def MY_REGISTRATION_FEATURE * @brief If enabled, node has to register to gateway/controller before allowed to send sensor data. */ #define MY_REGISTRATION_FEATURE - /** - * @def MY_REGISTRATION_RETRIES - * @brief Number of registration retries if no reply received from GW/controller - */ +/** +* @def MY_REGISTRATION_RETRIES +* @brief Number of registration retries if no reply received from GW/controller +*/ #ifndef MY_REGISTRATION_RETRIES -#define MY_REGISTRATION_RETRIES 3 +#define MY_REGISTRATION_RETRIES (3u) #endif - /** - * @def MY_REGISTRATION_DEFAULT - * @brief Node registration default - this applies if no registration response is recieved from controller - */ - +/** +* @def MY_REGISTRATION_DEFAULT +* @brief Node registration default - this applies if no registration response is received from controller +*/ #define MY_REGISTRATION_DEFAULT true - /** - * @def MY_REGISTRATION_CONTROLLER - * @brief If enabled, node registration request has to be handled by controller - */ - // #define MY_REGISTRATION_CONTROLLER +/** +* @def MY_REGISTRATION_CONTROLLER +* @brief If enabled, node registration request has to be handled by controller +*/ +// #define MY_REGISTRATION_CONTROLLER - /** - * @def MY_CORE_COMPATIBILITY_CHECK - * @brief If enabled, library compatibility is checked during node registration. Incompatible libraries are unable to send sensor data. - */ +/** +* @def MY_CORE_COMPATIBILITY_CHECK +* @brief If enabled, library compatibility is checked during node registration. Incompatible libraries are unable to send sensor data. +*/ #define MY_CORE_COMPATIBILITY_CHECK +/** +* @def MY_TRANSPORT_WAIT_READY_MS +* @brief Timeout in MS until transport is ready during startup, set to 0 for no timeout +*/ +#ifndef MY_TRANSPORT_WAIT_READY_MS +#define MY_TRANSPORT_WAIT_READY_MS (0ul) +#endif + /** * @def MY_NODE_ID * @brief Node id defaults to AUTO (tries to fetch id from controller). @@ -149,18 +202,29 @@ */ //#define MY_PARENT_NODE_IS_STATIC -// Enables repeater functionality (relays messages from other nodes) -// #define MY_REPEATER_FEATURE +/** +* @def MY_REPEATER_FEATURE +* @brief Enables repeater functionality (relays messages from other nodes) +*/ +//#define MY_REPEATER_FEATURE + +/** +* @def MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS +* @brief Timeout (in ms) to re-establish link if node is send to sleep and transport is not ready. +*/ +#ifndef MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS +#define MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS (10*1000ul) +#endif /** - * @def MY_SMART_SLEEP_WAIT_DURATION - * @brief The wait period before going to sleep when using smartSleep-functions. + * @def MY_SMART_SLEEP_WAIT_DURATION_MS + * @brief The wait period (in ms) before going to sleep when using smartSleep-functions. * * This period has to be long enough for controller to be able to send out * potential buffered messages. */ -#ifndef MY_SMART_SLEEP_WAIT_DURATION -#define MY_SMART_SLEEP_WAIT_DURATION 500 +#ifndef MY_SMART_SLEEP_WAIT_DURATION_MS +#define MY_SMART_SLEEP_WAIT_DURATION_MS (500ul) #endif /********************************** @@ -199,7 +263,7 @@ * @brief Max buffersize needed for messages coming from controller. */ #ifndef MY_GATEWAY_MAX_RECEIVE_LENGTH -#define MY_GATEWAY_MAX_RECEIVE_LENGTH 100 +#define MY_GATEWAY_MAX_RECEIVE_LENGTH (100u) #endif /** @@ -207,7 +271,7 @@ * @brief Max buffer size when sending messages. */ #ifndef MY_GATEWAY_MAX_SEND_LENGTH -#define MY_GATEWAY_MAX_SEND_LENGTH 120 +#define MY_GATEWAY_MAX_SEND_LENGTH (120u) #endif /** @@ -215,7 +279,7 @@ * @brief Max number of parallel clients (sever mode). */ #ifndef MY_GATEWAY_MAX_CLIENTS -#define MY_GATEWAY_MAX_CLIENTS 1 +#define MY_GATEWAY_MAX_CLIENTS (1u) #endif @@ -223,25 +287,22 @@ /********************************** * Information LEDs blinking ***********************************/ -// This feature enables LEDs blinking on message receive, transmit -// or if some error occurred. This was commonly used only in gateways, -// but now can be used in any sensor node. Also the LEDs can now be -// disabled in the gateway. - -//#define MY_LEDS_BLINKING_FEATURE +// If one of the following is defined here, or in the sketch, the pin will be used for the +// corresponding led function. +// They have to be enabled here (or in your sketch). Replace x with the pin number you have the LED on. +// +// NOTE!! that on some platforms (for example sensebender GW) the hardware variant can enable LEDs by default, +// These defaults can be overridden by defining one of these. +//#define MY_DEFAULT_ERR_LED_PIN x +//#define MY_DEFAULT_TX_LED_PIN x +//#define MY_DEFAULT_RX_LED_PIN x -// The following setting allows you to inverse the blinking feature MY_LEDS_BLINKING_FEATURE +// The following setting allows you to inverse the LED blinking // When MY_WITH_LEDS_BLINKING_INVERSE is enabled LEDSs are normally turned on and switches // off when blinking //#define MY_WITH_LEDS_BLINKING_INVERSE -// The following defines can be used to set the port pin, that the LED is connected to -// If one of the following is defined here, or in the sketch, MY_LEDS_BLINKING_FEATURE will be -// enabled by default. (Replace x with the pin number you have the LED on) -//#define MY_DEFAULT_ERR_LED_PIN x -//#define MY_DEFAULT_TX_LED_PIN x -//#define MY_DEFAULT_RX_LED_PIN x /********************************************** * Gateway inclusion button/mode configuration @@ -262,11 +323,11 @@ * @brief The default input pin used for the inclusion mode button. */ #ifndef MY_INCLUSION_MODE_BUTTON_PIN - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_INCLUSION_MODE_BUTTON_PIN 5 - #else - #define MY_INCLUSION_MODE_BUTTON_PIN 3 - #endif +#if defined(ARDUINO_ARCH_ESP8266) +#define MY_INCLUSION_MODE_BUTTON_PIN 5 +#else +#define MY_INCLUSION_MODE_BUTTON_PIN 3 +#endif #endif /** @@ -316,6 +377,15 @@ */ //#define MY_SIGNING_REQUEST_SIGNATURES +/** + * @def MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL + * @brief Enable this to have gateway require all nodes in the network to sign messages sent to it. @ref MY_SIGNING_REQUEST_SIGNATURES must also be set. + * + * Use this for maximum security, but be aware that every single node will have to be personalized before they can be used. + * Note that if this is enabled, and whitelisting is also enabled, whitelisting will also be in effect for all nodes. + */ +//#define MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL + /** * @def MY_VERIFICATION_TIMEOUT_MS * @brief Define a suitable timeout for a signature verification session @@ -382,6 +452,18 @@ #define MY_RS485_MAX_MESSAGE_LENGTH 40 #endif +/** + * @def MY_RS485_DE_PIN + * @brief RS485 driver enable pin. + */ +//#define MY_RS485_DE_PIN 2 + +/** + * @def MY_RS485_HWSERIAL + * @brief Enable this if RS485 is connected to a hardware serial port. + */ +//#define MY_RS485_HWSERIAL Serial1 + /********************************** * NRF24L01P Driver Defaults ***********************************/ @@ -406,13 +488,15 @@ * @brief Default RF24 chip enable pin setting. Override in sketch if needed. */ #ifndef MY_RF24_CE_PIN - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_RF24_CE_PIN 4 - #elif defined(ARDUINO_ARCH_SAMD) - #define MY_RF24_CE_PIN 27 - #else - #define MY_RF24_CE_PIN 9 - #endif +#if defined(ARDUINO_ARCH_ESP8266) +#define MY_RF24_CE_PIN 4 +#elif defined(ARDUINO_ARCH_SAMD) +#define MY_RF24_CE_PIN 27 +#elif defined(LINUX_ARCH_RASPBERRYPI) +#define MY_RF24_CE_PIN 22 +#else +#define MY_RF24_CE_PIN 9 +#endif #endif /** @@ -420,13 +504,33 @@ * @brief Default RF24 chip select pin setting. Override in sketch if needed. */ #ifndef MY_RF24_CS_PIN - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_RF24_CS_PIN 15 - #elif defined(ARDUINO_ARCH_SAMD) - #define MY_RF24_CS_PIN 3 - #else - #define MY_RF24_CS_PIN 10 - #endif +#if defined(ARDUINO_ARCH_ESP8266) +#define MY_RF24_CS_PIN 15 +#elif defined(ARDUINO_ARCH_SAMD) +#define MY_RF24_CS_PIN 3 +#elif defined(LINUX_ARCH_RASPBERRYPI) +#define MY_RF24_CS_PIN 24 +#else +#define MY_RF24_CS_PIN 10 +#endif +#endif + +/** +* @def MY_RX_MESSAGE_BUFFER_FEATURE +* @brief This enabled the receiving buffer feature. +* +* This feature is currently not supported for RFM69 and RS485, for RF24 MY_RF24_IRQ_PIN has to be defined. +*/ +//#define MY_RX_MESSAGE_BUFFER_FEATURE + +/** + * @def MY_RX_MESSAGE_BUFFER_SIZE + * @brief Declare the amount of incoming messages that can be buffered. + */ +#ifdef MY_RX_MESSAGE_BUFFER_FEATURE +#ifndef MY_RX_MESSAGE_BUFFER_SIZE +#define MY_RX_MESSAGE_BUFFER_SIZE (20) +#endif #endif /** @@ -483,14 +587,6 @@ #define MY_RF24_ADDR_WIDTH 5 #endif -/** - * @def MY_RF24_SANITY_CHECK - * @brief RF24 sanity check to verify functional RF module - * - * This reads back and compares configuration registers. Disable if using non-P modules - */ -#define MY_RF24_SANITY_CHECK - // Enable SOFTSPI for NRF24L01, useful for the W5100 Ethernet module //#define MY_SOFTSPI @@ -543,9 +639,9 @@ * @brief Set to true if @ref MY_IS_RFM69HW is set. */ #ifdef MY_IS_RFM69HW - #define MY_RFM69HW true +#define MY_RFM69HW true #else - #define MY_RFM69HW false +#define MY_RFM69HW false #endif /** @@ -577,16 +673,97 @@ * @brief RF69 IRQ pin number. */ #ifndef MY_RF69_IRQ_NUM - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_RF69_IRQ_NUM MY_RF69_IRQ_PIN - #else - #define MY_RF69_IRQ_NUM RF69_IRQ_NUM - #endif +#if defined(ARDUINO_ARCH_ESP8266) +#define MY_RF69_IRQ_NUM RF69_IRQ_PIN +#else +#define MY_RF69_IRQ_NUM RF69_IRQ_NUM +#endif #endif // Enables RFM69 encryption (all nodes and gateway must have this enabled, and all must be personalized with the same AES key) //#define MY_RFM69_ENABLE_ENCRYPTION +/********************************** +* RFM95 driver defaults +***********************************/ + +/** + * @def MY_RFM95_FREQUENCY + * @brief RFM95 frequency + * + * This must match the hardware version of the RFM95 radio. + */ +#ifndef MY_RFM95_FREQUENCY +#define MY_RFM95_FREQUENCY (868.1f) +#endif +/** +* @def MY_RFM95_MODEM_CONFIGRUATION +* @brief RFM95 modem configuration, see table +* +* BW = Bandwidth in kHz +* CR = Error correction code +* SF = Spreading factor, chips / symbol +* +* | CONFIG | BW | CR | SF | Comment +* |------------------------|-------|-----|------|----------------------------- +* | RFM95_BW125CR45SF128 | 125 | 4/5 | 128 | Default, medium range +* | RFM95_BW500CR45SF128 | 500 | 4/5 | 128 | Fast, short range +* | RFM95_BW31_25CR48SF512 | 31.25 | 4/8 | 512 | Slow, long range +* | RFM95_BW125CR48SF4096 | 125 | 4/8 | 4096 | Slow, long range +* +*/ + +#ifndef MY_RFM95_MODEM_CONFIGRUATION +// default +#define MY_RFM95_MODEM_CONFIGRUATION RFM95_BW125CR45SF128 +#endif + +/** + * @def MY_RFM95_RST_PIN + * @brief RFM95 reset pin, uncomment if used + */ +//#define MY_RFM95_RST_PIN RFM95_RST_PIN + +/** + * @def MY_RFM95_IRQ_PIN + * @brief RFM95 IRQ pin + */ +#ifndef MY_RFM95_IRQ_PIN +#define MY_RFM95_IRQ_PIN RFM95_IRQ_PIN +#endif + +/** + * @def MY_RFM95_SPI_CS + * @brief RFM95 SPI chip select pin + */ +#ifndef MY_RFM95_SPI_CS +#define MY_RFM95_SPI_CS RFM95_SPI_CS +#endif + +/** + * @def MY_RFM95_TX_POWER + * @brief RFM95 TX power level. + */ +#ifndef MY_RFM95_TX_POWER +#define MY_RFM95_TX_POWER 13 +#endif + +/** +* @def MY_RFM95_ATC_MODE_DISABLED +* @brief Enable to disable ATC mode +*/ +//#define MY_RFM95_ATC_MODE_DISABLED + +/** +* @def MY_RFM95_ATC_TARGET_RSSI +* @brief Traget RSSI level for ATC mode +*/ +#ifndef MY_RFM95_ATC_TARGET_RSSI +#define MY_RFM95_ATC_TARGET_RSSI (-60) +#endif + + + /************************************** * Ethernet Gateway Transport Defaults ***************************************/ @@ -595,14 +772,19 @@ //#define MY_GATEWAY_W5100 //#define MY_GATEWAY_ENC28J60 //#define MY_GATEWAY_ESP8266 +//#define MY_GATEWAY_LINUX /** * @def MY_PORT * @brief The Ethernet TCP/UDP port to open on controller or gateway. */ #ifndef MY_PORT +#ifdef MY_GATEWAY_MQTT_CLIENT +#define MY_PORT 1883 +#else #define MY_PORT 5003 #endif +#endif // Static ip address of gateway (if this is disabled, DHCP will be used) //#define MY_IP_ADDRESS 192,168,178,66 @@ -632,6 +814,10 @@ // If MY_CONTROLLER_IP_ADDRESS is left un-defined, gateway acts as server allowing incoming connections. //#define MY_CONTROLLER_IP_ADDRESS 192, 168, 178, 254 +/************************************** +* Node Locking +***************************************/ + /** * @defgroup MyLockgrp MyNodeLock * @ingroup internals @@ -688,18 +874,89 @@ #endif /** @}*/ // Node lock group +/********************************** +* ESP8266 Defaults +***********************************/ + +/** + * @def MY_ESP8266_SERIAL_MODE + * @brief Serial modes: SERIAL_FULL, SERIAL_RX_ONLY, SERIAL_TX_ONLY + * + * SERIAL_FULL: Default mode. + * SERIAL_TX_ONLY: allows to use RX (GPIO3) as a general purpose input/output. + * SERIAL_RX_ONLY: allows to use TX (GPIO1) as a general purpose input/output. + */ +#ifndef MY_ESP8266_SERIAL_MODE +#define MY_ESP8266_SERIAL_MODE SERIAL_FULL #endif +/************************************** +* Linux Settings +***************************************/ + +/** + * @def MY_LINUX_SERIAL_PORT + * @brief Serial device port + */ +#ifndef MY_LINUX_SERIAL_PORT +#define MY_LINUX_SERIAL_PORT "/dev/ttyACM0" +#endif + +/** + * @def MY_IS_SERIAL_PTY + * @brief Set serial as a pseudo terminal. + * + * Enable this if you need to connect to a controller running on the same device. + */ +//#define MY_IS_SERIAL_PTY + +/** + * @def MY_LINUX_SERIAL_PTY + * @brief Symlink name for the PTY device. + */ +#ifndef MY_LINUX_SERIAL_PTY +#define MY_LINUX_SERIAL_PTY "/dev/ttyMySensorsGateway" +#endif + +/** + * @def MY_LINUX_SERIAL_GROUPNAME + * @brief Grant access to the specified system group for the serial device. + */ +//#define MY_LINUX_SERIAL_GROUPNAME "tty" + +/** + * @def MY_LINUX_CONFIG_FILE + * @brief Set the filepath for the gateway config file + * + * For now the configuration file is only used to store the emulated eeprom state + */ +#ifndef MY_LINUX_CONFIG_FILE +#define MY_LINUX_CONFIG_FILE "/etc/mysensors.dat" +#endif + +#endif // MyConfig_h + // Doxygen specific constructs, not included when built normally // This is used to enable disabled macros/definitions to be included in the documentation as well. #if DOXYGEN #define MY_SIGNING_ATSHA204 #define MY_SIGNING_SOFT #define MY_SIGNING_REQUEST_SIGNATURES +#define MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL #define MY_SIGNING_NODE_WHITELISTING {{.nodeId = GATEWAY_ADDRESS,.serial = {0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01}}} +#define MY_RS485_HWSERIAL #define MY_IS_RFM69HW #define MY_PARENT_NODE_IS_STATIC #define MY_REGISTRATION_CONTROLLER +#define MY_TRANSPORT_UPLINK_CHECK_DISABLED #define MY_DEBUG_VERBOSE_RF24 #define MY_TRANSPORT_SANITY_CHECK +#define MY_RX_MESSAGE_BUFFER_FEATURE +#define MY_RX_MESSAGE_BUFFER_SIZE +#define MY_NODE_LOCK_FEATURE +#define MY_REPEATER_FEATURE +#define MY_LINUX_SERIAL_GROUPNAME +#define MY_IS_SERIAL_PTY +#define MY_RFM95_ATC_MODE_DISABLED +#define MY_RFM95_RST_PIN #endif diff --git a/MySensors.h b/MySensors.h index 189990e24..3c5bf3b15 100644 --- a/MySensors.h +++ b/MySensors.h @@ -40,61 +40,58 @@ * @def MY_NODE_TYPE * @brief Contain a string describing the class of sketch/node (gateway/repeater/sensor). */ -#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_MQTT_CLIENT) - #define MY_GATEWAY_FEATURE - #define MY_IS_GATEWAY (true) - #define MY_NODE_TYPE "gateway" +#if defined(MY_GATEWAY_SERIAL) || defined(MY_GATEWAY_W5100) || defined(MY_GATEWAY_ENC28J60) || defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) || defined(MY_GATEWAY_MQTT_CLIENT) +#define MY_GATEWAY_FEATURE +#define MY_IS_GATEWAY (true) +#define MY_NODE_TYPE "GW" #elif defined(MY_REPEATER_FEATURE) - #define MY_IS_GATEWAY (false) - #define MY_NODE_TYPE "repeater" +#define MY_IS_GATEWAY (false) +#define MY_NODE_TYPE "REPEATER" #else - #define MY_IS_GATEWAY (false) - #define MY_NODE_TYPE "sensor" +#define MY_IS_GATEWAY (false) +#define MY_NODE_TYPE "NODE" #endif // Enable radio "feature" if one of the radio types was enabled -#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485) - #define MY_RADIO_FEATURE +#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) || defined(MY_RS485) +#define MY_SENSOR_NETWORK #endif // HARDWARE #if defined(ARDUINO_ARCH_ESP8266) - // Remove PSTR macros from debug prints - #undef PSTR - #define PSTR(x) (x) - //#undef F - //#define F(x) (x) - #include "core/MyHwESP8266.cpp" +#include "core/MyHwESP8266.cpp" #elif defined(ARDUINO_ARCH_AVR) - #include "core/MyHwATMega328.cpp" +#include "drivers/AVR/DigitalWriteFast/digitalWriteFast.h" +#include "core/MyHwATMega328.cpp" #elif defined(ARDUINO_ARCH_SAMD) - #include "core/MyHwSAMD.cpp" +#include "core/MyHwSAMD.cpp" +#elif defined(__linux__) +#ifdef LINUX_ARCH_RASPBERRYPI +#include "core/MyHwRPi.cpp" +#else +#include "core/MyHwLinuxGeneric.cpp" +#endif #endif // LEDS -#if !defined(MY_DEFAULT_ERR_LED_PIN) & defined(MY_HW_ERR_LED_PIN) - #define MY_DEFAULT_ERR_LED_PIN MY_HW_ERR_LED_PIN +#if !defined(MY_DEFAULT_ERR_LED_PIN) && defined(MY_HW_ERR_LED_PIN) +#define MY_DEFAULT_ERR_LED_PIN MY_HW_ERR_LED_PIN #endif #if !defined(MY_DEFAULT_TX_LED_PIN) && defined(MY_HW_TX_LED_PIN) - #define MY_DEFAULT_TX_LED_PIN MY_HW_TX_LED_PIN +#define MY_DEFAULT_TX_LED_PIN MY_HW_TX_LED_PIN #endif #if !defined(MY_DEFAULT_RX_LED_PIN) && defined(MY_HW_TX_LED_PIN) - #define MY_DEFAULT_RX_LED_PIN MY_HW_TX_LED_PIN -#endif - -// Not necessary to include blinking feature if no LED's are defined! -#if defined(MY_LEDS_BLINKING_FEATURE) && !defined(MY_DEFAULT_RX_LED_PIN) && !defined(MY_DEFAULT_TX_LED_PIN) && !defined(MY_ERR_LED_PIN) - #undef MY_LEDS_BLINKING_FEATURE +#define MY_DEFAULT_RX_LED_PIN MY_HW_TX_LED_PIN #endif -// Enable LED BLINKING FEATURE, if there are any LEDs defined. -#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED) || defined(MY_DEFAULT_TX_LED_PIN) - #define MY_LEDS_BLINKING_FEATURE +#if defined(MY_LEDS_BLINKING_FEATURE) +#error MY_LEDS_BLINKING_FEATURE is now removed from MySensors core,\ +define MY_DEFAULT_ERR_LED_PIN, MY_DEFAULT_TX_LED_PIN or\ +MY_DEFAULT_RX_LED_PIN in your sketch instead to enable LEDs #endif - /** * @def MY_DEFAULT_LED_BLINK_PERIOD * @brief Default LEDs blinking period in milliseconds. @@ -102,44 +99,11 @@ #ifndef MY_DEFAULT_LED_BLINK_PERIOD #define MY_DEFAULT_LED_BLINK_PERIOD 300 #endif -/** - * @def MY_DEFAULT_RX_LED_PIN - * @brief The RX LED default pin. - */ -#ifndef MY_DEFAULT_RX_LED_PIN - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_DEFAULT_RX_LED_PIN 8 - #else - #define MY_DEFAULT_RX_LED_PIN 6 - #endif -#endif -/** - * @def MY_DEFAULT_TX_LED_PIN - * @brief The TX LED default pin. - */ -#ifndef MY_DEFAULT_TX_LED_PIN - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_DEFAULT_TX_LED_PIN 9 - #else - #define MY_DEFAULT_TX_LED_PIN 5 - #endif -#endif -/** - * @def MY_DEFAULT_ERR_LED_PIN - * @brief The Error LED default pin. - */ -#ifndef MY_DEFAULT_ERR_LED_PIN - #if defined(ARDUINO_ARCH_ESP8266) - #define MY_DEFAULT_ERR_LED_PIN 7 - #else - #define MY_DEFAULT_ERR_LED_PIN 4 - #endif -#endif -#if defined(MY_LEDS_BLINKING_FEATURE) - #include "core/MyLeds.cpp" +#if defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) +#include "core/MyLeds.cpp" #else - #include "core/MyLeds.h" +#include "core/MyLeds.h" #endif #include "core/MyIndication.cpp" @@ -147,145 +111,226 @@ // INCLUSION MODE #if defined(MY_INCLUSION_MODE_FEATURE) - #include "core/MyInclusionMode.cpp" +#include "core/MyInclusionMode.cpp" #endif // SIGNING #if defined(MY_SIGNING_ATSHA204) || defined(MY_SIGNING_SOFT) - #define MY_SIGNING_FEATURE +#define MY_SIGNING_FEATURE #endif #include "core/MySigning.cpp" #include "drivers/ATSHA204/sha256.cpp" #if defined(MY_SIGNING_FEATURE) - // SIGNING COMMON FUNCTIONS - #if defined(MY_SIGNING_ATSHA204) && defined(MY_SIGNING_SOFT) - #error Only one signing engine can be activated - #endif +// SIGNING COMMON FUNCTIONS +#if defined(MY_SIGNING_ATSHA204) && defined(MY_SIGNING_SOFT) +#error Only one signing engine can be activated +#endif +#if defined(MY_SIGNING_ATSHA204) && defined(__linux__) +#error No support for ATSHA204 on this platform +#endif - #if defined(MY_SIGNING_ATSHA204) - #include "core/MySigningAtsha204.cpp" - #include "drivers/ATSHA204/ATSHA204.cpp" - #elif defined(MY_SIGNING_SOFT) - #include "core/MySigningAtsha204Soft.cpp" - #endif +#if defined(MY_SIGNING_ATSHA204) +#include "core/MySigningAtsha204.cpp" +#include "drivers/ATSHA204/ATSHA204.cpp" +#elif defined(MY_SIGNING_SOFT) +#include "core/MySigningAtsha204Soft.cpp" +#endif #endif // FLASH #if defined(MY_OTA_FIRMWARE_FEATURE) - #include "drivers/SPIFlash/SPIFlash.cpp" - #include "core/MyOTAFirmwareUpdate.cpp" +#include "drivers/SPIFlash/SPIFlash.cpp" +#include "core/MyOTAFirmwareUpdate.cpp" #endif // GATEWAY - TRANSPORT +#if defined(MY_CONTROLLER_IP_ADDRESS) || defined(MY_CONTROLLER_URL_ADDRESS) +#define MY_GATEWAY_CLIENT_MODE +#endif +#if defined(MY_USE_UDP) && !defined(MY_GATEWAY_CLIENT_MODE) +#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS for UDP +#endif + #if defined(MY_GATEWAY_MQTT_CLIENT) - #if defined(MY_RADIO_FEATURE) - // We assume that a gateway having a radio also should act as repeater - #define MY_REPEATER_FEATURE - #endif - // GATEWAY - COMMON FUNCTIONS - // We only support MQTT Client using W5100 and ESP8266 at the moment - #if !(defined(MY_CONTROLLER_URL_ADDRESS) || defined(MY_CONTROLLER_IP_ADDRESS)) - #error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS - #endif - - #if !defined(MY_MQTT_PUBLISH_TOPIC_PREFIX) - #error You must specify a topic publish prefix MY_MQTT_PUBLISH_TOPIC_PREFIX for this MQTT client - #endif - #if !defined(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) - #error You must specify a topic subscribe prefix MY_MQTT_SUBSCRIBE_TOPIC_PREFIX for this MQTT client - #endif - #if !defined(MY_MQTT_CLIENT_ID) - #error You must define a unique MY_MQTT_CLIENT_ID for this MQTT client - #endif - - #include "drivers/PubSubClient/PubSubClient.cpp" - #include "core/MyGatewayTransport.cpp" - #include "core/MyGatewayTransportMQTTClient.cpp" +#if defined(MY_SENSOR_NETWORK) +// We assume that a gateway having a radio also should act as repeater +#define MY_REPEATER_FEATURE +#endif +// GATEWAY - COMMON FUNCTIONS +// We support MQTT Client using W5100, ESP8266 and Linux +#if !defined(MY_GATEWAY_CLIENT_MODE) +#error You must specify MY_CONTROLLER_IP_ADDRESS or MY_CONTROLLER_URL_ADDRESS +#endif + +#if !defined(MY_MQTT_PUBLISH_TOPIC_PREFIX) +#error You must specify a topic publish prefix MY_MQTT_PUBLISH_TOPIC_PREFIX for this MQTT client +#endif +#if !defined(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) +#error You must specify a topic subscribe prefix MY_MQTT_SUBSCRIBE_TOPIC_PREFIX for this MQTT client +#endif + +#if !defined(MY_MQTT_CLIENT_ID) +#error You must define a unique MY_MQTT_CLIENT_ID for this MQTT client +#endif + +#include "core/MyGatewayTransport.cpp" +#include "core/MyProtocolMySensors.cpp" + +#if defined(MY_GATEWAY_LINUX) +#include "drivers/Linux/EthernetClient.h" +#include "drivers/Linux/EthernetServer.h" +#include "drivers/Linux/IPAddress.h" +#endif +#include "drivers/PubSubClient/PubSubClient.cpp" +#include "core/MyGatewayTransportMQTTClient.cpp" #elif defined(MY_GATEWAY_FEATURE) - // GATEWAY - COMMON FUNCTIONS - #include "core/MyGatewayTransport.cpp" - - // We currently only support one protocol at the moment, enable it. - #include "core/MyProtocolMySensors.cpp" - - // GATEWAY - CONFIGURATION - #if defined(MY_RADIO_FEATURE) - // We assume that a gateway having a radio also should act as repeater - #define MY_REPEATER_FEATURE - #endif - #if defined(MY_CONTROLLER_IP_ADDRESS) - #define MY_GATEWAY_CLIENT_MODE - #endif - #if !defined(MY_PORT) - #error You must define MY_PORT (controller or gatway port to open) - #endif - #if defined(MY_GATEWAY_ESP8266) - // GATEWAY - ESP8266 - #include "core/MyGatewayTransportEthernet.cpp" - #elif defined(MY_GATEWAY_W5100) - // GATEWAY - W5100 - #include "core/MyGatewayTransportEthernet.cpp" - #elif defined(MY_GATEWAY_ENC28J60) - // GATEWAY - ENC28J60 - #if defined(MY_USE_UDP) - #error UDP mode is not available for ENC28J60 - #endif - #include "core/MyGatewayTransportEthernet.cpp" - #elif defined(MY_GATEWAY_SERIAL) - // GATEWAY - SERIAL - #include "core/MyGatewayTransportSerial.cpp" - #endif +// GATEWAY - COMMON FUNCTIONS +#include "core/MyGatewayTransport.cpp" + +#include "core/MyProtocolMySensors.cpp" + +// GATEWAY - CONFIGURATION +#if defined(MY_SENSOR_NETWORK) +// We assume that a gateway having a radio also should act as repeater +#define MY_REPEATER_FEATURE +#endif +#if !defined(MY_PORT) +#error You must define MY_PORT (controller or gatway port to open) +#endif +#if defined(MY_GATEWAY_ESP8266) +// GATEWAY - ESP8266 +#include "core/MyGatewayTransportEthernet.cpp" +#elif defined(MY_GATEWAY_LINUX) +// GATEWAY - Generic Linux +#include "drivers/Linux/EthernetClient.h" +#include "drivers/Linux/EthernetServer.h" +#include "drivers/Linux/IPAddress.h" +#include "core/MyGatewayTransportEthernet.cpp" +#elif defined(MY_GATEWAY_W5100) +// GATEWAY - W5100 +#include "core/MyGatewayTransportEthernet.cpp" +#elif defined(MY_GATEWAY_ENC28J60) +// GATEWAY - ENC28J60 +#if defined(MY_USE_UDP) +#error UDP mode is not available for ENC28J60 +#endif +#include "core/MyGatewayTransportEthernet.cpp" +#elif defined(MY_GATEWAY_SERIAL) +// GATEWAY - SERIAL +#include "core/MyGatewayTransportSerial.cpp" +#endif +#endif + +// RAM ROUTING TABLE +#if defined(MY_RAM_ROUTING_TABLE_FEATURE) && defined(MY_REPEATER_FEATURE) +// activate feature based on architecture +#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_SAMD) || defined(LINUX_ARCH_RASPBERRYPI) +#define MY_RAM_ROUTING_TABLE_ENABLED +#elif defined(ARDUINO_ARCH_AVR) +// memory limited, enable with care +// #define MY_RAM_ROUTING_TABLE_ENABLED +#endif +#endif + +#if defined(MY_REPEATER_FEATURE) +#define MY_TRANSPORT_SANITY_CHECK +#endif + + +#if defined(MY_TRANSPORT_DONT_CARE_MODE) +#error This directive is deprecated, set MY_TRANSPORT_WAIT_READY_MS instead! #endif // RADIO -#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485) - // SOFTSPI - #ifdef MY_SOFTSPI - #if defined(ARDUINO_ARCH_ESP8266) - #error Soft SPI is not available on ESP8266 - #endif - #include "drivers/AVR/DigitalIO/DigitalIO.h" - #endif - - #include "core/MyTransport.cpp" - #if (defined(MY_RADIO_NRF24) && defined(MY_RADIO_RFM69)) || (defined(MY_RADIO_NRF24) && defined(MY_RS485)) || (defined(MY_RADIO_RFM69) && defined(MY_RS485)) - #error Only one forward link driver can be activated - #endif - #if defined(MY_RADIO_NRF24) - #if defined(MY_RF24_ENABLE_ENCRYPTION) - #include "drivers/AES/AES.cpp" - #endif - #include "drivers/RF24/RF24.cpp" - #include "core/MyTransportNRF24.cpp" - #elif defined(MY_RS485) - #include "drivers/AltSoftSerial/AltSoftSerial.cpp" - #include "core/MyTransportRS485.cpp" - #elif defined(MY_RADIO_RFM69) - #include "drivers/RFM69/RFM69.cpp" - #include "core/MyTransportRFM69.cpp" - #endif +#if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RADIO_RFM95) ||defined(MY_RS485) +// SOFTSPI +#ifdef MY_SOFTSPI +#if defined(ARDUINO_ARCH_ESP8266) +#error Soft SPI is not available on ESP8266 +#endif +#include "drivers/AVR/DigitalIO/DigitalIO.h" +#endif + +#if defined(MY_RADIO_NRF24) && defined(__linux__) && !defined(LINUX_ARCH_RASPBERRYPI) +#error No support for nRF24 radio on this platform +#endif + +#include "core/MyTransport.cpp" + +// count enabled transports +#if defined(MY_RADIO_NRF24) +#define __RF24CNT 1 +#else +#define __RF24CNT 0 +#endif +#if defined(MY_RADIO_RFM69) +#define __RFM69CNT 1 +#else +#define __RFM69CNT 0 +#endif +#if defined(MY_RADIO_RFM95) +#define __RFM95CNT 1 +#else +#define __RFM95CNT 0 +#endif +#if defined(MY_RS485) +#define __RS485CNT 1 +#else +#define __RS485CNT 0 +#endif + + +#if (__RF24CNT + __RFM69CNT + __RFM95CNT + __RS485CNT > 1) +#error Only one forward link driver can be activated +#endif + +#if defined(MY_RADIO_NRF24) +#if defined(MY_RF24_ENABLE_ENCRYPTION) +#include "drivers/AES/AES.cpp" +#endif +#include "drivers/RF24/RF24.cpp" +#include "core/MyTransportNRF24.cpp" +#elif defined(MY_RS485) +#if !defined(MY_RS485_HWSERIAL) +#if defined(__linux__) +#error You must specify MY_RS485_HWSERIAL for RS485 transport +#endif +#include "drivers/AltSoftSerial/AltSoftSerial.cpp" +#endif +#include "core/MyTransportRS485.cpp" +#elif defined(MY_RADIO_RFM69) +#include "drivers/RFM69/RFM69.cpp" +#include "core/MyTransportRFM69.cpp" +#elif defined(MY_RADIO_RFM95) +#include "drivers/RFM95/RFM95.cpp" +#include "core/MyTransportRFM95.cpp" +#endif +#endif + +#if defined(MY_PARENT_NODE_IS_STATIC) && (MY_PARENT_NODE_ID == AUTO) +#error Parent is static but no parent ID defined. #endif // Make sure to disable child features when parent feature is disabled -#if !defined(MY_RADIO_FEATURE) - #undef MY_OTA_FIRMWARE_FEATURE - #undef MY_REPEATER_FEATURE - #undef MY_SIGNING_NODE_WHITELISTING - #undef MY_SIGNING_FEATURE +#if !defined(MY_SENSOR_NETWORK) +#undef MY_OTA_FIRMWARE_FEATURE +#undef MY_REPEATER_FEATURE +#undef MY_SIGNING_NODE_WHITELISTING +#undef MY_SIGNING_FEATURE #endif #if !defined(MY_GATEWAY_FEATURE) - #undef MY_INCLUSION_MODE_FEATURE - #undef MY_INCLUSION_BUTTON_FEATURE +#undef MY_INCLUSION_MODE_FEATURE +#undef MY_INCLUSION_BUTTON_FEATURE #endif #if !defined(MY_CORE_ONLY) - #if !defined(MY_GATEWAY_FEATURE) && !defined(MY_RADIO_FEATURE) - #error No forward link or gateway feature activated. This means nowhere to send messages! Pretty pointless. - #endif +#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_SENSOR_NETWORK) +#error No forward link or gateway feature activated. This means nowhere to send messages! Pretty pointless. +#endif #endif #include "core/MyCapabilities.h" @@ -295,11 +340,13 @@ #include #if !defined(MY_CORE_ONLY) - #if defined(ARDUINO_ARCH_ESP8266) - #include "core/MyMainESP8266.cpp" - #else - #include "core/MyMainDefault.cpp" - #endif +#if defined(ARDUINO_ARCH_ESP8266) +#include "core/MyMainESP8266.cpp" +#elif defined(__linux__) +#include "core/MyMainLinux.cpp" +#else +#include "core/MyMainDefault.cpp" +#endif #endif #endif @@ -307,4 +354,5 @@ // This is used to enable disabled macros/definitions to be included in the documentation as well. #if DOXYGEN #define MY_GATEWAY_FEATURE +#define MY_LEDS_BLINKING_FEATURE //!< \deprecated use MY_DEFAULT_RX_LED_PIN, MY_DEFAULT_TX_LED_PIN and/or MY_DEFAULT_ERR_LED_PIN instead **** DEPRECATED, DO NOT USE **** #endif diff --git a/README.md b/README.md index 4dad5a3f9..ee6593b32 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,17 @@ -Arduino -======= - -MySensors Arduino Library v2.0.0 +MySensors Library v2.1.0 Please visit www.mysensors.org for more information Doxygen ------- -[master](https://ci.mysensors.org/job/MySensorsArduino/branch/master/Doxygen_HTML/index.html) [development](https://ci.mysensors.org/job/MySensorsArduino/branch/development/Doxygen_HTML/index.html) +[master](https://ci.mysensors.org/job/Verifiers/job/MySensors/branch/master/Doxygen_HTML/index.html) [development](https://ci.mysensors.org/job/Verifiers/job/MySensors/branch/development/Doxygen_HTML/index.html) CI statuses ----------- -Current build status of master branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/master/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/master/) - -Current build status of development branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/development/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensorsArduino/job/development/) - -Current build status of master branch (nightly build): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/master/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/master/) +Current build status of master branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/master/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/master/) -Current build status of development branch (nightly build): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/development/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightly/job/development/) +Current build status of development branch: [![Build Status](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/development/badge/icon)](https://ci.mysensors.org/job/Verifiers/job/MySensors/job/development/) Current build status of master branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/master/) -Current build status of development branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/) +Current build status of development branch (nightly build of Arduino IDE): [![Build Status](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/badge/icon)](https://ci.mysensors.org/job/Nightlies/job/MySensorsArduinoNightlyIDE/job/development/) \ No newline at end of file diff --git a/configure b/configure new file mode 100755 index 000000000..7fee365a8 --- /dev/null +++ b/configure @@ -0,0 +1,497 @@ +#!/bin/bash +# This version is heavily based on the work of mz-fuzzy (https://github.com/mz-fuzzy) +# adapted to work with MySensors project. +# Original work: https://github.com/TMRh20/RF24/blob/master/configure + +function help { +cat < CPU defining/optimizing flags to be used. [configure autodetected] + --extra-cflags= Extra C flags passed to C compilation. [] + --extra-cxxflags= Extra C++ flags passed to C++ compilation. [] + --extra-ldflags= Extra C flags passed to linking. [] + --c_compiler= C compiler. [arm-linux-gnueabihf-gcc][gcc] + --cxx_compiler= C++ compiler [arm-linux-gnueabihf-g++][g++] + --build-dir= Compiler directory to store object files. [build] + --bin-dir= Compiler directory to store binary files. [bin] + --arduino-lib-dir= Arduino library directory. + --no-clean Don't clean previous build artifacts. + +Installation options: + --prefix= Installation prefix path. [/usr/local] + --gateway-dir= Gateway files installation directory. [PREFIX/bin] + +MySensors options: + --my-debug=[enable|disable] Enables or disables MySensors core debugging. [enable] + --my-config-file= Config file path. [/etc/mysensors.dat] + --my-gateway=[ethernet|serial|mqtt] + Gateway type, set to none to disable gateway feature. [ethernet] + --my-node-id= Disable gateway feature and run as a node with given id. + --my-controller-url-address= + Controller or MQTT broker url. + --my-controller-ip-address= + Controller or MQTT broker ip. + --my-port= The port to keep open on gateway mode. + If gateway is set to mqtt, it sets the broker port. + --my-serial-port= Serial port. [/dev/ttyACM0] + --my-serial-baudrate= Serial baud rate. [115200] + --my-serial-is-pty Set the serial port to be a pseudo terminal. Use this if you want + to connect to a controller running on the same device. + --my-serial-pty= Symlink name for the PTY device. [/dev/ttyMySensorsGateway] + --my-serial-groupname= + Grant access to the specified system group for the serial device. + --my-mqtt-client-id= MQTT client id. + --my-mqtt-publish-topic-prefix= + MQTT publish topic prefix. + --my-mqtt-subscribe-topic-prefix= + MQTT subscribe topic prefix. + --my-transport=[none|nrf24|rs485|rfm95] + Transport type, set to none to disable transport feature. [nrf24] + --my-rf24-channel=<0-125> RF channel for the sensor net, 0-125. [76] + --my-rf24-pa-level=[RF24_PA_MAX|RF24_PA_LOW] + RF24 PA level. [RF24_PA_MAX] + --my-rf24-irq-pin= Pin number connected to nRF24L01 IRQ pin. + --my-rf24-encryption-enabled + Enables RF24 encryption. + All nodes and gateway must have this enabled, and all must be + personalized with the same AES key + --my-rx-message-buffer-size= + Buffer size for incoming messages when using rf24 interrupts. [20] + --my-rs485-serial-port= + RS485 serial port. You must provide a port. + --my-rs485-baudrate= RS485 baudrate. [9600] + --my-rs485-de-pin= Pin number connected to RS485 driver enable pin. + --my-rs485-max-msg-length= + The maximum message length used for RS485. + --my-leds-err-pin= Error LED pin. + --my-leds-rx-pin= Receive LED pin. + --my-leds-tx-pin= Transmit LED pin. + --my-leds-blinking-inverse Inverse the blinking feature. + --my-signing=[none|software] + Message signing. [none] + --my-signing-debug Enable signing related debug. + --my-signing-request-signatures + Enable signature request from nodes that in turn requested + gateway signature. + --my-signing-request-gw-signatures-from-all + Require all nodes in the network to sign messages sent to the + gateway. + +EOF +} + +function die { + echo "[ERROR] $1" + exit $2 +} + +function detect_rpi_revision { + # get PI Revision from cpuinfo + local pirev=$(eval "cat /proc/cpuinfo 2>/dev/null | grep Revision | cut -f 2 -d ':' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$$//'") + echo ${pirev} +} + +function detect_machine { + local cpu=$(eval "uname -m 2>/dev/null") + local machine=$(eval "cat /sys/firmware/devicetree/base/model 2>/dev/null") + local hardware=$(eval "grep sunxi_platform /sys/class/sunxi_info/sys_info 2>/dev/null | sed 's/^.*: \(.*\)$/\1/'") + if [ -z "$hardware" ]; then + local hardware=$(eval "grep Hardware /proc/cpuinfo 2>/dev/null | sed 's/^.*: \(.*\)$/\1/'") + fi + local soc="unknown" + local tp="unknown" + + if [ -z "$cpu" ]; then + cpu="unknown" + fi + + case $hardware in + BCM2708) + soc="BCM2835" + if [[ $machine == "Raspberry"* ]]; then + tp="RPi" + fi + ;; + BCM2709) + soc="BCM2836" + if [[ $machine == "Raspberry"* ]]; then + local rev=($(detect_rpi_revision)) + if [[ $rev == "a02082" || $rev == "a22082" ]]; then + tp="RPi3" + else + tp="Rpi2" + fi + fi + ;; + sun4i|Sun4iw1p1) + soc="A10" + ;; + sun5i|Sun4iw2p1) + soc="A13" + ;; + Sun4iw2p2) + soc="A12" + ;; + Sun4iw2p3) + soc="A10s" + ;; + sun6i|Sun8iw1p1) + soc="A31" + ;; + Sun8iw1p2) + soc="A31s" + ;; + sun7i|Sun8iw2p1) + soc="A20" + if [[ $machine == "Banana Pi"* ]]; then + tp="BananaPi" + elif [[ $machine == "Banana Pro"* ]]; then + tp="BananaPro" + fi + ;; + sun8i|Sun8iw7p1) + soc="H3" + ;; + Sun8iw3p1) + soc="A23" + ;; + Sun8iw5p1) + soc="A33" + ;; + Sun8iw6p1) + soc="A83t" + ;; + sun9i|Sun9iw1p1) + soc="A80" + ;; + Sun9iw1p2) + soc="A80t" + ;; + sun50i|Sun50iw1p1) + soc="A64" + ;; + 'Generic AM33XX'*) + soc="AM33XX" + ;; + *) + soc="unknown" + esac + echo "${soc} ${tp} ${cpu}" +} + +function gcc_cpu_flags { + local soc=$1 + case $soc in + BCM2835) + flags="-march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard" + ;; + BCM2836) + flags="-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard" + ;; + AM33XX) + flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard" + ;; + A10) + flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard" + ;; + A13) + flags="-march=armv7-a -mtune=cortex-a8 -mfpu=neon -mfloat-abi=hard" + ;; + A20) + flags="-march=armv7-a -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard" + ;; + H3) + flags="-march=armv8-a -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard" + ;; + *) + flags="" + esac + echo ${flags} +} + +# Default values +debug=enable +gateway_type=ethernet +transport_type=nrf24 +signing=none +signing_request_signatures=false + +params="SOC CFLAGS CXXFLAGS CPPFLAGS LDFLAGS PREFIX CC CXX ARDUINO_LIB_DIR BUILDDIR BINDIR GATEWAY_DIR INIT_SYSTEM" + +for opt do + if [ "$opt" = "-h" ] || [ "$opt" = "--help" ]; then + help + exit 0 + fi + optarg="${opt#*=}" + case "$opt" in + --soc=*) + SOC="$optarg" + ;; + --platform-type=*) + PT="$optarg" + ;; + --cpu-flags=*) + CPUFLAGS="$optarg" + ;; + --extra-cflags=*) + CFLAGS="$optarg" + ;; + --extra-cxxflags=*) + CXXFLAGS="$optarg" + ;; + --extra-ldflags=*) + LDFLAGS="$optarg" + ;; + --c_compiler=*) + CC="$optarg" + ;; + --cxx_compiler=*) + CXX="$optarg" + ;; + --arduino-lib-dir=*) + ARDUINO_LIB_DIR=$optarg + ;; + --build-dir=*) + BUILDDIR="$optarg" + ;; + --bin-dir=*) + BINDIR="$optarg" + ;; + --no-clean*) + NO_CLEAN="1" + ;; + --prefix=*) + PREFIX="$optarg" + ;; + --exec-prefix=*) + PREFIX="$optarg" + ;; + --no_init_system*) + NO_INIT="1" + ;; + --gateway-dir=*) + GATEWAY_DIR="$optarg" + ;; + --my-debug=*) + debug=${optarg} + ;; + --my-gateway=*) + gateway_type=${optarg} + ;; + --my-node-id=*) + gateway_type="none"; + CPPFLAGS="-DMY_NODE_ID=${optarg} $CPPFLAGS" + ;; + --my-config-file=*) + CPPFLAGS="-DMY_LINUX_CONFIG_FILE=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-transport=*) + transport_type=${optarg} + ;; + --my-radio=*) + echo "Warning: --my-radio is deprecated, please use --my-transport" + transport_type=${optarg} + ;; + --my-serial-port=*) + CPPFLAGS="-DMY_LINUX_SERIAL_PORT=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-serial-baudrate=*) + CPPFLAGS="-DMY_BAUD_RATE=${optarg} $CPPFLAGS" + ;; + --my-serial-is-pty*) + CPPFLAGS="-DMY_IS_SERIAL_PTY $CPPFLAGS" + ;; + --my-serial-pty=*) + CPPFLAGS="-DMY_LINUX_SERIAL_PTY=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-serial-groupname=*) + CPPFLAGS="-DMY_LINUX_SERIAL_GROUPNAME=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-rf24-channel=*) + CPPFLAGS="-DMY_RF24_CHANNEL=${optarg} $CPPFLAGS" + ;; + --my-rf24-pa-level=*) + CPPFLAGS="-DMY_RF24_PA_LEVEL=${optarg} $CPPFLAGS" + ;; + --my-controller-url-address=*) + CPPFLAGS="-DMY_CONTROLLER_URL_ADDRESS=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-controller-ip-address=*) + controller_ip=`echo ${optarg//./,}` + CPPFLAGS="-DMY_CONTROLLER_IP_ADDRESS=${controller_ip} $CPPFLAGS" + ;; + --my-port=*) + CPPFLAGS="-DMY_PORT=${optarg} $CPPFLAGS" + ;; + --my-mqtt-client-id=*) + CPPFLAGS="-DMY_MQTT_CLIENT_ID=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-mqtt-publish-topic-prefix=*) + CPPFLAGS="-DMY_MQTT_PUBLISH_TOPIC_PREFIX=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-mqtt-subscribe-topic-prefix=*) + CPPFLAGS="-DMY_MQTT_SUBSCRIBE_TOPIC_PREFIX=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-rf24-irq-pin=*) + CPPFLAGS="-DMY_RX_MESSAGE_BUFFER_FEATURE -DMY_RF24_IRQ_PIN=${optarg} $CPPFLAGS" + ;; + --my-rf24-encryption-enabled*) + CPPFLAGS="-DMY_RF24_ENABLE_ENCRYPTION $CPPFLAGS" + ;; + --my-rx-message-buffer-size=*) + CPPFLAGS="-DMY_RX_MESSAGE_BUFFER_SIZE=${optarg} $CPPFLAGS" + ;; + --my-rs485-serial-port=*) + CPPFLAGS="-DMY_RS485_HWSERIAL=\\\"${optarg}\\\" $CPPFLAGS" + ;; + --my-rs485-baudrate=*) + CPPFLAGS="-DMY_RS485_BAUD_RATE=${optarg} $CPPFLAGS" + ;; + --my-rs485-de-pin=*) + CPPFLAGS="-DMY_RS485_DE_PIN=${optarg} $CPPFLAGS" + ;; + --my-rs485-max-msg-length=*) + CPPFLAGS="-DMY_RS485_MAX_MESSAGE_LENGTH=${optarg} $CPPFLAGS" + ;; + --my-leds-err-pin=*) + CPPFLAGS="-DMY_DEFAULT_ERR_LED_PIN=${optarg} $CPPFLAGS" + ;; + --my-leds-rx-pin=*) + CPPFLAGS="-DMY_DEFAULT_RX_LED_PIN=${optarg} $CPPFLAGS" + ;; + --my-leds-tx-pin=*) + CPPFLAGS="-DMY_DEFAULT_TX_LED_PIN=${optarg} $CPPFLAGS" + ;; + --my-leds-blinking-inverse*) + CPPFLAGS="-DMY_WITH_LEDS_BLINKING_INVERSE $CPPFLAGS" + ;; + --my-signing=*) + signing=${optarg} + ;; + --my-signing-debug*) + CPPFLAGS="-DMY_DEBUG_VERBOSE_SIGNING $CPPFLAGS" + ;; + --my-signing-request-signatures*) + signing_request_signatures=true + ;; + --my-signing-request-gw-signatures-from-all*) + signing_request_signatures=true + CPPFLAGS="-DMY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL $CPPFLAGS" + ;; + *) + echo "[WARNING] Unknown option detected:$opt, ignored" + ;; + esac +done + +PREFIX=${PREFIX:-/usr/local} +BUILDDIR=${BUILDDIR:-build} +BINDIR=${BINDIR:-bin} +GATEWAY_DIR=${GATEWAY_DIR:-${PREFIX}/bin} +CC=${CC:-gcc} +CXX=${CXX:-g++} + +if [ -z "${SOC}" ]; then + echo "[SECTION] Detecting target machine." + info=($(detect_machine)) + SOC=${info[0]} + TYPE=${info[1]} + CPU=${info[2]} + echo "[OK] machine detected: SoC=${SOC}, Type=${TYPE}, CPU=${CPU}." +fi + +if [ -z "${CPUFLAGS}" ]; then + CPUFLAGS=$(gcc_cpu_flags $SOC) +fi + +if [[ $SOC == "BCM2835" || $SOC == "BCM2836" ]]; then + CPPFLAGS="-DLINUX_ARCH_RASPBERRYPI $CPPFLAGS" +fi + +if [[ ${debug} == "enable" ]]; then + CPPFLAGS="-DMY_DEBUG $CPPFLAGS" +fi + +if [[ ${gateway_type} == "none" ]]; then + # Node mode selected + : +elif [[ ${gateway_type} == "ethernet" ]]; then + CPPFLAGS="-DMY_GATEWAY_LINUX $CPPFLAGS" +elif [[ ${gateway_type} == "serial" ]]; then + CPPFLAGS="-DMY_GATEWAY_SERIAL $CPPFLAGS" +elif [[ ${gateway_type} == "mqtt" ]]; then + CPPFLAGS="-DMY_GATEWAY_LINUX -DMY_GATEWAY_MQTT_CLIENT $CPPFLAGS" +else + echo "Invalid gateway type." + echo "Aborting." + exit 1 +fi + +if [[ ${transport_type} == "none" ]]; then + # Transport disabled + : +elif [[ ${transport_type} == "nrf24" ]]; then + CPPFLAGS="-DMY_RADIO_NRF24 $CPPFLAGS" +elif [[ ${transport_type} == "rs485" ]]; then + CPPFLAGS="-DMY_RS485 $CPPFLAGS" +elif [[ ${transport_type} == "rfm95" ]]; then + CPPFLAGS="-DMY_RADIO_RFM95 $CPPFLAGS" +else + echo "Invalid transport type." + echo "Aborting." + exit 1 +fi + +if [[ ${signing} == "none" ]]; then + # Signing disabled + : +elif [[ ${signing} == "software" ]]; then + CPPFLAGS="-DMY_SIGNING_SOFT $CPPFLAGS" + if [[ ${signing_request_signatures} == true ]]; then + CPPFLAGS="-DMY_SIGNING_REQUEST_SIGNATURES $CPPFLAGS" + fi +else + echo "Invalid message signing option." + echo "Aborting." + exit 1 +fi + +LDFLAGS="-pthread $LDFLAGS" +CPPFLAGS="$CPUFLAGS $CPPFLAGS" + +if [ "${NO_INIT}" ]; then + echo "[OK] no init system chosen." +elif [ -x /usr/bin/systemctl ] || [ -x /bin/systemctl ]; then + INIT_SYSTEM=systemd + echo "[OK] init system detected: systemd" +elif [ -f /etc/init.d/cron ] && [ ! -h /etc/init.d/cron ]; then + INIT_SYSTEM=sysvinit + echo "[OK] init system detected: sysvinit" +else + echo "[FAILED] unknown init system" +fi + +echo "[SECTION] Saving configuration." +echo -n "" > Makefile.inc +for param in ${params}; do + if [[ ${!param} ]]; then + echo "${param}=${!param}" >> Makefile.inc + fi +done + +if [ -z "${NO_CLEAN}" ]; then + echo "[SECTION] Cleaning previous builds." + make clean >/dev/null +fi + + +echo "[OK] Finished." diff --git a/core/MyCapabilities.h b/core/MyCapabilities.h index 2ec468d79..3b45fecac 100644 --- a/core/MyCapabilities.h +++ b/core/MyCapabilities.h @@ -22,55 +22,63 @@ #define MyCapabilities_h #if defined(MY_DISABLE_REMOTE_RESET) - #define MY_CAP_RESET "N" +#define MY_CAP_RESET "N" #else - #define MY_CAP_RESET "R" +#define MY_CAP_RESET "R" #endif #if defined(MY_OTA_FIRMWARE_FEATURE) - #define MY_CAP_OTA_FW "O" +#define MY_CAP_OTA_FW "O" #else - #define MY_CAP_OTA_FW "N" +#define MY_CAP_OTA_FW "N" #endif #if defined(MY_RADIO_NRF24) - #define MY_CAP_RADIO "N" +#define MY_CAP_RADIO "N" #elif defined(MY_RADIO_RFM69) - #define MY_CAP_RADIO "R" +#define MY_CAP_RADIO "R" +#elif defined(MY_RADIO_RFM95) +#define MY_CAP_RADIO "L" #elif defined(MY_RS485) - #define MY_CAP_RADIO "S" +#define MY_CAP_RADIO "S" #else - #define MY_CAP_RADIO "-" +#define MY_CAP_RADIO "-" #endif #if defined(MY_GATEWAY_FEATURE) - #define MY_CAP_TYPE "G" +#define MY_CAP_TYPE "G" #elif defined(MY_REPEATER_FEATURE) - #define MY_CAP_TYPE "R" +#define MY_CAP_TYPE "R" #else - #define MY_CAP_TYPE "N" +#define MY_CAP_TYPE "N" #endif #if defined(ARDUINO_ARCH_SAMD) - #define MY_CAP_ARCH "S" +#define MY_CAP_ARCH "S" #elif defined(ARDUINO_ARCH_ESP8266) - #define MY_CAP_ARCH "E" +#define MY_CAP_ARCH "E" #elif defined(ARDUINO_ARCH_AVR) - #define MY_CAP_ARCH "A" +#define MY_CAP_ARCH "A" #else - #define MY_CAP_ARCH "-" +#define MY_CAP_ARCH "-" #endif #if defined(MY_SIGNING_ATSHA204) - #define MY_CAP_SIGN "A" +#define MY_CAP_SIGN "A" #elif defined(MY_SIGNING_SOFT) - #define MY_CAP_SIGN "S" +#define MY_CAP_SIGN "S" #else - #define MY_CAP_SIGN "-" +#define MY_CAP_SIGN "-" +#endif + +#if defined(MY_RX_MESSAGE_BUFFER_FEATURE) +#define MY_CAP_RXBUF "Q" +#else +#define MY_CAP_RXBUF "-" #endif -#define MY_CAPABILITIES MY_CAP_RESET MY_CAP_RADIO MY_CAP_OTA_FW MY_CAP_TYPE MY_CAP_ARCH MY_CAP_SIGN +#define MY_CAPABILITIES MY_CAP_RESET MY_CAP_RADIO MY_CAP_OTA_FW MY_CAP_TYPE MY_CAP_ARCH MY_CAP_SIGN MY_CAP_RXBUF -#endif /* MyGatewayTransportEthernet_h */ +#endif /* MyCapabilities_h */ diff --git a/core/MyEepromAddresses.h b/core/MyEepromAddresses.h index 3c94be3bf..11502332b 100644 --- a/core/MyEepromAddresses.h +++ b/core/MyEepromAddresses.h @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad - * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2013-2016 Sensnology AB * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -17,28 +17,73 @@ * version 2 as published by the Free Software Foundation. */ +/** +* @file MyEepromAddresses.h +* @brief Eeprom addresses for MySensors library data +* +* @defgroup MyEepromAddressesgrp MyEepromAddresses +* @ingroup internals +* @{ +* +*/ + + #ifndef MyEepromAddresses_h #define MyEepromAddresses_h -// EEPROM start address for mysensors library data + +// EEPROM variable sizes, in bytes +#define SIZE_NODE_ID (1) //!< Size node ID +#define SIZE_PARENT_NODE_ID (1) //!< Size parent node ID +#define SIZE_DISTANCE (1) //!< Size GW distance +#define SIZE_ROUTES (256) //!< Size routing table +#define SIZE_CONTROLLER_CONFIG (24) //!< Size controller config +#define SIZE_FIRMWARE_TYPE (2) //!< Size firmware type +#define SIZE_FIRMWARE_VERSION (2) //!< Size firmware version +#define SIZE_FIRMWARE_BLOCKS (2) //!< Size firmware blocks +#define SIZE_FIRMWARE_CRC (2) //!< Size firmware CRC +#define SIZE_SIGNING_REQUIREMENT_TABLE (32) //!< Size signing requirement table +#define SIZE_WHITELIST_REQUIREMENT_TABLE (32) //!< Size whitelist requirement table +#define SIZE_SIGNING_SOFT_HMAC_KEY (32) //!< Size soft signing HMAC key +#define SIZE_SIGNING_SOFT_SERIAL (9) //!< Size soft signing serial +#define SIZE_RF_ENCRYPTION_AES_KEY (16) //!< Size RF AES encryption key +#define SIZE_NODE_LOCK_COUNTER (1) //!< Size node lock counter + + +/** @brief EEPROM start address */ #define EEPROM_START 0 -// EEPROM location of node id +/** @brief Address node ID */ #define EEPROM_NODE_ID_ADDRESS EEPROM_START -// EEPROM location of parent id -#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_START+1) -// EEPROM location of distance to gateway -#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS+1) -#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS+1) // Where to start storing routing information in EEPROM. Will allocate 256 bytes. -#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS+256) // Location of controller sent configuration (we allow one payload of config data from controller) -#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS+24) -#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS+2) -#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS+2) -#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS+2) -#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS+2) -#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS+32) -#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS+32) // This is set with SecurityPersonalizer.ino -#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS+32) // This is set with SecurityPersonalizer.ino -#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS+9) // This is set with SecurityPersonalizer.ino -#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS+16) -#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER+1) // First free address for sketch static configuration - -#endif +/** @brief Address parent node ID */ +#define EEPROM_PARENT_NODE_ID_ADDRESS (EEPROM_NODE_ID_ADDRESS + SIZE_NODE_ID) +/** @brief Address distance to GW */ +#define EEPROM_DISTANCE_ADDRESS (EEPROM_PARENT_NODE_ID_ADDRESS + SIZE_PARENT_NODE_ID) +/** @brief Address routing table */ +#define EEPROM_ROUTES_ADDRESS (EEPROM_DISTANCE_ADDRESS + SIZE_DISTANCE) +/** @brief Address configuration bytes sent by controller */ +#define EEPROM_CONTROLLER_CONFIG_ADDRESS (EEPROM_ROUTES_ADDRESS + SIZE_ROUTES) +/** @brief Address firmware type */ +#define EEPROM_FIRMWARE_TYPE_ADDRESS (EEPROM_CONTROLLER_CONFIG_ADDRESS + SIZE_CONTROLLER_CONFIG) +/** @brief Address firmware version */ +#define EEPROM_FIRMWARE_VERSION_ADDRESS (EEPROM_FIRMWARE_TYPE_ADDRESS + SIZE_FIRMWARE_TYPE) +/** @brief Address firmware blocks */ +#define EEPROM_FIRMWARE_BLOCKS_ADDRESS (EEPROM_FIRMWARE_VERSION_ADDRESS + SIZE_FIRMWARE_VERSION) +/** @brief Address firmware CRC */ +#define EEPROM_FIRMWARE_CRC_ADDRESS (EEPROM_FIRMWARE_BLOCKS_ADDRESS + SIZE_FIRMWARE_BLOCKS) +/** @brief Address signing requirement table */ +#define EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS (EEPROM_FIRMWARE_CRC_ADDRESS + SIZE_FIRMWARE_CRC) +/** @brief Address whitelist requirement table */ +#define EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS (EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS + SIZE_SIGNING_REQUIREMENT_TABLE) +/** @brief Address soft signing HMAC key. This is set with @ref SecurityPersonalizer.ino */ +#define EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS (EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS + SIZE_WHITELIST_REQUIREMENT_TABLE) +/** @brief Address soft signing serial key. This is set with @ref SecurityPersonalizer.ino */ +#define EEPROM_SIGNING_SOFT_SERIAL_ADDRESS (EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS + SIZE_SIGNING_SOFT_HMAC_KEY) +/** @brief Address RF AES encryption key. This is set with @ref SecurityPersonalizer.ino */ +#define EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS (EEPROM_SIGNING_SOFT_SERIAL_ADDRESS + SIZE_SIGNING_SOFT_SERIAL) +/** @brief Address node lock couner. This is set with @ref SecurityPersonalizer.ino */ +#define EEPROM_NODE_LOCK_COUNTER (EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS + SIZE_RF_ENCRYPTION_AES_KEY) +/** @brief First free address for sketch static configuration */ +#define EEPROM_LOCAL_CONFIG_ADDRESS (EEPROM_NODE_LOCK_COUNTER + SIZE_NODE_LOCK_COUNTER) + +#endif // MyEepromAddresses_h + +/** @}*/ diff --git a/core/MyGatewayTransport.cpp b/core/MyGatewayTransport.cpp index a7838aa82..b37bb6b5b 100644 --- a/core/MyGatewayTransport.cpp +++ b/core/MyGatewayTransport.cpp @@ -20,9 +20,13 @@ #include "MyGatewayTransport.h" extern bool transportSendRoute(MyMessage &message); + +// global variables extern MyMessage _msg; +extern MyMessage _msgTmp; -inline void gatewayTransportProcess() { +inline void gatewayTransportProcess(void) +{ if (gatewayTransportAvailable()) { _msg = gatewayTransportReceive(); if (_msg.destination == GATEWAY_ADDRESS) { @@ -31,21 +35,22 @@ inline void gatewayTransportProcess() { if (mGetRequestAck(_msg)) { // Copy message _msgTmp = _msg; - mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop) + mSetRequestAck(_msgTmp, + false); // Reply without ack flag (otherwise we would end up in an eternal loop) mSetAck(_msgTmp, true); - _msgTmp.sender = _nc.nodeId; + _msgTmp.sender = getNodeId(); _msgTmp.destination = _msg.sender; gatewayTransportSend(_msgTmp); } if (mGetCommand(_msg) == C_INTERNAL) { if (_msg.type == I_VERSION) { // Request for version. Create the response - gatewayTransportSend(buildGw(_msg, I_VERSION).set(MYSENSORS_LIBRARY_VERSION)); - #ifdef MY_INCLUSION_MODE_FEATURE + gatewayTransportSend(buildGw(_msgTmp, I_VERSION).set(MYSENSORS_LIBRARY_VERSION)); +#ifdef MY_INCLUSION_MODE_FEATURE } else if (_msg.type == I_INCLUSION_MODE) { // Request to change inclusion mode inclusionModeSet(atoi(_msg.data) == 1); - #endif +#endif } else { _processInternalMessages(); } @@ -56,9 +61,9 @@ inline void gatewayTransportProcess() { } } } else { - #if defined(MY_RADIO_FEATURE) - transportSendRoute(_msg); - #endif +#if defined(MY_SENSOR_NETWORK) + transportSendRoute(_msg); +#endif } } } diff --git a/core/MyGatewayTransport.h b/core/MyGatewayTransport.h index d34bd31ac..4342f6b58 100644 --- a/core/MyGatewayTransport.h +++ b/core/MyGatewayTransport.h @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Tomas Hozza - * Copyright (C) 2015 Tomas Hozza + * Copyright (C) 2015 Tomas Hozza * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -21,10 +21,13 @@ #define MyGatewayTransport_h #include "MyProtocol.h" +#include "MySensorsCore.h" + +#define MSG_GW_STARTUP_COMPLETE "Gateway startup complete." // Common gateway functions -void gatewayTransportProcess(); +void gatewayTransportProcess(void); // Gateway "interface" functions @@ -32,7 +35,7 @@ void gatewayTransportProcess(); /** * initialize the driver */ -bool gatewayTransportInit(); +bool gatewayTransportInit(void); /** * Send message to controller @@ -42,11 +45,11 @@ bool gatewayTransportSend(MyMessage &message); /* * Check if a new message is available from controller */ -bool gatewayTransportAvailable(); +bool gatewayTransportAvailable(void); /* * Pick up last message received from controller */ -MyMessage& gatewayTransportReceive(); +MyMessage& gatewayTransportReceive(void); #endif /* MyGatewayTransportEthernet_h */ diff --git a/core/MyGatewayTransportEthernet.cpp b/core/MyGatewayTransportEthernet.cpp index 2db3b5ac8..693939d7f 100644 --- a/core/MyGatewayTransportEthernet.cpp +++ b/core/MyGatewayTransportEthernet.cpp @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Tomas Hozza - * Copyright (C) 2015 Tomas Hozza + * Copyright (C) 2015 Tomas Hozza * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -19,353 +19,406 @@ #include "MyGatewayTransport.h" +// global variables +extern MyMessage _msgTmp; + #if defined(MY_CONTROLLER_IP_ADDRESS) - IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS); +IPAddress _ethernetControllerIP(MY_CONTROLLER_IP_ADDRESS); #endif #if defined(MY_IP_ADDRESS) - IPAddress _ethernetGatewayIP(MY_IP_ADDRESS); +IPAddress _ethernetGatewayIP(MY_IP_ADDRESS); #endif -byte _ethernetGatewayMAC[] = { MY_MAC_ADDRESS }; +uint8_t _ethernetGatewayMAC[] = { MY_MAC_ADDRESS }; uint16_t _ethernetGatewayPort = MY_PORT; MyMessage _ethernetMsg; #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) -// gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete.")); - -typedef struct -{ - char string[MY_GATEWAY_MAX_RECEIVE_LENGTH]; - uint8_t idx; +typedef struct { + char string[MY_GATEWAY_MAX_RECEIVE_LENGTH]; + uint8_t idx; } inputBuffer; #if defined(MY_GATEWAY_ESP8266) - // Some re-defines to make code more readable below - #define EthernetServer WiFiServer - #define EthernetClient WiFiClient - #define EthernetUDP WiFiUDP +// Some re-defines to make code more readable below +#define EthernetServer WiFiServer +#define EthernetClient WiFiClient +#define EthernetUDP WiFiUDP - #if defined(MY_IP_ADDRESS) - IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS); - IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS); - #endif - static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS]; +#if defined(MY_IP_ADDRESS) +IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS); +IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS); +#endif #endif #if defined(MY_USE_UDP) - EthernetUDP _ethernetServer; +EthernetUDP _ethernetServer; +#elif defined(MY_GATEWAY_LINUX) +EthernetServer _ethernetServer(_ethernetGatewayPort, MY_GATEWAY_MAX_CLIENTS); +#elif defined(MY_GATEWAY_CLIENT_MODE) +// Nothing to do here #else - EthernetServer _ethernetServer(_ethernetGatewayPort); +EthernetServer _ethernetServer(_ethernetGatewayPort); #endif - -#if defined(MY_GATEWAY_ESP8266) - static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS]; - static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS]; +#if defined(MY_GATEWAY_CLIENT_MODE) +static EthernetClient client = EthernetClient(); +static inputBuffer inputString; +#elif defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) +static EthernetClient clients[MY_GATEWAY_MAX_CLIENTS]; +static bool clientsConnected[MY_GATEWAY_MAX_CLIENTS]; +static inputBuffer inputString[MY_GATEWAY_MAX_CLIENTS]; #else - static EthernetClient client = EthernetClient(); - static inputBuffer inputString; +static EthernetClient client = EthernetClient(); +static inputBuffer inputString; #endif - #ifndef MY_IP_ADDRESS - void gatewayTransportRenewIP(); +void gatewayTransportRenewIP(); #endif // On W5100 boards with SPI_EN exposed we can use the real SPI bus together with radio // (if we enable it during usage) #ifdef MY_W5100_SPI_EN - void _w5100_spi_en(bool enable) - { - if (enable) - { - // Pull up pin - pinMode(MY_W5100_SPI_EN, INPUT); - digitalWrite(MY_W5100_SPI_EN, HIGH); - } - else - { - // Ground pin - pinMode(MY_W5100_SPI_EN, OUTPUT); - digitalWrite(MY_W5100_SPI_EN, LOW); - } +void _w5100_spi_en(bool enable) +{ + if (enable) { + // Pull up pin + hwPinMode(MY_W5100_SPI_EN, INPUT); + hwDigitalWrite(MY_W5100_SPI_EN, HIGH); + } else { + // Ground pin + hwPinMode(MY_W5100_SPI_EN, OUTPUT); + hwDigitalWrite(MY_W5100_SPI_EN, LOW); } +} #else - #define _w5100_spi_en(x) +#define _w5100_spi_en(x) #endif - - -bool gatewayTransportInit() { +bool gatewayTransportInit(void) +{ _w5100_spi_en(true); - #if defined(MY_GATEWAY_ESP8266) - #if defined(MY_ESP8266_SSID) - // Turn off access point - WiFi.mode (WIFI_STA); - #if defined(MY_ESP8266_HOSTNAME) - WiFi.hostname(MY_ESP8266_HOSTNAME); - #endif - (void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD); - #ifdef MY_IP_ADDRESS - WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp); - #endif - while (WiFi.status() != WL_CONNECTED) - { - delay(500); - MY_SERIALDEVICE.print("."); - yield(); - } - MY_SERIALDEVICE.print(F("IP: ")); - MY_SERIALDEVICE.println(WiFi.localIP()); - #endif - - #else - #ifdef MY_IP_ADDRESS - Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP); - #else - // Get IP address from DHCP - if (!Ethernet.begin(_ethernetGatewayMAC)) { - MY_SERIALDEVICE.print("DHCP FAILURE..."); - _w5100_spi_en(false); - return false; - } - #endif - MY_SERIALDEVICE.print(F("IP: ")); - MY_SERIALDEVICE.println(Ethernet.localIP()); - // give the Ethernet interface a second to initialize - delay(1000); - #endif - - #ifdef MY_USE_UDP - _ethernetServer.begin(_ethernetGatewayPort); - #else - // we have to use pointers due to the constructor of EthernetServer - _ethernetServer.begin(); - #endif /* USE_UDP */ +#if defined(MY_GATEWAY_ESP8266) +#if defined(MY_ESP8266_SSID) + // Turn off access point + WiFi.mode (WIFI_STA); +#if defined(MY_ESP8266_HOSTNAME) + WiFi.hostname(MY_ESP8266_HOSTNAME); +#endif +#ifdef MY_IP_ADDRESS + WiFi.config(_ethernetGatewayIP, _gatewayIp, _subnetIp); +#endif + (void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD); + while (WiFi.status() != WL_CONNECTED) { + wait(500); + MY_SERIALDEVICE.print(F(".")); + } + MY_SERIALDEVICE.print(F("IP: ")); + MY_SERIALDEVICE.println(WiFi.localIP()); +#endif +#elif defined(MY_GATEWAY_LINUX) + // Nothing to do here +#else +#ifdef MY_IP_ADDRESS + Ethernet.begin(_ethernetGatewayMAC, _ethernetGatewayIP); +#else + // Get IP address from DHCP + if (!Ethernet.begin(_ethernetGatewayMAC)) { + MY_SERIALDEVICE.print(F("DHCP FAILURE...")); + _w5100_spi_en(false); + return false; + } +#endif + MY_SERIALDEVICE.print(F("IP: ")); + MY_SERIALDEVICE.println(Ethernet.localIP()); + // give the Ethernet interface a second to initialize + delay(1000); +#endif /* MY_GATEWAY_ESP8266 */ + +#ifdef MY_USE_UDP + _ethernetServer.begin(_ethernetGatewayPort); +#elif defined(MY_GATEWAY_CLIENT_MODE) +#if defined(MY_CONTROLLER_URL_ADDRESS) + if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { +#else + if (client.connect(_ethernetControllerIP, MY_PORT)) { +#endif + debug(PSTR("Eth: connect\n")); + _w5100_spi_en(false); + gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE)); + _w5100_spi_en(true); + presentNode(); + } else { + client.stop(); + debug(PSTR("Eth: Failed to connect\n")); + } +#else +#if defined(MY_GATEWAY_LINUX) && defined(MY_IP_ADDRESS) + _ethernetServer.begin(_ethernetGatewayIP); +#else + // we have to use pointers due to the constructor of EthernetServer + _ethernetServer.begin(); +#endif +#endif /* USE_UDP */ _w5100_spi_en(false); return true; } bool gatewayTransportSend(MyMessage &message) { - bool ret = true; + int nbytes = 0; char *_ethernetMsg = protocolFormat(message); - setIndication(INDICATION_GW_TX); + setIndication(INDICATION_GW_TX); _w5100_spi_en(true); - #if defined(MY_CONTROLLER_IP_ADDRESS) - #if defined(MY_USE_UDP) - _ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT); - _ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg)); - // returns 1 if the packet was sent successfully - ret = _ethernetServer.endPacket(); - #else - EthernetClient client; - #if defined(MY_CONTROLLER_URL_ADDRESS) - if (client.connected() || client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { - #else - if (client.connected() || client.connect(_ethernetControllerIP, MY_PORT)) { - #endif - client.write(_ethernetMsg, strlen(_ethernetMsg)); - } - else { - // connecting to the server failed! - ret = false; - } - #endif - #else - // Send message to connected clients - #if defined(MY_GATEWAY_ESP8266) - for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) - { - if (clients[i] && clients[i].connected()) - { - clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg)); - } - } - #else - _ethernetServer.write(_ethernetMsg); - #endif - #endif +#if defined(MY_GATEWAY_CLIENT_MODE) +#if defined(MY_USE_UDP) +#if defined(MY_CONTROLLER_URL_ADDRESS) + _ethernetServer.beginPacket(MY_CONTROLLER_URL_ADDRESS, MY_PORT); +#else + _ethernetServer.beginPacket(_ethernetControllerIP, MY_PORT); +#endif + _ethernetServer.write(_ethernetMsg, strlen(_ethernetMsg)); + // returns 1 if the packet was sent successfully + nbytes = _ethernetServer.endPacket(); +#else + if (!client.connected()) { + client.stop(); +#if defined(MY_CONTROLLER_URL_ADDRESS) + if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { +#else + if (client.connect(_ethernetControllerIP, MY_PORT)) { +#endif + debug(PSTR("Eth: connect\n")); + _w5100_spi_en(false); + gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE)); + _w5100_spi_en(true); + presentNode(); + } else { + // connecting to the server failed! + debug(PSTR("Eth: Failed to connect\n")); + _w5100_spi_en(false); + return false; + } + } + nbytes = client.write(_ethernetMsg, strlen(_ethernetMsg)); +#endif +#else + // Send message to connected clients +#if defined(MY_GATEWAY_ESP8266) + for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) { + if (clients[i] && clients[i].connected()) { + nbytes += clients[i].write((uint8_t*)_ethernetMsg, strlen(_ethernetMsg)); + } + } +#else + nbytes = _ethernetServer.write(_ethernetMsg); +#endif +#endif /* MY_GATEWAY_CLIENT_MODE */ _w5100_spi_en(false); - return ret; - + return (nbytes > 0); } - -#if defined(MY_GATEWAY_ESP8266) - bool _readFromClient(uint8_t i) { - while (clients[i].connected() && clients[i].available()) { - char inChar = clients[i].read(); - if (inputString[i].idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) { - // if newline then command is complete - if (inChar == '\n' || inChar == '\r') { - // Add string terminator and prepare for the next message - inputString[i].string[inputString[i].idx] = 0; - debug(PSTR("Client %d: %s\n"), i, inputString[i].string); - inputString[i].idx = 0; - if (protocolParse(_ethernetMsg, inputString[i].string)) { - return true; - } - - } else { - // add it to the inputString: - inputString[i].string[inputString[i].idx++] = inChar; +#if (defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX)) && !defined(MY_GATEWAY_CLIENT_MODE) +bool _readFromClient(uint8_t i) +{ + while (clients[i].connected() && clients[i].available()) { + const char inChar = clients[i].read(); + if (inputString[i].idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) { + // if newline then command is complete + if (inChar == '\n' || inChar == '\r') { + // Add string terminator and prepare for the next message + inputString[i].string[inputString[i].idx] = 0; + debug(PSTR("Client %d: %s\n"), i, inputString[i].string); + inputString[i].idx = 0; + if (protocolParse(_ethernetMsg, inputString[i].string)) { + return true; } + } else { - // Incoming message too long. Throw away - debug(PSTR("Client %d: Message too long\n"), i); - inputString[i].idx = 0; - // Finished with this client's message. Next loop() we'll see if there's more to read. - break; + // add it to the inputString: + inputString[i].string[inputString[i].idx++] = inChar; } + } else { + // Incoming message too long. Throw away + debug(PSTR("Client %d: Message too long\n"), i); + inputString[i].idx = 0; + // Finished with this client's message. Next loop() we'll see if there's more to read. + break; } - return false; } + return false; +} #else - bool _readFromClient() { - while (client.connected() && client.available()) { - char inChar = client.read(); - if (inputString.idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) { - // if newline then command is complete - if (inChar == '\n' || inChar == '\r') { - // Add string terminator and prepare for the next message - inputString.string[inputString.idx] = 0; - debug(PSTR("Eth: %s\n"), inputString.string); - inputString.idx = 0; - if (protocolParse(_ethernetMsg, inputString.string)) { - return true; - } - - } else { - // add it to the inputString: - inputString.string[inputString.idx++] = inChar; +bool _readFromClient(void) +{ + while (client.connected() && client.available()) { + const char inChar = client.read(); + if (inputString.idx < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) { + // if newline then command is complete + if (inChar == '\n' || inChar == '\r') { + // Add string terminator and prepare for the next message + inputString.string[inputString.idx] = 0; + debug(PSTR("Eth: %s\n"), inputString.string); + inputString.idx = 0; + if (protocolParse(_ethernetMsg, inputString.string)) { + return true; } + } else { - // Incoming message too long. Throw away - debug(PSTR("Eth: Message too long\n")); - inputString.idx = 0; - // Finished with this client's message. Next loop() we'll see if there's more to read. - break; + // add it to the inputString: + inputString.string[inputString.idx++] = inChar; } + } else { + // Incoming message too long. Throw away + debug(PSTR("Eth: Message too long\n")); + inputString.idx = 0; + // Finished with this client's message. Next loop() we'll see if there's more to read. + break; } - return false; } + return false; +} #endif -bool gatewayTransportAvailable() +bool gatewayTransportAvailable(void) { _w5100_spi_en(true); - #if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100) - // renew IP address using DHCP - gatewayTransportRenewIP(); - #endif - - #ifdef MY_USE_UDP +#if !defined(MY_IP_ADDRESS) && defined(MY_GATEWAY_W5100) + // renew IP address using DHCP + gatewayTransportRenewIP(); +#endif - int packet_size = _ethernetServer.parsePacket(); +#ifdef MY_USE_UDP + int packet_size = _ethernetServer.parsePacket(); - if (packet_size) { - //debug(PSTR("UDP packet available. Size:%d\n"), packet_size); - setIndication(INDICATION_GW_RX); - #if defined(MY_GATEWAY_ESP8266) - _ethernetServer.read(inputString[0].string, MY_GATEWAY_MAX_RECEIVE_LENGTH); - inputString[0].string[packet_size] = 0; - debug(PSTR("UDP packet received: %s\n"), inputString[0].string); - return protocolParse(_ethernetMsg, inputString[0].string); - #else - _ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH); - inputString.string[packet_size] = 0; - debug(PSTR("UDP packet received: %s\n"), inputString.string); - _w5100_spi_en(false); - return protocolParse(_ethernetMsg, inputString.string); - #endif + if (packet_size) { + //debug(PSTR("UDP packet available. Size:%d\n"), packet_size); +#if defined(MY_GATEWAY_ESP8266) + _ethernetServer.read(inputString[0].string, MY_GATEWAY_MAX_RECEIVE_LENGTH); + inputString[0].string[packet_size] = 0; + debug(PSTR("UDP packet received: %s\n"), inputString[0].string); + const bool ok = protocolParse(_ethernetMsg, inputString[0].string); +#else + _ethernetServer.read(inputString.string, MY_GATEWAY_MAX_RECEIVE_LENGTH); + inputString.string[packet_size] = 0; + debug(PSTR("UDP packet received: %s\n"), inputString.string); + _w5100_spi_en(false); + const bool ok = protocolParse(_ethernetMsg, inputString.string); +#endif + if (ok) { + setIndication(INDICATION_GW_RX); } - #else - #if defined(MY_GATEWAY_ESP8266) - // ESP8266: Go over list of clients and stop any that are no longer connected. - // If the server has a new client connection it will be assigned to a free slot. - bool allSlotsOccupied = true; - for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) { - if (!clients[i].connected()) { - if (clientsConnected[i]) { - debug(PSTR("Client %d disconnected\n"), i); - clients[i].stop(); - } - //check if there are any new clients - if (_ethernetServer.hasClient()) { - clients[i] = _ethernetServer.available(); - inputString[i].idx = 0; - debug(PSTR("Client %d connected\n"), i); - gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete.")); - if (presentation) - presentation(); - } - } - bool connected = clients[i].connected(); - clientsConnected[i] = connected; - allSlotsOccupied &= connected; - } - if (allSlotsOccupied && _ethernetServer.hasClient()) { - //no free/disconnected spot so reject - debug(PSTR("No free slot available\n")); - EthernetClient c = _ethernetServer.available(); - c.stop(); - } - // Loop over clients connect and read available data - for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) { - if (_readFromClient(i)) { - setIndication(INDICATION_GW_RX); - _w5100_spi_en(false); - return true; - } + return ok; + } +#elif defined(MY_GATEWAY_CLIENT_MODE) + if (!client.connected()) { + client.stop(); +#if defined(MY_CONTROLLER_URL_ADDRESS) + if (client.connect(MY_CONTROLLER_URL_ADDRESS, MY_PORT)) { +#else + if (client.connect(_ethernetControllerIP, MY_PORT)) { +#endif + debug(PSTR("Eth: connect\n")); + _w5100_spi_en(false); + gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE)); + _w5100_spi_en(true); + presentNode(); + } else { + debug(PSTR("Eth: Failed to connect\n")); + _w5100_spi_en(false); + return false; + } + } + if (_readFromClient()) { + setIndication(INDICATION_GW_RX); + _w5100_spi_en(false); + return true; + } +#else +#if defined(MY_GATEWAY_ESP8266) || defined(MY_GATEWAY_LINUX) + // ESP8266: Go over list of clients and stop any that are no longer connected. + // If the server has a new client connection it will be assigned to a free slot. + bool allSlotsOccupied = true; + for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) { + if (!clients[i].connected()) { + if (clientsConnected[i]) { + debug(PSTR("Client %d disconnected\n"), i); + clients[i].stop(); } - #else - // W5100/ENC module does not have hasClient-method. We can only serve one client at the time. - EthernetClient newclient = _ethernetServer.available(); - // if a new client connects make sure to dispose any previous existing sockets - if (newclient) { - if (client != newclient) { - client.stop(); - client = newclient; - debug(PSTR("Eth: connect\n")); - _w5100_spi_en(false); - gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete.")); - _w5100_spi_en(true); - if (presentation) - presentation(); - } + //check if there are any new clients + if (_ethernetServer.hasClient()) { + clients[i] = _ethernetServer.available(); + inputString[i].idx = 0; + debug(PSTR("Client %d connected\n"), i); + gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE)); + // Send presentation of locally attached sensors (and node if applicable) + presentNode(); } - if (client) { - if (!client.connected()) { - debug(PSTR("Eth: disconnect\n")); - client.stop(); - } else { - if (_readFromClient()) { - setIndication(INDICATION_GW_RX); - _w5100_spi_en(false); - return true; - } - } + } + bool connected = clients[i].connected(); + clientsConnected[i] = connected; + allSlotsOccupied &= connected; + } + if (allSlotsOccupied && _ethernetServer.hasClient()) { + //no free/disconnected spot so reject + debug(PSTR("No free slot available\n")); + EthernetClient c = _ethernetServer.available(); + c.stop(); + } + // Loop over clients connect and read available data + for (uint8_t i = 0; i < ARRAY_SIZE(clients); i++) { + if (_readFromClient(i)) { + setIndication(INDICATION_GW_RX); + _w5100_spi_en(false); + return true; + } + } +#else + // W5100/ENC module does not have hasClient-method. We can only serve one client at the time. + EthernetClient newclient = _ethernetServer.available(); + // if a new client connects make sure to dispose any previous existing sockets + if (newclient) { + if (client != newclient) { + client.stop(); + client = newclient; + debug(PSTR("Eth: connect\n")); + _w5100_spi_en(false); + gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE)); + _w5100_spi_en(true); + presentNode(); + } + } + if (client) { + if (!client.connected()) { + debug(PSTR("Eth: disconnect\n")); + client.stop(); + } else { + if (_readFromClient()) { + setIndication(INDICATION_GW_RX); + _w5100_spi_en(false); + return true; } - #endif - #endif + } + } +#endif /* MY_GATEWAY_ESP8266 */ +#endif _w5100_spi_en(false); return false; } -MyMessage& gatewayTransportReceive() +MyMessage& gatewayTransportReceive(void) { // Return the last parsed message return _ethernetMsg; } - -#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266) -void gatewayTransportRenewIP() +#if !defined(MY_IP_ADDRESS) && !defined(MY_GATEWAY_ESP8266) && !defined(MY_GATEWAY_LINUX) +void gatewayTransportRenewIP(void) { /* renew/rebind IP address 0 - nothing happened @@ -378,8 +431,9 @@ void gatewayTransportRenewIP() unsigned long now = hwMillis(); // http://playground.arduino.cc/Code/TimingRollover - if ((long)(now - next_time) < 0) + if ((long)(now - next_time) < 0) { return; + } if (Ethernet.maintain() & ~(0x06)) { debug(PSTR("IP was not renewed correctly\n")); /* Error occured -> IP was not renewed */ @@ -388,4 +442,4 @@ void gatewayTransportRenewIP() _w5100_spi_en(false); next_time = now + MY_IP_RENEWAL_INTERVAL; } -#endif /* IP_ADDRESS_DHCP */ +#endif diff --git a/core/MyGatewayTransportMQTTClient.cpp b/core/MyGatewayTransportMQTTClient.cpp index f6c36e7d1..9016f2621 100644 --- a/core/MyGatewayTransportMQTTClient.cpp +++ b/core/MyGatewayTransportMQTTClient.cpp @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad -* Copyright (C) 2013-2015 Sensnology AB +* Copyright (C) 2013-2016 Sensnology AB * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -20,23 +20,26 @@ // Topic structure: MY_MQTT_PUBLISH_TOPIC_PREFIX/NODE-ID/SENSOR-ID/CMD-TYPE/ACK-FLAG/SUB-TYPE +#include "MyGatewayTransport.h" #if defined MY_CONTROLLER_IP_ADDRESS - IPAddress _brokerIp(MY_CONTROLLER_IP_ADDRESS); +IPAddress _brokerIp(MY_CONTROLLER_IP_ADDRESS); #endif #if defined(MY_GATEWAY_ESP8266) - #define EthernetClient WiFiClient - #if defined(MY_IP_ADDRESS) - IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS); - IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS); - #endif +#define EthernetClient WiFiClient +#if defined(MY_IP_ADDRESS) +IPAddress _gatewayIp(MY_IP_GATEWAY_ADDRESS); +IPAddress _subnetIp(MY_IP_SUBNET_ADDRESS); +#endif +#elif defined(MY_GATEWAY_LINUX) +// Nothing to do here #else - byte _MQTT_clientMAC[] = { MY_MAC_ADDRESS }; +uint8_t _MQTT_clientMAC[] = { MY_MAC_ADDRESS }; #endif #if defined(MY_IP_ADDRESS) - IPAddress _MQTT_clientIp(MY_IP_ADDRESS); +IPAddress _MQTT_clientIp(MY_IP_ADDRESS); #endif static EthernetClient _MQTT_ethClient; @@ -45,107 +48,37 @@ static bool _MQTT_connecting = true; static bool _MQTT_available = false; static MyMessage _MQTT_msg; -uint8_t protocolH2i(char c) { - uint8_t i = 0; - if (c <= '9') - i += c - '0'; - else if (c >= 'a') - i += c - 'a' + 10; - else - i += c - 'A' + 10; - return i; -} - - -bool gatewayTransportSend(MyMessage &message) { - if (!_MQTT_client.connected()) +bool gatewayTransportSend(MyMessage &message) +{ + if (!_MQTT_client.connected()) { return false; + } setIndication(INDICATION_GW_TX); - char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH]; - char _convBuffer[MAX_PAYLOAD * 2 + 1]; - snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR(MY_MQTT_PUBLISH_TOPIC_PREFIX "/%d/%d/%d/%d/%d"), message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type); - debug(PSTR("Sending message on topic: %s\n"), _fmtBuffer); - return _MQTT_client.publish(_fmtBuffer, message.getString(_convBuffer)); + char *topic = protocolFormatMQTTTopic(MY_MQTT_PUBLISH_TOPIC_PREFIX, message); + debug(PSTR("Sending message on topic: %s\n"), topic); + return _MQTT_client.publish(topic, message.getString(_convBuffer)); } -void incomingMQTT(char* topic, byte* payload, unsigned int length) { +void incomingMQTT(char* topic, uint8_t* payload, unsigned int length) +{ debug(PSTR("Message arrived on topic: %s\n"), topic); - char *str, *p; - uint8_t i = 0; - uint8_t bvalue[MAX_PAYLOAD]; - uint8_t blen = 0; - uint8_t command = 0; - for (str = strtok_r(topic, "/", &p); str && i <= 5; - str = strtok_r(NULL, "/", &p)) { - switch (i) { - case 0: { - // Topic prefix - if (strcmp(str, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) != 0) { - // Message not for us or malformed! - return; - } - break; - } - case 1: { - // Node id - _MQTT_msg.destination = atoi(str); - break; - } - case 2: { - // Sensor id - _MQTT_msg.sensor = atoi(str); - break; - } - case 3: { - // Command type - command = atoi(str); - mSetCommand(_MQTT_msg, command); - break; - } - case 4: { - // Ack flag - mSetRequestAck(_MQTT_msg, atoi(str) ? 1 : 0); - break; - } - case 5: { - // Sub type - _MQTT_msg.type = atoi(str); - // Add payload - if (command == C_STREAM) { - blen = 0; - uint8_t val; - while (*payload) { - val = protocolH2i(*payload++) << 4; - val += protocolH2i(*payload++); - bvalue[blen] = val; - blen++; - } - _MQTT_msg.set(bvalue, blen); - } - else { - char* ca; - ca = (char *)payload; - ca += length; - *ca = '\0'; - _MQTT_msg.set((const char*)payload); - } - _MQTT_available = true; - } - } - i++; - } + _MQTT_available = protocolMQTTParse(_MQTT_msg, topic, payload, length); } - -bool reconnectMQTT() { +bool reconnectMQTT(void) +{ debug(PSTR("Attempting MQTT connection...\n")); // Attempt to connect if (_MQTT_client.connect(MY_MQTT_CLIENT_ID #if defined(MY_MQTT_USER) && defined(MY_MQTT_PASSWORD) - , MY_MQTT_USER, MY_MQTT_PASSWORD + , MY_MQTT_USER, MY_MQTT_PASSWORD #endif - )) { + )) { debug(PSTR("MQTT connected\n")); + + // Send presentation of locally attached sensors (and node if applicable) + presentNode(); + // Once connected, publish an announcement... //_MQTT_client.publish("outTopic","hello world"); // ... and resubscribe @@ -155,54 +88,60 @@ bool reconnectMQTT() { return false; } -bool gatewayTransportConnect() { - #if defined(MY_GATEWAY_ESP8266) - while (WiFi.status() != WL_CONNECTED) { - delay(500); // delay calls yield - MY_SERIALDEVICE.print("."); - } - MY_SERIALDEVICE.print("IP: "); - MY_SERIALDEVICE.println(WiFi.localIP()); - #else - #ifdef MY_IP_ADDRESS - Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp); - #else - // Get IP address from DHCP - if (!Ethernet.begin(_MQTT_clientMAC)) { - MY_SERIALDEVICE.print("DHCP FAILURE..."); - _MQTT_connecting = false; - return false; - } - #endif - MY_SERIALDEVICE.print("IP: "); - MY_SERIALDEVICE.println(Ethernet.localIP()); - // give the Ethernet interface a second to initialize - delay(1000); - #endif +bool gatewayTransportConnect(void) +{ +#if defined(MY_GATEWAY_ESP8266) + while (WiFi.status() != WL_CONNECTED) { + wait(500); + MY_SERIALDEVICE.print(F(".")); + } + MY_SERIALDEVICE.print(F("IP: ")); + MY_SERIALDEVICE.println(WiFi.localIP()); +#elif defined(MY_GATEWAY_LINUX) +#if defined(MY_IP_ADDRESS) + //TODO +#endif +#else +#ifdef MY_IP_ADDRESS + Ethernet.begin(_MQTT_clientMAC, _MQTT_clientIp); +#else + // Get IP address from DHCP + if (!Ethernet.begin(_MQTT_clientMAC)) { + MY_SERIALDEVICE.print(F("DHCP FAILURE...")); + _MQTT_connecting = false; + return false; + } +#endif + MY_SERIALDEVICE.print(F("IP: ")); + MY_SERIALDEVICE.println(Ethernet.localIP()); + // give the Ethernet interface a second to initialize + delay(1000); +#endif return true; } -bool gatewayTransportInit() { +bool gatewayTransportInit(void) +{ _MQTT_connecting = true; - #if defined(MY_CONTROLLER_IP_ADDRESS) - _MQTT_client.setServer(_brokerIp, MY_PORT); - #else - _MQTT_client.setServer(MY_CONTROLLER_URL_ADDRESS, MY_PORT); - #endif - - _MQTT_client.setCallback(incomingMQTT); - - #if defined(MY_GATEWAY_ESP8266) - // Turn off access point - WiFi.mode(WIFI_STA); - #if defined(MY_ESP8266_HOSTNAME) - WiFi.hostname(MY_ESP8266_HOSTNAME); - #endif - (void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD); - #ifdef MY_IP_ADDRESS - WiFi.config(_MQTT_clientIp, _gatewayIp, _subnetIp); - #endif - #endif +#if defined(MY_CONTROLLER_IP_ADDRESS) + _MQTT_client.setServer(_brokerIp, MY_PORT); +#else + _MQTT_client.setServer(MY_CONTROLLER_URL_ADDRESS, MY_PORT); +#endif + + _MQTT_client.setCallback(incomingMQTT); + +#if defined(MY_GATEWAY_ESP8266) + // Turn off access point + WiFi.mode(WIFI_STA); +#if defined(MY_ESP8266_HOSTNAME) + WiFi.hostname(MY_ESP8266_HOSTNAME); +#endif + (void)WiFi.begin(MY_ESP8266_SSID, MY_ESP8266_PASSWORD); +#ifdef MY_IP_ADDRESS + WiFi.config(_MQTT_clientIp, _gatewayIp, _subnetIp); +#endif +#endif gatewayTransportConnect(); @@ -210,30 +149,27 @@ bool gatewayTransportInit() { return true; } - - -bool gatewayTransportAvailable() { - - if (_MQTT_connecting) +bool gatewayTransportAvailable(void) +{ + if (_MQTT_connecting) { return false; - + } //keep lease on dhcp address //Ethernet.maintain(); if (!_MQTT_client.connected()) { //reinitialise client - if (gatewayTransportConnect()) + if (gatewayTransportConnect()) { reconnectMQTT(); + } return false; } _MQTT_client.loop(); return _MQTT_available; } -MyMessage & gatewayTransportReceive() { +MyMessage & gatewayTransportReceive(void) +{ // Return the last parsed message _MQTT_available = false; return _MQTT_msg; } - - - diff --git a/core/MyGatewayTransportSerial.cpp b/core/MyGatewayTransportSerial.cpp index e3ea5fb3e..b54fa9241 100644 --- a/core/MyGatewayTransportSerial.cpp +++ b/core/MyGatewayTransportSerial.cpp @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad - * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2013-2016 Sensnology AB * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -17,42 +17,49 @@ * version 2 as published by the Free Software Foundation. */ - #include "MyConfig.h" #include "MyProtocol.h" #include "MyGatewayTransport.h" #include "MyMessage.h" #include "MyProtocol.h" +// global variables +extern MyMessage _msgTmp; char _serialInputString[MY_GATEWAY_MAX_RECEIVE_LENGTH]; // A buffer for incoming commands from serial interface -int _serialInputPos; +uint8_t _serialInputPos; MyMessage _serialMsg; - -bool gatewayTransportSend(MyMessage &message) { - setIndication(INDICATION_GW_TX); +bool gatewayTransportSend(MyMessage &message) +{ + setIndication(INDICATION_GW_TX); MY_SERIALDEVICE.print(protocolFormat(message)); // Serial print is always successful return true; } -bool gatewayTransportInit() { - gatewayTransportSend(buildGw(_msg, I_GATEWAY_READY).set("Gateway startup complete.")); +bool gatewayTransportInit(void) +{ + (void)gatewayTransportSend(buildGw(_msgTmp, I_GATEWAY_READY).set(MSG_GW_STARTUP_COMPLETE)); + // Send presentation of locally attached sensors (and node if applicable) + presentNode(); return true; } - -bool gatewayTransportAvailable() { +bool gatewayTransportAvailable(void) +{ while (MY_SERIALDEVICE.available()) { // get the new byte: - char inChar = (char) MY_SERIALDEVICE.read(); + const char inChar = (char)MY_SERIALDEVICE.read(); // if the incoming character is a newline, set a flag // so the main loop can do something about it: if (_serialInputPos < MY_GATEWAY_MAX_RECEIVE_LENGTH - 1) { if (inChar == '\n') { _serialInputString[_serialInputPos] = 0; - bool ok = protocolParse(_serialMsg, _serialInputString); + const bool ok = protocolParse(_serialMsg, _serialInputString); + if (ok) { + setIndication(INDICATION_GW_RX); + } _serialInputPos = 0; return ok; } else { @@ -68,7 +75,8 @@ bool gatewayTransportAvailable() { return false; } -MyMessage & gatewayTransportReceive() { +MyMessage & gatewayTransportReceive(void) +{ // Return the last parsed message return _serialMsg; } diff --git a/core/MyHw.h b/core/MyHw.h index 1224f6629..3b4cfbae3 100644 --- a/core/MyHw.h +++ b/core/MyHw.h @@ -1,4 +1,4 @@ -/** +/* * The MySensors Arduino library handles the wireless radio link and protocol * between your home built sensors/actuators and HA controller of choice. * The sensors forms a self healing radio network with optional repeaters. Each @@ -17,6 +17,12 @@ * version 2 as published by the Free Software Foundation. */ +/** +* @file MyHw.h +* +* MySensors hardware abstraction layer +*/ + #ifndef MyHw_h #define MyHw_h @@ -30,23 +36,103 @@ // Implement these as functions or macros /* -#define hwDigitalWrite(__pin, __value) #define hwInit() MY_SERIALDEVICE.begin(BAUD_RATE) #define hwWatchdogReset() wdt_reset() #define hwReboot() wdt_enable(WDTO_15MS); while (1) #define hwMillis() millis() +#define hwDigitalWrite(__pin, __value) +#define hwDigitalRead(__pin) +#define hwPinMode(__pin, __value) + void hwReadConfigBlock(void* buf, void* adr, size_t length); void hwWriteConfigBlock(void* buf, void* adr, size_t length); void hwWriteConfig(int adr, uint8_t value); uint8_t hwReadConfig(int adr); */ +/** + * Sleep for a defined time, using minimum power. + * @param ms Time to sleep, in [ms]. + * @return Nonsense, please ignore. + */ int8_t hwSleep(unsigned long ms); + +/** + * Sleep for a defined time, using minimum power, or until woken by interrupt. + * @param interrupt Interrupt number, which can wake the mcu from sleep. + * @param mode Interrupt mode, as passed to attachInterrupt. + * @param ms Time to sleep, in [ms]. + * @return -1 when woken by timer, or interrupt number when woken by interrupt. + */ int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms); -int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms); + +/** + * Sleep for a defined time, using minimum power, or until woken by one of the interrupts. + * @param interrupt1 Interrupt1 number, which can wake the mcu from sleep. + * @param mode1 Interrupt1 mode, as passed to attachInterrupt. + * @param interrupt2 Interrupt2 number, which can wake the mcu from sleep. + * @param mode2 Interrupt2 mode, as passed to attachInterrupt. + * @param ms Time to sleep, in [ms]. + * @return -1 when woken by timer, or interrupt number when woken by interrupt. + */ +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, + unsigned long ms); + +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) +/** + * CPU voltage + * @return CPU voltage in mV + */ +uint16_t hwCPUVoltage(); + +/** + * CPU frequency + * @return CPU frequency in 1/10Mhz + */ +uint16_t hwCPUFrequency(); + +/** + * Free memory + * @return free memory in bytes + */ +uint16_t hwFreeMem(); +#endif + #ifdef MY_DEBUG - void hwDebugPrint(const char *fmt, ... ); +void hwDebugPrint(const char *fmt, ... ); #endif +/** + * @def MY_CRITICAL_SECTION + * @brief Creates a block of code that is guaranteed to be executed atomically. + * Upon entering the block all interrupts are disabled, and re-enabled upon + * exiting the block from any exit path. + * A typical example that requires atomic access is a 16 (or more) bit variable + * that is shared between the main execution path and an ISR, on an 8-bit + * platform (e.g AVR): + * @code + * volatile uint16_t val = 0; + * + * void interrupHandler() + * { + * val = ~val; + * } + * + * void loop() + * { + * uint16_t copy_val; + * MY_CRITICAL_SECTION + * { + * copy_val = val; + * } + * } + * @endcode + * All code within the MY_CRITICAL_SECTION block will be protected from being + * interrupted during execution. + */ +#ifdef DOXYGEN +#define MY_CRITICAL_SECTION +#endif /* DOXYGEN */ + #endif // #ifdef MyHw_h diff --git a/core/MyHwATMega328.cpp b/core/MyHwATMega328.cpp index 9aad4fd1d..50c2e5115 100644 --- a/core/MyHwATMega328.cpp +++ b/core/MyHwATMega328.cpp @@ -21,15 +21,35 @@ #include "MyHwATMega328.h" -volatile int8_t pinIntTrigger = 0; +#define INVALID_INTERRUPT_NUM (0xFFu) -void wakeUp() //place to send the interrupts +volatile uint8_t _wokeUpByInterrupt = + INVALID_INTERRUPT_NUM; // Interrupt number that woke the mcu. +volatile uint8_t _wakeUp1Interrupt = + INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp1-callback. +volatile uint8_t _wakeUp2Interrupt = + INVALID_INTERRUPT_NUM; // Interrupt number for wakeUp2-callback. + +void wakeUp1() //place to send the interrupts { - pinIntTrigger = 1; + detachInterrupt(_wakeUp1Interrupt); + if (_wakeUp2Interrupt != INVALID_INTERRUPT_NUM) { + detachInterrupt(_wakeUp2Interrupt); + } + _wokeUpByInterrupt = _wakeUp1Interrupt; } void wakeUp2() //place to send the second interrupts { - pinIntTrigger = 2; + detachInterrupt(_wakeUp2Interrupt); + if (_wakeUp1Interrupt != INVALID_INTERRUPT_NUM) { + detachInterrupt(_wakeUp1Interrupt); + } + _wokeUpByInterrupt = _wakeUp2Interrupt; +} + +bool interruptWakeUp() +{ + return _wokeUpByInterrupt != INVALID_INTERRUPT_NUM; } // Watchdog Timer interrupt service routine. This routine is required @@ -38,14 +58,13 @@ ISR (WDT_vect) { } -void hwPowerDown(period_t period) { - +void hwPowerDown(period_t period) +{ // disable ADC for power saving ADCSRA &= ~(1 << ADEN); // save WDT settings uint8_t WDTsave = WDTCSR; - if (period != SLEEP_FOREVER) - { + if (period != SLEEP_FOREVER) { wdt_enable(period); // enable WDT interrupt before system reset WDTCSR |= (1 << WDCE) | (1 << WDIE); @@ -59,10 +78,11 @@ void hwPowerDown(period_t period) { #if defined __AVR_ATmega328P__ sleep_bod_disable(); #endif + // Enable interrupts & sleep until WDT or ext. interrupt sei(); - // sleep until WDT or ext. interrupt + // Directly sleep CPU, to prevent race conditions! (see chapter 7.7 of ATMega328P datasheet) sleep_cpu(); - sleep_disable(); + sleep_disable(); // restore previous WDT settings cli(); wdt_reset(); @@ -75,88 +95,136 @@ void hwPowerDown(period_t period) { ADCSRA |= (1 << ADEN); } -void hwInternalSleep(unsigned long ms) { +void hwInternalSleep(unsigned long ms) +{ // Let serial prints finish (debug, log etc) - #ifndef MY_DISABLED_SERIAL - MY_SERIALDEVICE.flush(); - #endif - // reset interrupt trigger var - pinIntTrigger = 0; - while (!pinIntTrigger && ms >= 8000) { hwPowerDown(SLEEP_8S); ms -= 8000; } - if (!pinIntTrigger && ms >= 4000) { hwPowerDown(SLEEP_4S); ms -= 4000; } - if (!pinIntTrigger && ms >= 2000) { hwPowerDown(SLEEP_2S); ms -= 2000; } - if (!pinIntTrigger && ms >= 1000) { hwPowerDown(SLEEP_1S); ms -= 1000; } - if (!pinIntTrigger && ms >= 500) { hwPowerDown(SLEEP_500MS); ms -= 500; } - if (!pinIntTrigger && ms >= 250) { hwPowerDown(SLEEP_250MS); ms -= 250; } - if (!pinIntTrigger && ms >= 125) { hwPowerDown(SLEEP_120MS); ms -= 120; } - if (!pinIntTrigger && ms >= 64) { hwPowerDown(SLEEP_60MS); ms -= 60; } - if (!pinIntTrigger && ms >= 32) { hwPowerDown(SLEEP_30MS); ms -= 30; } - if (!pinIntTrigger && ms >= 16) { hwPowerDown(SLEEP_15MS); ms -= 15; } +#ifndef MY_DISABLED_SERIAL + MY_SERIALDEVICE.flush(); +#endif + while (!interruptWakeUp() && ms >= 8000) { + hwPowerDown(SLEEP_8S); + ms -= 8000; + } + if (!interruptWakeUp() && ms >= 4000) { + hwPowerDown(SLEEP_4S); + ms -= 4000; + } + if (!interruptWakeUp() && ms >= 2000) { + hwPowerDown(SLEEP_2S); + ms -= 2000; + } + if (!interruptWakeUp() && ms >= 1000) { + hwPowerDown(SLEEP_1S); + ms -= 1000; + } + if (!interruptWakeUp() && ms >= 500) { + hwPowerDown(SLEEP_500MS); + ms -= 500; + } + if (!interruptWakeUp() && ms >= 250) { + hwPowerDown(SLEEP_250MS); + ms -= 250; + } + if (!interruptWakeUp() && ms >= 125) { + hwPowerDown(SLEEP_120MS); + ms -= 120; + } + if (!interruptWakeUp() && ms >= 64) { + hwPowerDown(SLEEP_60MS); + ms -= 60; + } + if (!interruptWakeUp() && ms >= 32) { + hwPowerDown(SLEEP_30MS); + ms -= 30; + } + if (!interruptWakeUp() && ms >= 16) { + hwPowerDown(SLEEP_15MS); + ms -= 15; + } } -int8_t hwSleep(unsigned long ms) { +int8_t hwSleep(unsigned long ms) +{ hwInternalSleep(ms); - return -1; + return MY_WAKE_UP_BY_TIMER; } -int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { - return hwSleep(interrupt,mode,0xFF,0x00,ms); +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ + return hwSleep(interrupt,mode,INVALID_INTERRUPT_NUM,0u,ms); } -int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { - // reset interrupt trigger var - pinIntTrigger = 0; - // attach interrupts - attachInterrupt(interrupt1, wakeUp, mode1); - if (interrupt2!=0xFF) attachInterrupt(interrupt2, wakeUp2, mode2); - +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, + unsigned long ms) +{ + // Disable interrupts until going to sleep, otherwise interrupts occurring between attachInterrupt() + // and sleep might cause the ATMega to not wakeup from sleep as interrupt has already be handled! + cli(); + // attach interrupts + _wakeUp1Interrupt = interrupt1; + _wakeUp2Interrupt = interrupt2; + if (interrupt1 != INVALID_INTERRUPT_NUM) { + attachInterrupt(interrupt1, wakeUp1, mode1); + } + if (interrupt2 != INVALID_INTERRUPT_NUM) { + attachInterrupt(interrupt2, wakeUp2, mode2); + } + if (ms>0) { // sleep for defined time hwInternalSleep(ms); } else { // sleep until ext interrupt triggered - hwPowerDown(SLEEP_FOREVER); + hwPowerDown(SLEEP_FOREVER); + } + + // Assure any interrupts attached, will get detached when they did not occur. + if (interrupt1 != INVALID_INTERRUPT_NUM) { + detachInterrupt(interrupt1); + } + if (interrupt2 != INVALID_INTERRUPT_NUM) { + detachInterrupt(interrupt2); } - - detachInterrupt(interrupt1); - if (interrupt2!=0xFF) detachInterrupt(interrupt2); - - // default: no interrupt triggered, timer wake up - int8_t retVal = -1; - if (pinIntTrigger == 1) { - retVal = (int8_t)interrupt1; - } else if (pinIntTrigger == 2) { - retVal = (int8_t)interrupt2; + // Return what woke the mcu. + int8_t ret = MY_WAKE_UP_BY_TIMER; // default: no interrupt triggered, timer wake up + if (interruptWakeUp()) { + ret = static_cast(_wokeUpByInterrupt); } - return retVal; + // Clear woke-up-by-interrupt flag, so next sleeps won't return immediately. + _wokeUpByInterrupt = INVALID_INTERRUPT_NUM; + + return ret; } -uint16_t hwCPUVoltage() { - // Measure Vcc against 1.1V Vref - #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) - ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1)); - #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) - ADMUX = (_BV(MUX5) | _BV(MUX0)); - #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) - ADMUX = (_BV(MUX3) | _BV(MUX2)); - #else - ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1)); - #endif +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) +uint16_t hwCPUVoltage() +{ + // Measure Vcc against 1.1V Vref +#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) + ADMUX = (_BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1)); +#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) + ADMUX = (_BV(MUX5) | _BV(MUX0)); +#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) + ADMUX = (_BV(MUX3) | _BV(MUX2)); +#else + ADMUX = (_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1)); +#endif // Vref settle - delay(70); + delay(70); // Do conversion ADCSRA |= _BV(ADSC); - while (bit_is_set(ADCSRA,ADSC)); + while (bit_is_set(ADCSRA,ADSC)) {}; // return Vcc in mV return (1125300UL) / ADC; } -uint16_t hwCPUFrequency() { - noInterrupts(); +uint16_t hwCPUFrequency() +{ + cli(); // setup timer1 - TIFR1 = 0xFF; - TCNT1 = 0; + TIFR1 = 0xFF; + TCNT1 = 0; TCCR1A = 0; TCCR1C = 0; // save WDT settings @@ -166,47 +234,51 @@ uint16_t hwCPUFrequency() { WDTCSR |= (1 << WDIE); wdt_reset(); // start timer1 with 1024 prescaling - TCCR1B = _BV(CS12) | _BV(CS10); + TCCR1B = _BV(CS12) | _BV(CS10); // wait until wdt interrupt - while (bit_is_clear(WDTCSR,WDIF)); + while (bit_is_clear(WDTCSR,WDIF)) {}; // stop timer TCCR1B = 0; // restore WDT settings wdt_reset(); WDTCSR |= (1 << WDCE) | (1 << WDE); WDTCSR = WDTsave; - interrupts(); + sei(); // return frequency in 1/10MHz (accuracy +- 10%) return TCNT1 * 2048UL / 100000UL; } -uint16_t hwFreeMem() { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +uint16_t hwFreeMem() +{ + extern int __heap_start, *__brkval; + int v; + return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); } - - +#endif #ifdef MY_DEBUG -void hwDebugPrint(const char *fmt, ... ) { - char fmtBuffer[300]; - #ifdef MY_GATEWAY_FEATURE - // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) - snprintf_P(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); - MY_SERIALDEVICE.print(fmtBuffer); - #endif +void hwDebugPrint(const char *fmt, ... ) +{ + char fmtBuffer[MY_SERIAL_OUTPUT_SIZE]; +#ifdef MY_GATEWAY_FEATURE + // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) + snprintf_P(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); + MY_SERIALDEVICE.print(fmtBuffer); +#else + // prepend timestamp (AVR nodes) + MY_SERIALDEVICE.print(hwMillis()); + MY_SERIALDEVICE.print(" "); +#endif va_list args; va_start (args, fmt ); - va_end (args); - #ifdef MY_GATEWAY_FEATURE - // Truncate message if this is gateway node - vsnprintf_P(fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, fmt, args); - fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1] = '\n'; - fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH] = '\0'; - #else - vsnprintf_P(fmtBuffer, 299, fmt, args); - #endif +#ifdef MY_GATEWAY_FEATURE + // Truncate message if this is gateway node + vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args); + fmtBuffer[sizeof(fmtBuffer) - 2] = '\n'; + fmtBuffer[sizeof(fmtBuffer) - 1] = '\0'; +#else + vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args); +#endif va_end (args); MY_SERIALDEVICE.print(fmtBuffer); MY_SERIALDEVICE.flush(); diff --git a/core/MyHwATMega328.h b/core/MyHwATMega328.h index 8eb0060b1..7f120319b 100644 --- a/core/MyHwATMega328.h +++ b/core/MyHwATMega328.h @@ -27,56 +27,61 @@ #include #include #include - +#include #ifdef __cplusplus #include #endif +#ifndef MY_SERIALDEVICE #define MY_SERIALDEVICE Serial +#endif + #if defined __AVR_ATmega328P__ #ifndef sleep_bod_disable #define sleep_bod_disable() \ -do { \ - unsigned char tempreg; \ - __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \ - "ori %[tempreg], %[bods_bodse]" "\n\t" \ - "out %[mcucr], %[tempreg]" "\n\t" \ - "andi %[tempreg], %[not_bodse]" "\n\t" \ - "out %[mcucr], %[tempreg]" \ - : [tempreg] "=&d" (tempreg) \ - : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \ - [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \ - [not_bodse] "i" (~_BV(BODSE))); \ -} while (0) + do { \ + unsigned char tempreg; \ + __asm__ __volatile__("in %[tempreg], %[mcucr]" "\n\t" \ + "ori %[tempreg], %[bods_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" "\n\t" \ + "andi %[tempreg], %[not_bodse]" "\n\t" \ + "out %[mcucr], %[tempreg]" \ + : [tempreg] "=&d" (tempreg) \ + : [mcucr] "I" _SFR_IO_ADDR(MCUCR), \ + [bods_bodse] "i" (_BV(BODS) | _BV(BODSE)), \ + [not_bodse] "i" (~_BV(BODSE))); \ + } while (0) #endif #endif // Define these as macros to save valuable space -#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value)) -#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE) -#define hwWatchdogReset() wdt_reset() -#define hwReboot() wdt_enable(WDTO_15MS); while (1) -#define hwMillis() millis() -#define hwReadConfig(__pos) (eeprom_read_byte((uint8_t*)(__pos))) +#define hwDigitalWrite(__pin, __value) digitalWriteFast(__pin, __value) +#define hwDigitalRead(__pin) digitalReadFast(__pin) +#define hwPinMode(__pin, __value) pinModeFast(__pin, __value) -#ifndef eeprom_update_byte - #define hwWriteConfig(loc, val) if((uint8_t)(val) != eeprom_read_byte((uint8_t*)(loc))) { eeprom_write_byte((uint8_t*)(loc), (val)); } + +#if defined(MY_DISABLED_SERIAL) +#define hwInit() #else - #define hwWriteConfig(__pos, __value) (eeprom_update_byte((uint8_t*)(__pos), (__value))) +#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE) #endif -// -#define hwReadConfigBlock(__buf, __pos, __length) (eeprom_read_block((__buf), (void*)(__pos), (__length))) -#define hwWriteConfigBlock(__pos, __buf, __length) (eeprom_write_block((void*)(__pos), (void*)__buf, (__length))) +#define hwWatchdogReset() wdt_reset() +#define hwReboot() wdt_enable(WDTO_15MS); while (1) +#define hwMillis() millis() +#define hwRandomNumberInit() randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN)) +#define hwReadConfig(__pos) eeprom_read_byte((uint8_t*)(__pos)) +#define hwWriteConfig(__pos, __val) eeprom_update_byte((uint8_t*)(__pos), (__val)) +#define hwReadConfigBlock(__buf, __pos, __length) eeprom_read_block((void*)(__buf), (void*)(__pos), (__length)) +#define hwWriteConfigBlock(__buf, __pos, __length) eeprom_update_block((void*)(__buf), (void*)(__pos), (__length)) -enum period_t -{ +enum period_t { SLEEP_15MS, SLEEP_30MS, SLEEP_60MS, @@ -92,4 +97,8 @@ enum period_t void hwInternalSleep(unsigned long ms); +#ifndef DOXYGEN +#define MY_CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE) +#endif /* DOXYGEN */ + #endif diff --git a/core/MyHwESP8266.cpp b/core/MyHwESP8266.cpp index 1e501565f..9cc852abe 100644 --- a/core/MyHwESP8266.cpp +++ b/core/MyHwESP8266.cpp @@ -20,135 +20,128 @@ #include "MyHwESP8266.h" #include -/* -int8_t pinIntTrigger = 0; -void wakeUp() //place to send the interrupts +void hwInit(void) { - pinIntTrigger = 1; -} -void wakeUp2() //place to send the second interrupts -{ - pinIntTrigger = 2; +#if !defined(MY_DISABLED_SERIAL) + MY_SERIALDEVICE.begin(MY_BAUD_RATE, SERIAL_8N1, MY_ESP8266_SERIAL_MODE, 1); + MY_SERIALDEVICE.setDebugOutput(true); +#endif + EEPROM.begin(EEPROM_size); } -// Watchdog Timer interrupt service routine. This routine is required -// to allow automatic WDIF and WDIE bit clearance in hardware. -ISR (WDT_vect) +void hwReadConfigBlock(void* buf, void* addr, size_t length) { - // WDIE & WDIF is cleared in hardware upon entering this ISR - wdt_disable(); + uint8_t* dst = static_cast(buf); + int pos = reinterpret_cast(addr); + while (length-- > 0) { + *dst++ = EEPROM.read(pos++); + } } -*/ -static void hwInitConfigBlock( size_t length = 1024 /*ATMega328 has 1024 bytes*/ ) +void hwWriteConfigBlock(void* buf, void* addr, size_t length) { - static bool initDone = false; - if (!initDone) - { - EEPROM.begin(length); - initDone = true; - } + uint8_t* src = static_cast(buf); + int pos = reinterpret_cast(addr); + while (length-- > 0) { + EEPROM.write(pos++, *src++); + } + // see implementation, commit only executed if diff + EEPROM.commit(); } -void hwReadConfigBlock(void* buf, void* adr, size_t length) +uint8_t hwReadConfig(const int addr) { - hwInitConfigBlock(); - uint8_t* dst = static_cast(buf); - int offs = reinterpret_cast(adr); - while (length-- > 0) - { - *dst++ = EEPROM.read(offs++); - } + uint8_t value; + hwReadConfigBlock(&value, reinterpret_cast(addr), 1); + return value; } -void hwWriteConfigBlock(void* buf, void* adr, size_t length) +void hwWriteConfig(const int addr, uint8_t value) { - hwInitConfigBlock(); - uint8_t* src = static_cast(buf); - int offs = reinterpret_cast(adr); - while (length-- > 0) - { - EEPROM.write(offs++, *src++); - } - EEPROM.commit(); + hwWriteConfigBlock(&value, reinterpret_cast(addr), 1); } -uint8_t hwReadConfig(int adr) -{ - uint8_t value; - hwReadConfigBlock(&value, reinterpret_cast(adr), 1); - return value; -} -void hwWriteConfig(int adr, uint8_t value) +int8_t hwSleep(unsigned long ms) { - uint8_t curr = hwReadConfig(adr); - if (curr != value) - { - hwWriteConfigBlock(&value, reinterpret_cast(adr), 1); - } -} - - -int8_t hwSleep(unsigned long ms) { // TODO: Not supported! (void)ms; - return -2; + return MY_SLEEP_NOT_POSSIBLE; } -int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ // TODO: Not supported! (void)interrupt; (void)mode; (void)ms; - return -2; + return MY_SLEEP_NOT_POSSIBLE; } -int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, + unsigned long ms) +{ // TODO: Not supported! (void)interrupt1; (void)mode1; (void)interrupt2; (void)mode2; (void)ms; - return -2; + return MY_SLEEP_NOT_POSSIBLE; } +#if defined(MY_SPECIAL_DEBUG) +// settings for getVcc() ADC_MODE(ADC_VCC); +#else +// [default] settings for analogRead(A0) +ADC_MODE(ADC_TOUT); +#endif -uint16_t hwCPUVoltage() { - // in mV +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) + +uint16_t hwCPUVoltage() +{ +#if defined(MY_SPECIAL_DEBUG) + // in mV, requires ADC_VCC set return ESP.getVcc(); +#else + // not possible + return 0; +#endif } -uint16_t hwCPUFrequency() { +uint16_t hwCPUFrequency() +{ // in 1/10Mhz return ESP.getCpuFreqMHz()*10; } -uint16_t hwFreeMem() { +uint16_t hwFreeMem() +{ return ESP.getFreeHeap(); } +#endif #ifdef MY_DEBUG -void hwDebugPrint(const char *fmt, ... ) { - char fmtBuffer[300]; - #ifdef MY_GATEWAY_FEATURE - // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) - snprintf_P(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); - MY_SERIALDEVICE.print(fmtBuffer); - #endif +void hwDebugPrint(const char *fmt, ... ) +{ + char fmtBuffer[MY_SERIAL_OUTPUT_SIZE]; +#ifdef MY_GATEWAY_FEATURE + // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) + snprintf_P(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); + MY_SERIALDEVICE.print(fmtBuffer); +#endif va_list args; va_start (args, fmt ); - va_end (args); - #ifdef MY_GATEWAY_FEATURE - // Truncate message if this is gateway node - vsnprintf_P(fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, fmt, args); - fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1] = '\n'; - fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH] = '\0'; - #else - vsnprintf_P(fmtBuffer, 299, fmt, args); - #endif +#ifdef MY_GATEWAY_FEATURE + // Truncate message if this is gateway node + vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args); + fmtBuffer[sizeof(fmtBuffer) - 2] = '\n'; + fmtBuffer[sizeof(fmtBuffer) - 1] = '\0'; +#else + vsnprintf_P(fmtBuffer, sizeof(fmtBuffer), fmt, args); +#endif va_end (args); MY_SERIALDEVICE.print(fmtBuffer); MY_SERIALDEVICE.flush(); diff --git a/core/MyHwESP8266.h b/core/MyHwESP8266.h index a9693ff5b..bfb7e7cb0 100644 --- a/core/MyHwESP8266.h +++ b/core/MyHwESP8266.h @@ -25,23 +25,41 @@ #include #endif +#ifndef MY_SERIALDEVICE #define MY_SERIALDEVICE Serial +#endif + #define min(a,b) ((a)<(b)?(a):(b)) #define max(a,b) ((a)>(b)?(a):(b)) +#define EEPROM_size (1024) // Define these as macros to save valuable space - -#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value)) -#define hwInit() MY_SERIALDEVICE.begin(MY_BAUD_RATE); MY_SERIALDEVICE.setDebugOutput(true) +#define hwDigitalWrite(__pin, __value) digitalWrite(__pin, __value) +#define hwDigitalRead(__pin) digitalRead(__pin) +#define hwPinMode(__pin, __value) pinMode(__pin, __value) #define hwWatchdogReset() wdt_reset() -#define hwReboot() ESP.restart(); +#define hwReboot() ESP.restart() #define hwMillis() millis() +#define hwRandomNumberInit() randomSeed(RANDOM_REG32) +void hwInit(void); void hwReadConfigBlock(void* buf, void* adr, size_t length); void hwWriteConfigBlock(void* buf, void* adr, size_t length); -void hwWriteConfig(int adr, uint8_t value); -uint8_t hwReadConfig(int adr); +void hwWriteConfig(const int addr, uint8_t value); +uint8_t hwReadConfig(const int addr); + +/** + * Restore interrupt state. + * Helper function for MY_CRITICAL_SECTION. + */ +static __inline__ void __psRestore(const uint32_t *__s) +{ + xt_wsr_ps( *__s ); +} +#ifndef DOXYGEN +#define MY_CRITICAL_SECTION for ( uint32_t __psSaved __attribute__((__cleanup__(__psRestore))) = xt_rsil(15), __ToDo = 1; __ToDo ; __ToDo = 0 ) +#endif /* DOXYGEN */ #endif // #ifdef ARDUINO_ARCH_ESP8266 diff --git a/core/MyHwLinuxGeneric.cpp b/core/MyHwLinuxGeneric.cpp new file mode 100644 index 000000000..e90b596a1 --- /dev/null +++ b/core/MyHwLinuxGeneric.cpp @@ -0,0 +1,132 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2016 Sensnology AB + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include "MyHwLinuxGeneric.h" + +#include +#include +#include "SoftEeprom.h" +#include "log.h" + +static SoftEeprom eeprom = SoftEeprom(MY_LINUX_CONFIG_FILE, 1024); // ATMega328 has 1024 bytes + +void hwInit() +{ +#ifdef MY_GATEWAY_SERIAL + MY_SERIALDEVICE.begin(MY_BAUD_RATE); +#ifdef MY_LINUX_SERIAL_GROUPNAME + if (!MY_SERIALDEVICE.setGroupPerm(MY_LINUX_SERIAL_GROUPNAME)) { + logError("Unable to change permission for serial port device.\n"); + exit(1); + } +#endif +#endif +} + +void hwReadConfigBlock(void* buf, void* addr, size_t length) +{ + eeprom.readBlock(buf, addr, length); +} + +void hwWriteConfigBlock(void* buf, void* addr, size_t length) +{ + eeprom.writeBlock(buf, addr, length); +} + +uint8_t hwReadConfig(int addr) +{ + return eeprom.readByte(addr); +} + +void hwWriteConfig(int addr, uint8_t value) +{ + eeprom.writeByte(addr, value); +} + +void hwRandomNumberInit() +{ + randomSeed(time(NULL)); +} + +unsigned long hwMillis() +{ + return millis(); +} + +// Not supported! +int8_t hwSleep(unsigned long ms) +{ + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +// Not supported! +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ + (void)interrupt; + (void)mode; + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +// Not supported! +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, + unsigned long ms) +{ + (void)interrupt1; + (void)mode1; + (void)interrupt2; + (void)mode2; + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) +uint16_t hwCPUVoltage() +{ + // TODO: Not supported! + return 0; +} + +uint16_t hwCPUFrequency() +{ + // TODO: Not supported! + return 0; +} + +uint16_t hwFreeMem() +{ + // TODO: Not supported! + return 0; +} +#endif + +#ifdef MY_DEBUG +void hwDebugPrint(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogDebug(fmt, args); + va_end(args); +} +#endif diff --git a/core/MyHwLinuxGeneric.h b/core/MyHwLinuxGeneric.h new file mode 100644 index 000000000..234156f78 --- /dev/null +++ b/core/MyHwLinuxGeneric.h @@ -0,0 +1,91 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2016 Sensnology AB + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef MyHwLinuxGeneric_h +#define MyHwLinuxGeneric_h + +#include +#include +#include "MyHw.h" +#include "SerialPort.h" + +#ifdef MY_IS_SERIAL_PTY +SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PTY, true); +#else +SerialPort Serial = SerialPort(MY_LINUX_SERIAL_PORT); +#endif + +#ifndef MY_SERIALDEVICE +#define MY_SERIALDEVICE Serial +#endif + +// Define these as macros (do nothing) +#define hwWatchdogReset() +#define hwReboot() + +#define hwDigitalWrite(__pin, __value) _Pragma("GCC error \"Not supported on linux-generic\"") +#define hwDigitalRead(__pin) _Pragma("GCC error \"Not supported on linux-generic\"") +#define hwPinMode(__pin, __value) _Pragma("GCC error \"Not supported on linux-generic\"") + +void hwInit(); +inline void hwReadConfigBlock(void* buf, void* addr, size_t length); +inline void hwWriteConfigBlock(void* buf, void* addr, size_t length); +inline uint8_t hwReadConfig(int addr); +inline void hwWriteConfig(int addr, uint8_t value); +inline void hwRandomNumberInit(); +inline unsigned long hwMillis(); + +#ifdef MY_RF24_IRQ_PIN +static pthread_mutex_t hw_mutex = PTHREAD_MUTEX_INITIALIZER; + +static __inline__ void __hwUnlock(const uint8_t *__s) +{ + pthread_mutex_unlock(&hw_mutex); + (void)__s; +} + +static __inline__ void __hwLock() +{ + pthread_mutex_lock(&hw_mutex); +} +#endif + +#if defined(DOXYGEN) +#define ATOMIC_BLOCK_CLEANUP +#elif defined(MY_RF24_IRQ_PIN) +#define ATOMIC_BLOCK_CLEANUP uint8_t __atomic_loop \ + __attribute__((__cleanup__( __hwUnlock ))) = 1 +#else +#define ATOMIC_BLOCK_CLEANUP +#endif /* DOXYGEN */ + +#if defined(DOXYGEN) +#define ATOMIC_BLOCK +#elif defined(MY_RF24_IRQ_PIN) +#define ATOMIC_BLOCK for ( ATOMIC_BLOCK_CLEANUP, __hwLock(); \ + __atomic_loop ; __atomic_loop = 0 ) +#else +#define ATOMIC_BLOCK +#endif /* DOXYGEN */ + +#ifndef DOXYGEN +#define MY_CRITICAL_SECTION ATOMIC_BLOCK +#endif /* DOXYGEN */ + +#endif diff --git a/core/MyHwRPi.cpp b/core/MyHwRPi.cpp new file mode 100644 index 000000000..989af5fa9 --- /dev/null +++ b/core/MyHwRPi.cpp @@ -0,0 +1,146 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2016 Sensnology AB + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#include "MyHwRPi.h" + +#include +#include +#include "SoftEeprom.h" + +static SoftEeprom eeprom = SoftEeprom(MY_LINUX_CONFIG_FILE, 1024); // ATMega328 has 1024 bytes + +void hwInit() +{ +#ifdef MY_GATEWAY_SERIAL + MY_SERIALDEVICE.begin(MY_BAUD_RATE); +#ifdef MY_LINUX_SERIAL_GROUPNAME + if (!MY_SERIALDEVICE.setGroupPerm(MY_LINUX_SERIAL_GROUPNAME)) { + logError("Unable to change permission for serial port device.\n"); + exit(1); + } +#endif +#endif +} + +void hwReadConfigBlock(void* buf, void* adr, size_t length) +{ + eeprom.readBlock(buf, adr, length); +} + +void hwWriteConfigBlock(void* buf, void* adr, size_t length) +{ + eeprom.writeBlock(buf, adr, length); +} + +uint8_t hwReadConfig(int adr) +{ + return eeprom.readByte(adr); +} + +void hwWriteConfig(int adr, uint8_t value) +{ + eeprom.writeByte(adr, value); +} + +void hwRandomNumberInit() +{ + randomSeed(time(NULL)); +} + +unsigned long hwMillis() +{ + return millis(); +} + +// Not supported! +int8_t hwSleep(unsigned long ms) +{ + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +// Not supported! +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ + (void)interrupt; + (void)mode; + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +// Not supported! +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, + unsigned long ms) +{ + (void)interrupt1; + (void)mode1; + (void)interrupt2; + (void)mode2; + (void)ms; + + return MY_SLEEP_NOT_POSSIBLE; +} + +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) +uint16_t hwCPUVoltage() +{ + // TODO: Not supported! + return 0; +} + +uint16_t hwCPUFrequency() +{ + // TODO: Not supported! + return 0; +} + +uint16_t hwFreeMem() +{ + // TODO: Not supported! + return 0; +} +#endif + +#ifdef MY_DEBUG +void hwDebugPrint(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vlogDebug(fmt, args); + va_end(args); +} +#endif + +void hwDigitalWrite(uint8_t pin, uint8_t value) +{ + digitalWrite(pin, value); +} + +int hwDigitalRead(uint8_t pin) +{ + return digitalRead(pin); +} + +void hwPinMode(uint8_t pin, uint8_t mode) +{ + pinMode(pin, mode); +} diff --git a/core/MyHwRPi.h b/core/MyHwRPi.h new file mode 100644 index 000000000..2e39d136a --- /dev/null +++ b/core/MyHwRPi.h @@ -0,0 +1,55 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2016 Sensnology AB + * Full contributor list: https://github.com/mysensors/MySensors/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + +#ifndef MyHwRPi_h +#define MyHwRPi_h + +#include +#include +#include "MyHwLinuxGeneric.h" +#include "log.h" +#include "bcm2835.h" + +class Bcm2835Init +{ +public: + Bcm2835Init() + { + if (!bcm2835_init()) { + logError("Failed to initialized bcm2835.\n"); + exit(1); + } + } + ~Bcm2835Init() + { + bcm2835_close(); + } +}; +Bcm2835Init bcm2835Init; + +#undef hwDigitalWrite +inline void hwDigitalWrite(uint8_t, uint8_t); + +#undef hwDigitalRead +inline int hwDigitalRead(uint8_t); + +#undef hwPinMode +inline void hwPinMode(uint8_t, uint8_t); + +#endif diff --git a/core/MyHwSAMD.cpp b/core/MyHwSAMD.cpp index 115400a28..b5c220757 100644 --- a/core/MyHwSAMD.cpp +++ b/core/MyHwSAMD.cpp @@ -42,142 +42,187 @@ ISR (WDT_vect) -void i2c_eeprom_write_byte(unsigned int eeaddress, byte data ) { - int rdata = data; - Wire.beginTransmission(I2C_EEP_ADDRESS); - Wire.write((int)(eeaddress >> 8)); // MSB - Wire.write((int)(eeaddress & 0xFF)); // LSB - Wire.write(rdata); - Wire.endTransmission(); -} - -byte i2c_eeprom_read_byte(unsigned int eeaddress ) { - byte rdata = 0xFF; - Wire.beginTransmission(I2C_EEP_ADDRESS); - Wire.write((int)(eeaddress >> 8)); // MSB - Wire.write((int)(eeaddress & 0xFF)); // LSB - Wire.endTransmission(); - Wire.requestFrom(I2C_EEP_ADDRESS,1); - if (Wire.available()) rdata = Wire.read(); - return rdata; +void i2c_eeprom_write_byte(unsigned int eeaddress, byte data ) +{ + int rdata = data; + Wire.beginTransmission(I2C_EEP_ADDRESS); + Wire.write((int)(eeaddress >> 8)); // MSB + Wire.write((int)(eeaddress & 0xFF)); // LSB + Wire.write(rdata); + Wire.endTransmission(); +} + +byte i2c_eeprom_read_byte(unsigned int eeaddress ) +{ + byte rdata = 0xFF; + Wire.beginTransmission(I2C_EEP_ADDRESS); + Wire.write((int)(eeaddress >> 8)); // MSB + Wire.write((int)(eeaddress & 0xFF)); // LSB + Wire.endTransmission(); + Wire.requestFrom(I2C_EEP_ADDRESS,1); + if (Wire.available()) { + rdata = Wire.read(); + } + return rdata; } void hwReadConfigBlock(void* buf, void* adr, size_t length) { - uint8_t* dst = static_cast(buf); - int offs = reinterpret_cast(adr); - while (length-- > 0) - { - *dst++ = i2c_eeprom_read_byte(offs++); - } + uint8_t* dst = static_cast(buf); + int offs = reinterpret_cast(adr); + while (length-- > 0) { + *dst++ = i2c_eeprom_read_byte(offs++); + } } void hwWriteConfigBlock(void* buf, void* adr, size_t length) { - uint8_t* src = static_cast(buf); - int offs = reinterpret_cast(adr); - while (length-- > 0) - { - i2c_eeprom_write_byte(offs++, *src++); - } + uint8_t* src = static_cast(buf); + int offs = reinterpret_cast(adr); + while (length-- > 0) { + i2c_eeprom_write_byte(offs++, *src++); + } } uint8_t hwReadConfig(int adr) { - uint8_t value; - hwReadConfigBlock(&value, reinterpret_cast(adr), 1); - return value; + uint8_t value; + hwReadConfigBlock(&value, reinterpret_cast(adr), 1); + return value; } void hwWriteConfig(int adr, uint8_t value) { - uint8_t curr = hwReadConfig(adr); - if (curr != value) - { - hwWriteConfigBlock(&value, reinterpret_cast(adr), 1); - } + uint8_t curr = hwReadConfig(adr); + if (curr != value) { + hwWriteConfigBlock(&value, reinterpret_cast(adr), 1); + } } -void hwInit() { - MY_SERIALDEVICE.begin(MY_BAUD_RATE); - #ifndef MY_GATEWAY_W5100 - while (!MY_SERIALDEVICE) {} - #endif - Wire.begin(); -} - -void hwWatchdogReset() { - // TODO: Not supported! +void hwInit() +{ + MY_SERIALDEVICE.begin(MY_BAUD_RATE); +#if defined(MY_GATEWAY_SERIAL) + while (!MY_SERIALDEVICE) {} +#endif + Wire.begin(); } -void hwReboot() { - // TODO: Not supported! +void hwWatchdogReset() +{ + // TODO: Not supported! } -int8_t hwSleep(unsigned long ms) { - // TODO: Not supported! - (void)ms; - return -2; +void hwReboot() +{ + NVIC_SystemReset(); + while (true); } -int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { - // TODO: Not supported! - (void)interrupt; - (void)mode; - (void)ms; - return -2; +int8_t hwSleep(unsigned long ms) +{ + // TODO: Not supported! + (void)ms; + return MY_SLEEP_NOT_POSSIBLE; } -int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { - // TODO: Not supported! - (void)interrupt1; - (void)mode1; - (void)interrupt2; - (void)mode2; - (void)ms; - return -2; +int8_t hwSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) +{ + // TODO: Not supported! + (void)interrupt; + (void)mode; + (void)ms; + return MY_SLEEP_NOT_POSSIBLE; } -uint16_t hwCPUVoltage() { +int8_t hwSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, + unsigned long ms) +{ // TODO: Not supported! - return 0; + (void)interrupt1; + (void)mode1; + (void)interrupt2; + (void)mode2; + (void)ms; + return MY_SLEEP_NOT_POSSIBLE; } - -uint16_t hwCPUFrequency() { - // TODO: Not supported! - return 0; + +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) +uint16_t hwCPUVoltage() +{ + + // disable ADC + while (ADC->STATUS.bit.SYNCBUSY); + ADC->CTRLA.bit.ENABLE = 0x00; + + // internal 1V reference (default) + analogReference(AR_INTERNAL1V0); + // 12 bit resolution (default) + analogWriteResolution(12); + // MUXp 0x1B = SCALEDIOVCC/4 => connected to Vcc + ADC->INPUTCTRL.bit.MUXPOS = 0x1B ; + + // enable ADC + while (ADC->STATUS.bit.SYNCBUSY); + ADC->CTRLA.bit.ENABLE = 0x01; + // start conversion + while (ADC->STATUS.bit.SYNCBUSY); + ADC->SWTRIG.bit.START = 1; + // clear the Data Ready flag + ADC->INTFLAG.bit.RESRDY = 1; + // start conversion again, since The first conversion after the reference is changed must not be used. + while (ADC->STATUS.bit.SYNCBUSY); + ADC->SWTRIG.bit.START = 1; + + // waiting for conversion to complete + while (!ADC->INTFLAG.bit.RESRDY); + const uint32_t valueRead = ADC->RESULT.reg; + + // disable ADC + while (ADC->STATUS.bit.SYNCBUSY); + ADC->CTRLA.bit.ENABLE = 0x00; + + return valueRead * 4; +} + +uint16_t hwCPUFrequency() +{ + // TODO: currently reporting compile time frequency (in 1/10MHz) + return F_CPU / 100000UL; } - -uint16_t hwFreeMem() { + +uint16_t hwFreeMem() +{ // TODO: Not supported! return 0; } +#endif #ifdef MY_DEBUG -void hwDebugPrint(const char *fmt, ... ) { - if (MY_SERIALDEVICE) { - char fmtBuffer[300]; - #ifdef MY_GATEWAY_FEATURE +void hwDebugPrint(const char *fmt, ... ) +{ + if (MY_SERIALDEVICE) { + char fmtBuffer[MY_SERIAL_OUTPUT_SIZE]; +#ifdef MY_GATEWAY_FEATURE // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) - snprintf(fmtBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); + snprintf(fmtBuffer, sizeof(fmtBuffer), PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); MY_SERIALDEVICE.print(fmtBuffer); - #endif - va_list args; - va_start (args, fmt ); - va_end (args); - #ifdef MY_GATEWAY_FEATURE +#endif + va_list args; + va_start (args, fmt ); +#ifdef MY_GATEWAY_FEATURE // Truncate message if this is gateway node - vsnprintf(fmtBuffer, 60, fmt, args); - fmtBuffer[59] = '\n'; - fmtBuffer[60] = '\0'; - #else - vsnprintf(fmtBuffer, 299, fmt, args); - #endif - va_end (args); - MY_SERIALDEVICE.print(fmtBuffer); -// MY_SERIALDEVICE.flush(); - - //MY_SERIALDEVICE.write(freeRam()); - } + vsnprintf(fmtBuffer, sizeof(fmtBuffer), fmt, args); + fmtBuffer[sizeof(fmtBuffer) - 2] = '\n'; + fmtBuffer[sizeof(fmtBuffer) - 1] = '\0'; +#else + vsnprintf(fmtBuffer, sizeof(fmtBuffer), fmt, args); +#endif + va_end (args); + MY_SERIALDEVICE.print(fmtBuffer); + // MY_SERIALDEVICE.flush(); + + //MY_SERIALDEVICE.write(freeRam()); + } } #endif diff --git a/core/MyHwSAMD.h b/core/MyHwSAMD.h index fcc5a9d79..647de3805 100644 --- a/core/MyHwSAMD.h +++ b/core/MyHwSAMD.h @@ -34,28 +34,48 @@ #define max(a,b) ((a)>(b)?(a):(b)) #define snprintf_P(s, f, ...) snprintf((s), (f), __VA_ARGS__) +uint8_t configBlock[1024]; // Define these as macros to save valuable space +#define hwDigitalWrite(__pin, __value) digitalWrite(__pin, __value) +#define hwDigitalRead(__pin) digitalRead(__pin) +#define hwPinMode(__pin, __value) pinMode(__pin, __value) +#define hwMillis() millis() +#define hwRandomNumberInit() randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN)) -uint8_t configBlock[1024]; -#define hwDigitalWrite(__pin, __value) (digitalWrite(__pin, __value)) void hwInit(); void hwWatchdogReset(); void hwReboot(); -#define hwMillis() millis() - void hwReadConfigBlock(void* buf, void* adr, size_t length); void hwWriteConfigBlock(void* buf, void* adr, size_t length); void hwWriteConfig(int adr, uint8_t value); uint8_t hwReadConfig(int adr); +#ifndef MY_SERIALDEVICE #define MY_SERIALDEVICE SerialUSB +#endif + +/** + * Disable all interrupts. + * Helper function for MY_CRITICAL_SECTION. + */ +static __inline__ uint8_t __disableIntsRetVal(void) +{ + __disable_irq(); + return 1; +} + +/** + * Restore priority mask register. + * Helper function for MY_CRITICAL_SECTION. + */ +static __inline__ void __priMaskRestore(const uint32_t *priMask) +{ + __set_PRIMASK(*priMask); +} +#ifndef DOXYGEN +#define MY_CRITICAL_SECTION for ( uint32_t __savePriMask __attribute__((__cleanup__(__priMaskRestore))) = __get_PRIMASK(), __ToDo = __disableIntsRetVal(); __ToDo ; __ToDo = 0 ) +#endif /* DOXYGEN */ -/* -#define hwReadConfigBlock(__buf, __adr, __length) ( __length = __length) -#define hwWriteConfigBlock(__buf, __adr, __length) ( __length = __length) -#define hwWriteConfig(__adr, __value) ( __value = __value) -#define hwReadConfig(__adr) (0) -*/ #endif // #ifdef ARDUINO_ARCH_SAMD diff --git a/core/MyInclusionMode.cpp b/core/MyInclusionMode.cpp index e55612f3b..a3a2319f8 100644 --- a/core/MyInclusionMode.cpp +++ b/core/MyInclusionMode.cpp @@ -19,38 +19,44 @@ #include "MyInclusionMode.h" +// global variables +extern MyMessage _msgTmp; + unsigned long _inclusionStartTime; bool _inclusionMode; -inline void inclusionInit() { +inline void inclusionInit() +{ _inclusionMode = false; - #if defined(MY_INCLUSION_BUTTON_FEATURE) - // Setup digital in that triggers inclusion mode - pinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT); - digitalWrite(MY_INCLUSION_MODE_BUTTON_PIN, HIGH); - #endif +#if defined(MY_INCLUSION_BUTTON_FEATURE) + // Setup digital in that triggers inclusion mode + hwPinMode(MY_INCLUSION_MODE_BUTTON_PIN, INPUT); + hwDigitalWrite(MY_INCLUSION_MODE_BUTTON_PIN, HIGH); +#endif } -void inclusionModeSet(bool newMode) { - if (newMode != _inclusionMode) { - _inclusionMode = newMode; - // Send back mode change to controller - gatewayTransportSend(buildGw(_msg, I_INCLUSION_MODE).set((uint8_t)(_inclusionMode?1:0))); - if (_inclusionMode) { - _inclusionStartTime = hwMillis(); - } - } +void inclusionModeSet(bool newMode) +{ + if (newMode != _inclusionMode) { + _inclusionMode = newMode; + // Send back mode change to controller + gatewayTransportSend(buildGw(_msgTmp, I_INCLUSION_MODE).set((uint8_t)(_inclusionMode?1:0))); + if (_inclusionMode) { + _inclusionStartTime = hwMillis(); + } + } } -inline void inclusionProcess() { - #ifdef MY_INCLUSION_BUTTON_FEATURE - if (!_inclusionMode && digitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == MY_INCLUSION_BUTTON_PRESSED) { +inline void inclusionProcess() +{ +#ifdef MY_INCLUSION_BUTTON_FEATURE + if (!_inclusionMode && hwDigitalRead(MY_INCLUSION_MODE_BUTTON_PIN) == MY_INCLUSION_BUTTON_PRESSED) { // Start inclusion mode inclusionModeSet(true); } - #endif +#endif if (_inclusionMode && hwMillis()-_inclusionStartTime>MY_INCLUSION_MODE_DURATION*1000L) { // inclusionTimeInMinutes minute(s) has passed.. stop inclusion mode diff --git a/core/MyIndication.cpp b/core/MyIndication.cpp index 8e5e65b86..72faa2285 100644 --- a/core/MyIndication.cpp +++ b/core/MyIndication.cpp @@ -18,25 +18,29 @@ */ #include "MyIndication.h" -#ifdef MY_LEDS_BLINKING_FEATURE +#if defined(MY_DEFAULT_TX_LED_PIN)|| defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) #include "MyLeds.h" #endif void setIndication( const indication_t ind ) { -#ifdef MY_LEDS_BLINKING_FEATURE - if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind)) - { - ledsBlinkTx(1); - } else if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind)) - { - ledsBlinkRx(1); - } else if (ind > INDICATION_ERR_START) - { - // Number of blinks indicates which error occurred. - ledsBlinkErr(ind-INDICATION_ERR_START); - } +#if defined(MY_DEFAULT_TX_LED_PIN) + if ((INDICATION_TX == ind) || (INDICATION_GW_TX == ind)) { + ledsBlinkTx(1); + } else #endif - if (indication) - indication(ind); +#if defined(MY_DEFAULT_RX_LED_PIN) + if ((INDICATION_RX == ind) || (INDICATION_GW_RX == ind)) { + ledsBlinkRx(1); + } else +#endif +#if defined(MY_DEFAULT_ERR_LED_PIN) + if (ind > INDICATION_ERR_START) { + // Number of blinks indicates which error occurred. + ledsBlinkErr(ind-INDICATION_ERR_START); + } +#endif + if (indication) { + indication(ind); + } } diff --git a/core/MyIndication.h b/core/MyIndication.h index ab2bbbc02..0e2f8ea2b 100644 --- a/core/MyIndication.h +++ b/core/MyIndication.h @@ -24,41 +24,44 @@ * Indication type */ typedef enum { - INDICATION_TX = 0, //!< Sent a message. - INDICATION_RX, //!< Received a message. - - INDICATION_GW_TX, //!< Gateway transmit message. - INDICATION_GW_RX, //!< Gateway receive message. - - INDICATION_FIND_PARENT, //!< Start finding parent node. - INDICATION_GOT_PARENT, //!< Found parent node. - INDICATION_REQ_NODEID, //!< Request node ID. - INDICATION_GOT_NODEID, //!< Got a node ID. - INDICATION_REQ_REGISTRATION, //!< Request node registration. - INDICATION_GOT_REGISTRATION, //!< Got registration reponse. - INDICATION_REBOOT, //!< Rebooting node. - INDICATION_PRESENT, //!< Presenting node to gateway. - INDICATION_CLEAR_ROUTING, //!< Clear rrouting table requested. - INDICATION_SLEEP, //!< Node goes to sleep. - INDICATION_WAKEUP, //!< Node just woke from sleep. - INDICATION_FW_UPDATE_START, //!< Start of OTA firmware update process. - INDICATION_FW_UPDATE_RX, //!< Received a piece of firmware data. + INDICATION_TX = 0, //!< Sent a message. + INDICATION_RX, //!< Received a message. - INDICATION_ERR_START = 100, - INDICATION_ERR_TX, //!< Failed to transmit message. + INDICATION_GW_TX, //!< Gateway transmit message. + INDICATION_GW_RX, //!< Gateway receive message. + + INDICATION_FIND_PARENT, //!< Start finding parent node. + INDICATION_GOT_PARENT, //!< Found parent node. + INDICATION_REQ_NODEID, //!< Request node ID. + INDICATION_GOT_NODEID, //!< Got a node ID. + INDICATION_CHECK_UPLINK, //!< Check uplink + INDICATION_REQ_REGISTRATION, //!< Request node registration. + INDICATION_GOT_REGISTRATION, //!< Got registration reponse. + INDICATION_REBOOT, //!< Rebooting node. + INDICATION_PRESENT, //!< Presenting node to gateway. + INDICATION_CLEAR_ROUTING, //!< Clear routing table requested. + INDICATION_SLEEP, //!< Node goes to sleep. + INDICATION_WAKEUP, //!< Node just woke from sleep. + INDICATION_FW_UPDATE_START, //!< Start of OTA firmware update process. + INDICATION_FW_UPDATE_RX, //!< Received a piece of firmware data. + + INDICATION_ERR_START = 100, + INDICATION_ERR_TX, //!< Failed to transmit message. INDICATION_ERR_TRANSPORT_FAILURE, //!< Transport failure. - INDICATION_ERR_INIT_TRANSPORT, //!< MySensors transport hardware (radio) init failure. - INDICATION_ERR_FIND_PARENT, //!< Failed to find parent node. - INDICATION_ERR_GET_NODEID, //!< Failed to receive node ID. - INDICATION_ERR_SIGN, //!< Error signing. - INDICATION_ERR_VERSION, //!< Protocol version mismatch. - INDICATION_ERR_NET_FULL, //!< Network full. All node ID's are taken. - INDICATION_ERR_INIT_GWTRANSPORT, //!< Gateway transport hardware init failure. - INDICATION_ERR_LOCKED, //!< Node is locked. - INDICATION_ERR_FW_FLASH_INIT, //!< Firmware update flash initialisation failure. - INDICATION_ERR_FW_TIMEOUT, //!< Firmware update timeout. - INDICATION_ERR_FW_CHECKSUM, //!< Firmware update checksum mismatch. - INDICATION_ERR_END + INDICATION_ERR_INIT_TRANSPORT, //!< MySensors transport hardware (radio) init failure. + INDICATION_ERR_FIND_PARENT, //!< Failed to find parent node. + INDICATION_ERR_GET_NODEID, //!< Failed to receive node ID. + INDICATION_ERR_CHECK_UPLINK, //!< Failed to check uplink + INDICATION_ERR_SIGN, //!< Error signing. + INDICATION_ERR_LENGTH, //!< Invalid message length. + INDICATION_ERR_VERSION, //!< Protocol version mismatch. + INDICATION_ERR_NET_FULL, //!< Network full. All node ID's are taken. + INDICATION_ERR_INIT_GWTRANSPORT, //!< Gateway transport hardware init failure. + INDICATION_ERR_LOCKED, //!< Node is locked. + INDICATION_ERR_FW_FLASH_INIT, //!< Firmware update flash initialisation failure. + INDICATION_ERR_FW_TIMEOUT, //!< Firmware update timeout. + INDICATION_ERR_FW_CHECKSUM, //!< Firmware update checksum mismatch. + INDICATION_ERR_END } indication_t; /** diff --git a/core/MyLeds.cpp b/core/MyLeds.cpp index 2974c183f..f27f71857 100644 --- a/core/MyLeds.cpp +++ b/core/MyLeds.cpp @@ -26,8 +26,7 @@ static uint8_t countRx; static uint8_t countTx; static uint8_t countErr; -static unsigned long prevTime = hwMillis() - LED_PROCESS_INTERVAL_MS; // Substract some, to make sure leds gets updated on first run. - +static unsigned long prevTime; inline void ledsInit() { @@ -37,46 +36,82 @@ inline void ledsInit() countErr = 0; // Setup led pins - pinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT); - pinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT); - pinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT); - - ledsProcess(); +#if defined(MY_DEFAULT_RX_LED_PIN) + hwPinMode(MY_DEFAULT_RX_LED_PIN, OUTPUT); +#endif +#if defined(MY_DEFAULT_TX_LED_PIN) + hwPinMode(MY_DEFAULT_TX_LED_PIN, OUTPUT); +#endif +#if defined(MY_DEFAULT_ERR_LED_PIN) + hwPinMode(MY_DEFAULT_ERR_LED_PIN, OUTPUT); +#endif + prevTime = hwMillis() - + LED_PROCESS_INTERVAL_MS; // Substract some, to make sure leds gets updated on first run. + ledsProcess(); } -void ledsProcess() { +void ledsProcess() +{ // Just return if it is not the time... - if ((hwMillis() - prevTime) < LED_PROCESS_INTERVAL_MS) + if ((hwMillis() - prevTime) < LED_PROCESS_INTERVAL_MS) { return; - - prevTime += LED_PROCESS_INTERVAL_MS; - - uint8_t state; - - // For an On/Off ratio of 4, the pattern repeated will be [on, on, on, off] - // until the counter becomes 0. - state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF; - hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state); - if (countRx) --countRx; - - state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF; - hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state); - if (countTx) --countTx; - - state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF; - hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state); - if (countErr) --countErr; + } + prevTime = hwMillis(); + + uint8_t state; + + // For an On/Off ratio of 4, the pattern repeated will be [on, on, on, off] + // until the counter becomes 0. +#if defined(MY_DEFAULT_RX_LED_PIN) + if (countRx) { + --countRx; + } + state = (countRx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF; + hwDigitalWrite(MY_DEFAULT_RX_LED_PIN, state); +#endif + +#if defined(MY_DEFAULT_TX_LED_PIN) + if (countTx) { + --countTx; + } + state = (countTx & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF; + hwDigitalWrite(MY_DEFAULT_TX_LED_PIN, state); +#endif + +#if defined(MY_DEFAULT_ERR_LED_PIN) + if (countErr) { + --countErr; + } + state = (countErr & (LED_ON_OFF_RATIO-1)) ? LED_ON : LED_OFF; + hwDigitalWrite(MY_DEFAULT_ERR_LED_PIN, state); +#endif } -void ledsBlinkRx(uint8_t cnt) { - if (!countRx) { countRx = cnt*LED_ON_OFF_RATIO; } +void ledsBlinkRx(uint8_t cnt) +{ + if (!countRx) { + countRx = cnt*LED_ON_OFF_RATIO; + } + ledsProcess(); } -void ledsBlinkTx(uint8_t cnt) { - if(!countTx) { countTx = cnt*LED_ON_OFF_RATIO; } +void ledsBlinkTx(uint8_t cnt) +{ + if(!countTx) { + countTx = cnt*LED_ON_OFF_RATIO; + } + ledsProcess(); } -void ledsBlinkErr(uint8_t cnt) { - if(!countErr) { countErr = cnt*LED_ON_OFF_RATIO; } +void ledsBlinkErr(uint8_t cnt) +{ + if(!countErr) { + countErr = cnt*LED_ON_OFF_RATIO; + } + ledsProcess(); } +bool ledsBlinking() +{ + return countRx || countTx || countErr; +} diff --git a/core/MyLeds.h b/core/MyLeds.h index 18e138d17..ab78ab2e6 100644 --- a/core/MyLeds.h +++ b/core/MyLeds.h @@ -29,26 +29,31 @@ #define LED_OFF 0x1 #endif -#ifdef MY_LEDS_BLINKING_FEATURE - #define ledBlinkTx(x,...) ledsBlinkTx(x) - #define ledBlinkRx(x,...) ledsBlinkRx(x) - #define ledBlinkErr(x,...) ledsBlinkErr(x) +#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) +#define ledBlinkTx(x,...) ledsBlinkTx(x) +#define ledBlinkRx(x,...) ledsBlinkRx(x) +#define ledBlinkErr(x,...) ledsBlinkErr(x) - /** - * Blink with LEDs - * @param cnt how many blink cycles to keep the LED on. Default cycle is 300ms - */ - void ledsInit(); - void ledsBlinkRx(uint8_t cnt); - void ledsBlinkTx(uint8_t cnt); - void ledsBlinkErr(uint8_t cnt); - void ledsProcess(); // do the actual blinking +/** + * Blink with LEDs + * @param cnt how many blink cycles to keep the LED on. Default cycle is 300ms + */ +void ledsInit(); +void ledsBlinkRx(uint8_t cnt); +void ledsBlinkTx(uint8_t cnt); +void ledsBlinkErr(uint8_t cnt); +void ledsProcess(); // do the actual blinking +/** + * Test if any LED is currently blinking. + * @return true when one or more LEDs are blinking, false otherwise. + */ +bool ledsBlinking(); #else - // Remove led functions if feature is disabled - #define ledBlinkTx(x,...) - #define ledBlinkRx(x,...) - #define ledBlinkErr(x,...) +// Remove led functions if feature is disabled +#define ledBlinkTx(x,...) +#define ledBlinkRx(x,...) +#define ledBlinkErr(x,...) #endif #endif diff --git a/core/MyMainDefault.cpp b/core/MyMainDefault.cpp index b1934b94e..0e006fd5a 100644 --- a/core/MyMainDefault.cpp +++ b/core/MyMainDefault.cpp @@ -1,19 +1,46 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ + // Initialize library and handle sketch functions like we want to -int main(void) { +extern "C" void __libc_init_array(void); + +int main(void) +{ init(); - #if defined(USBCON) - #if defined(ARDUINO_ARCH_SAMD) - USBDevice.init(); - #endif - USBDevice.attach(); - #endif +#if defined(USBCON) +#if defined(ARDUINO_ARCH_SAMD) + __libc_init_array(); + USBDevice.init(); +#endif + USBDevice.attach(); +#endif _begin(); // Startup MySensors library for(;;) { _process(); // Process incoming data - if (loop) loop(); // Call sketch loop - if (serialEventRun) serialEventRun(); + if (loop) { + loop(); // Call sketch loop + } + if (serialEventRun) { + serialEventRun(); + } } return 0; } diff --git a/core/MyMainESP8266.cpp b/core/MyMainESP8266.cpp index ee800a0a7..608a60f02 100644 --- a/core/MyMainESP8266.cpp +++ b/core/MyMainESP8266.cpp @@ -1,3 +1,21 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ //This may be used to change user task stack size: //#define CONT_STACKSIZE 4096 @@ -20,27 +38,31 @@ extern "C" { struct rst_info resetInfo; extern "C" { - extern const uint32_t __attribute__((section(".ver_number"))) core_version = ARDUINO_ESP8266_GIT_VER; + extern const uint32_t __attribute__((section(".ver_number"))) core_version = + ARDUINO_ESP8266_GIT_VER; const char* core_release = #ifdef ARDUINO_ESP8266_RELEASE - ARDUINO_ESP8266_RELEASE; + ARDUINO_ESP8266_RELEASE; #else - NULL; + NULL; #endif } // extern "C" -int atexit(void(*func)()) { +int atexit(void(*func)()) +{ (void)func; return 0; } extern "C" void ets_update_cpu_frequency(int freqmhz); void initVariant() __attribute__((weak)); -void initVariant() { +void initVariant() +{ } void preloop_update_frequency() __attribute__((weak)); -void preloop_update_frequency() { +void preloop_update_frequency() +{ #if defined(F_CPU) && (F_CPU == 160000000L) REG_SET_BIT(0x3ff00014, BIT(0)); ets_update_cpu_frequency(160); @@ -55,37 +77,40 @@ static os_event_t g_loop_queue[LOOP_QUEUE_SIZE]; static uint32_t g_micros_at_task_start; -extern "C" void esp_yield() { +extern "C" void esp_yield() +{ if (cont_can_yield(&g_cont)) { cont_yield(&g_cont); } } -extern "C" void esp_schedule() { +extern "C" void esp_schedule() +{ ets_post(LOOP_TASK_PRIORITY, 0, 0); } -extern "C" void __yield() { +extern "C" void __yield() +{ if (cont_can_yield(&g_cont)) { esp_schedule(); esp_yield(); - } - else { + } else { panic(); } } extern "C" void yield(void) __attribute__((weak, alias("__yield"))); -extern "C" void optimistic_yield(uint32_t interval_us) { +extern "C" void optimistic_yield(uint32_t interval_us) +{ if (cont_can_yield(&g_cont) && - (system_get_time() - g_micros_at_task_start) > interval_us) - { + (system_get_time() - g_micros_at_task_start) > interval_us) { yield(); } } -static void loop_wrapper() { +static void loop_wrapper() +{ static bool setup_done = false; preloop_update_frequency(); if (!setup_done) { @@ -98,7 +123,8 @@ static void loop_wrapper() { esp_schedule(); } -static void loop_task(os_event_t *events) { +static void loop_task(os_event_t *events) +{ (void)events; g_micros_at_task_start = system_get_time(); cont_run(&g_cont, &loop_wrapper); @@ -107,10 +133,12 @@ static void loop_task(os_event_t *events) { } } -static void do_global_ctors(void) { +static void do_global_ctors(void) +{ void(**p)(void) = &__init_array_end; - while (p != &__init_array_start) + while (p != &__init_array_start) { (*--p)(); + } } extern "C" void __gdb_init() {} @@ -119,7 +147,8 @@ extern "C" void gdb_init(void) __attribute__((weak, alias("__gdb_init"))); extern "C" void __gdb_do_break() {} extern "C" void gdb_do_break(void) __attribute__((weak, alias("__gdb_do_break"))); -void init_done() { +void init_done() +{ system_set_os_print(1); gdb_init(); do_global_ctors(); @@ -127,7 +156,8 @@ void init_done() { } -extern "C" void user_init(void) { +extern "C" void user_init(void) +{ struct rst_info *rtc_info_ptr = system_get_rst_info(); memcpy((void *)&resetInfo, (void *)rtc_info_ptr, sizeof(resetInfo)); @@ -140,8 +170,8 @@ extern "C" void user_init(void) { cont_init(&g_cont); ets_task(loop_task, - LOOP_TASK_PRIORITY, g_loop_queue, - LOOP_QUEUE_SIZE); + LOOP_TASK_PRIORITY, g_loop_queue, + LOOP_QUEUE_SIZE); system_init_done_cb(&init_done); -} \ No newline at end of file +} diff --git a/core/MyMainLinux.cpp b/core/MyMainLinux.cpp new file mode 100644 index 000000000..5e875ecea --- /dev/null +++ b/core/MyMainLinux.cpp @@ -0,0 +1,450 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ +// Initialize library and handle sketch functions like we want to + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "log.h" +#include "MySensorsCore.h" + +void handle_sigint(int sig) +{ + if (sig == SIGINT) { + logNotice("Received SIGINT\n\n"); + } else if (sig == SIGTERM) { + logNotice("Received SIGTERM\n\n"); + } else { + return; + } + +#ifdef MY_RF24_IRQ_PIN + detachInterrupt(MY_RF24_IRQ_PIN); +#endif + +#if defined(MY_GATEWAY_SERIAL) + MY_SERIALDEVICE.end(); +#endif + + closelog(); + + exit(0); +} + +static int daemonize(void) +{ + pid_t pid, sid; + + /* Fork off the parent process */ + pid = fork(); + if (pid < 0) { + logError("fork: %s\n", strerror(errno)); + return -1; + } + /* If we got a good PID, then we can exit the parent process. */ + if (pid > 0) { + exit(EXIT_SUCCESS); + } + + /* At this point we are executing as the child process */ + + /* Change the file mode mask */ + umask(0); + + /* Create a new SID for the child process */ + sid = setsid(); + if (sid < 0) { + logError("setsid: %s\n", strerror(errno)); + return -1; + } + + /* Change the current working directory. This prevents the current + directory from being locked; hence not being able to remove it. */ + if ((chdir("/")) < 0) { + logError("chdir(\"/\"): %s\n", strerror(errno)); + return -1; + } + + if (freopen( "/dev/null", "r", stdin) == NULL) { + logError("freopen: %s\n", strerror(errno)); + } + if (freopen( "/dev/null", "r", stdout) == NULL) { + logError("freopen: %s\n", strerror(errno)); + } + if (freopen( "/dev/null", "r", stderr) == NULL) { + logError("freopen: %s\n", strerror(errno)); + } + + return 0; +} + +void print_usage() +{ + printf("Usage: mysgw [options]\n\n" \ + "Options:\n" \ + " -h, --help Display a short summary of all program options.\n" \ + " -d, --debug Enable debug.\n" \ + " -b, --background Run as a background process.\n" + " --gen-soft-hmac-key Generate and print a soft hmac key.\n" + " --gen-soft-serial-key Generate and print a soft serial key.\n" + " --gen-aes-key Generate and print an aes encryption key.\n" + " --print-soft-hmac-key Print the soft hmac key from the config file.\n" + " --print-soft-serial-key Print the soft serial key from the config file.\n" + " --print-aes-key Print the aes encryption key from the config file.\n" + " --set-soft-hmac-key Write a soft hmac key to the config file.\n" + " --set-soft-serial-key Write a soft serial key to the config file.\n" + " --set-aes-key Write an aes encryption key to the config file.\n"); +} + +void print_soft_sign_hmac_key(uint8_t *key_ptr = NULL) +{ + uint8_t key[32]; + + if (key_ptr == NULL) { + hwReadConfigBlock(&key, reinterpret_castEEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32); + key_ptr = key; + } + + printf("SOFT_HMAC_KEY | "); + for (int i = 0; i < 32; i++) { + printf("%02X", key_ptr[i]); + } + printf("\n\n"); + + printf("The next line is intended to be used in SecurityPersonalizer.ino:\n"); + printf("#define MY_SOFT_HMAC_KEY "); + for (int i=0; i<32; i++) { + printf("%#02X", key_ptr[i]); + if (i < 31) { + printf(","); + } + } + printf("\n\n"); +} + +void generate_soft_sign_hmac_key() +{ + uint8_t key[32]; + + for (int i = 0; i < 32; i++) { + key[i] = random(256) ^ micros(); + unsigned long enter = hwMillis(); + while (hwMillis() - enter < (unsigned long)2); + } + + print_soft_sign_hmac_key(key); + + printf("To use this key, run mysgw with:\n" + " --set-soft-hmac-key="); + for (int i = 0; i < 32; i++) { + printf("%02X", key[i]); + } + printf("\n"); +} + +void set_soft_sign_hmac_key(char *key_str) +{ + uint8_t key[32]; + int n; + + if (strlen(key_str) != 64) { + printf("invalid key!\n"); + } else { + for (int i = 0; i < 64; ++i) { + char c = key_str[i]; + if (c <= '9') { + n = c - '0'; + } else if (c >= 'a') { + n = c - 'a' + 10; + } else { + n = c - 'A' + 10; + } + + if ((i & 0x1) == 0) { + key[i/2] = n * 16; + } else { + key[i/2] += n; + } + } + hwWriteConfigBlock(&key, reinterpret_castEEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32); + print_soft_sign_hmac_key(); + } +} + +void print_soft_sign_serial_key(uint8_t *key_ptr = NULL) +{ + uint8_t key[9]; + + if (key_ptr == NULL) { + hwReadConfigBlock(&key, reinterpret_castEEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9); + key_ptr = key; + } + + printf("SOFT_SERIAL | "); + for (int i = 0; i < 9; i++) { + printf("%02X", key_ptr[i]); + } + printf("\n\n"); + + printf("The next line is intended to be used in SecurityPersonalizer.ino:\n"); + printf("#define MY_SOFT_SERIAL "); + for (int i=0; i<9; i++) { + printf("%#02X", key_ptr[i]); + if (i < 8) { + printf(","); + } + } + printf("\n\n"); +} + +void generate_soft_sign_serial_key() +{ + uint8_t key[9]; + + for (int i = 0; i < 9; i++) { + key[i] = random(256) ^ micros(); + unsigned long enter = hwMillis(); + while (hwMillis() - enter < (unsigned long)2); + } + + print_soft_sign_serial_key(key); + + printf("To use this key, run mysgw with:\n" + " --set-soft-serial-key="); + for (int i = 0; i < 9; i++) { + printf("%02X", key[i]); + } + printf("\n"); +} + +void set_soft_sign_serial_key(char *key_str) +{ + uint8_t key[9]; + int n; + + if (strlen(key_str) != 18) { + printf("invalid key!\n"); + } else { + for (int i = 0; i < 18; ++i) { + char c = key_str[i]; + if (c <= '9') { + n = c - '0'; + } else if (c >= 'a') { + n = c - 'a' + 10; + } else { + n = c - 'A' + 10; + } + + if ((i & 0x1) == 0) { + key[i/2] = n * 16; + } else { + key[i/2] += n; + } + } + hwWriteConfigBlock(&key, reinterpret_castEEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9); + print_soft_sign_serial_key(); + } +} + +void print_aes_key(uint8_t *key_ptr = NULL) +{ + uint8_t key[16]; + + if (key_ptr == NULL) { + hwReadConfigBlock(&key, reinterpret_castEEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); + key_ptr = key; + } + + printf("AES_KEY | "); + for (int i = 0; i < 16; i++) { + printf("%02X", key_ptr[i]); + } + printf("\n\n"); + + printf("The next line is intended to be used in SecurityPersonalizer.ino:\n"); + printf("#define MY_AES_KEY "); + for (int i=0; i<16; i++) { + printf("%#02X", key_ptr[i]); + if (i < 15) { + printf(","); + } + } + printf("\n\n"); +} + +void generate_aes_key() +{ + uint8_t key[16]; + + for (int i = 0; i < 16; i++) { + key[i] = random(256) ^ micros(); + unsigned long enter = hwMillis(); + while (hwMillis() - enter < (unsigned long)2); + } + + print_aes_key(key); + + printf("To use this key, run mysgw with:\n" + " --set-aes-key="); + for (int i = 0; i < 16; i++) { + printf("%02X", key[i]); + } + printf("\n"); +} + +void set_aes_key(char *key_str) +{ + uint8_t key[16]; + int n; + + if (strlen(key_str) != 32) { + printf("invalid key!\n"); + } else { + for (int i = 0; i < 32; ++i) { + char c = key_str[i]; + if (c <= '9') { + n = c - '0'; + } else if (c >= 'a') { + n = c - 'a' + 10; + } else { + n = c - 'A' + 10; + } + + if ((i & 0x1) == 0) { + key[i/2] = n * 16; + } else { + key[i/2] += n; + } + } + hwWriteConfigBlock(&key, reinterpret_castEEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); + print_aes_key(); + } +} + +int main(int argc, char *argv[]) +{ + int opt, log_opts, debug = 0, foreground = 1; + char *key = NULL; + + /* register the signal handler */ + signal(SIGINT, handle_sigint); + signal(SIGTERM, handle_sigint); + + hwRandomNumberInit(); + + static struct option long_options[] = { + {"help", no_argument, 0, 'h'}, + {"debug", no_argument, 0, 'd'}, + {"background", no_argument, 0, 'b'}, + {"gen-soft-hmac-key", no_argument, 0, 'A'}, + {"gen-soft-serial-key", no_argument, 0, 'B'}, + {"gen-aes-key", no_argument, 0, 'C'}, + {"print-soft-hmac-key", no_argument, 0, 'D'}, + {"print-soft-serial-key", no_argument, 0, 'E'}, + {"print-aes-key", no_argument, 0, 'F'}, + {"set-soft-hmac-key", required_argument, 0, 'G'}, + {"set-soft-serial-key", required_argument, 0, 'H'}, + {"set-aes-key", required_argument, 0, 'I'}, + {0, 0, 0, 0} + }; + + int long_index = 0; + while ((opt = getopt_long(argc, argv,"hdbABCDEFGHI", long_options, &long_index )) != -1) { + switch (opt) { + case 'h': + print_usage(); + exit(0); + case 'd': + debug = 1; + break; + case 'b': + foreground = 0; + break; + case 'A': + generate_soft_sign_hmac_key(); + exit(0); + case 'B': + generate_soft_sign_serial_key(); + exit(0); + case 'C': + generate_aes_key(); + exit(0); + case 'D': + print_soft_sign_hmac_key(); + exit(0); + case 'E': + print_soft_sign_serial_key(); + exit(0); + case 'F': + print_aes_key(); + exit(0); + case 'G': + key = strdup(optarg); + set_soft_sign_hmac_key(key); + exit(0); + case 'H': + key = strdup(optarg); + set_soft_sign_serial_key(key); + exit(0); + case 'I': + key = strdup(optarg); + set_aes_key(key); + exit(0); + default: + print_usage(); + exit(0); + } + } + + log_opts = LOG_CONS; + if (foreground && isatty(STDIN_FILENO)) { + // Also print syslog to stderror + log_opts |= LOG_PERROR; + } + if (!debug) { + // Ignore debug type messages + setlogmask(LOG_UPTO (LOG_INFO)); + } + logOpen(log_opts, LOG_USER); + + if (!foreground && !debug) { + if (daemonize() != 0) { + exit(EXIT_FAILURE); + } + } + + logInfo("Starting gateway...\n"); + logInfo("Protocol version - %s\n", MYSENSORS_LIBRARY_VERSION); + + _begin(); // Startup MySensors library + + for (;;) { + _process(); // Process incoming data + if (loop) { + loop(); // Call sketch loop + } + } + return 0; +} diff --git a/core/MyMessage.cpp b/core/MyMessage.cpp index 117dfe34b..c5baf5177 100644 --- a/core/MyMessage.cpp +++ b/core/MyMessage.cpp @@ -21,32 +21,53 @@ #include "MyMessage.h" #include #include +#include - -MyMessage::MyMessage() { - destination = 0; // Gateway is default destination +MyMessage::MyMessage() +{ + clear(); } -MyMessage::MyMessage(uint8_t _sensor, uint8_t _type) { - destination = 0; // Gateway is default destination +MyMessage::MyMessage(uint8_t _sensor, uint8_t _type) +{ + clear(); sensor = _sensor; - type = _type; + type = _type; +} + +void MyMessage::clear() +{ + last = 0u; + sender = 0u; + destination = 0u; // Gateway is default destination + version_length = 0u; + command_ack_payload = 0u; + type = 0u; + sensor = 0u; + (void)memset(data, 0u, sizeof(data)); + + // set message protocol version + miSetVersion(PROTOCOL_VERSION); } -bool MyMessage::isAck() const { +bool MyMessage::isAck() const +{ return miGetAck(); } -uint8_t MyMessage::getCommand() const { +uint8_t MyMessage::getCommand() const +{ return miGetCommand(); } /* Getters for payload converted to desired form */ -void* MyMessage::getCustom() const { +void* MyMessage::getCustom() const +{ return (void *)data; } -const char* MyMessage::getString() const { +const char* MyMessage::getString() const +{ uint8_t payloadType = miGetPayloadType(); if (payloadType == P_STRING) { return data; @@ -56,17 +77,19 @@ const char* MyMessage::getString() const { } // handles single character hex (0 - 15) -char MyMessage::i2h(uint8_t i) const { +char MyMessage::i2h(uint8_t i) const +{ uint8_t k = i & 0x0F; - if (k <= 9) + if (k <= 9) { return '0' + k; - else + } else { return 'A' + k - 10; + } } -char* MyMessage::getCustomString(char *buffer) const { - for (uint8_t i = 0; i < miGetLength(); i++) - { +char* MyMessage::getCustomString(char *buffer) const +{ + for (uint8_t i = 0; i < miGetLength(); i++) { buffer[i * 2] = i2h(data[i] >> 4); buffer[(i * 2) + 1] = i2h(data[i]); } @@ -74,7 +97,8 @@ char* MyMessage::getCustomString(char *buffer) const { return buffer; } -char* MyMessage::getStream(char *buffer) const { +char* MyMessage::getStream(char *buffer) const +{ uint8_t cmd = miGetCommand(); if ((cmd == C_STREAM) && (buffer != NULL)) { return getCustomString(buffer); @@ -83,7 +107,8 @@ char* MyMessage::getStream(char *buffer) const { } } -char* MyMessage::getString(char *buffer) const { +char* MyMessage::getString(char *buffer) const +{ uint8_t payloadType = miGetPayloadType(); if (buffer != NULL) { if (payloadType == P_STRING) { @@ -100,7 +125,7 @@ char* MyMessage::getString(char *buffer) const { } else if (payloadType == P_ULONG32) { ultoa(ulValue, buffer, 10); } else if (payloadType == P_FLOAT32) { - dtostrf(fValue,2,min(fPrecision, 8),buffer); + dtostrf(fValue,2,min(fPrecision, (uint8_t)8),buffer); } else if (payloadType == P_CUSTOM) { return getCustomString(buffer); } @@ -110,11 +135,13 @@ char* MyMessage::getString(char *buffer) const { } } -bool MyMessage::getBool() const { +bool MyMessage::getBool() const +{ return getByte(); } -uint8_t MyMessage::getByte() const { +uint8_t MyMessage::getByte() const +{ if (miGetPayloadType() == P_BYTE) { return data[0]; } else if (miGetPayloadType() == P_STRING) { @@ -125,7 +152,8 @@ uint8_t MyMessage::getByte() const { } -float MyMessage::getFloat() const { +float MyMessage::getFloat() const +{ if (miGetPayloadType() == P_FLOAT32) { return fValue; } else if (miGetPayloadType() == P_STRING) { @@ -135,7 +163,8 @@ float MyMessage::getFloat() const { } } -int32_t MyMessage::getLong() const { +int32_t MyMessage::getLong() const +{ if (miGetPayloadType() == P_LONG32) { return lValue; } else if (miGetPayloadType() == P_STRING) { @@ -145,7 +174,8 @@ int32_t MyMessage::getLong() const { } } -uint32_t MyMessage::getULong() const { +uint32_t MyMessage::getULong() const +{ if (miGetPayloadType() == P_ULONG32) { return ulValue; } else if (miGetPayloadType() == P_STRING) { @@ -155,8 +185,9 @@ uint32_t MyMessage::getULong() const { } } -int16_t MyMessage::getInt() const { - if (miGetPayloadType() == P_INT16) { +int16_t MyMessage::getInt() const +{ + if (miGetPayloadType() == P_INT16) { return iValue; } else if (miGetPayloadType() == P_STRING) { return atoi(data); @@ -165,8 +196,9 @@ int16_t MyMessage::getInt() const { } } -uint16_t MyMessage::getUInt() const { - if (miGetPayloadType() == P_UINT16) { +uint16_t MyMessage::getUInt() const +{ + if (miGetPayloadType() == P_UINT16) { return uiValue; } else if (miGetPayloadType() == P_STRING) { return atoi(data); @@ -176,34 +208,40 @@ uint16_t MyMessage::getUInt() const { } -MyMessage& MyMessage::setType(uint8_t _type) { +MyMessage& MyMessage::setType(uint8_t _type) +{ type = _type; return *this; } -MyMessage& MyMessage::setSensor(uint8_t _sensor) { +MyMessage& MyMessage::setSensor(uint8_t _sensor) +{ sensor = _sensor; return *this; } -MyMessage& MyMessage::setDestination(uint8_t _destination) { +MyMessage& MyMessage::setDestination(uint8_t _destination) +{ destination = _destination; return *this; } // Set payload -MyMessage& MyMessage::set(void* value, uint8_t length) { +MyMessage& MyMessage::set(void* value, uint8_t length) +{ + uint8_t payloadLength = value == NULL ? 0 : min(length, (uint8_t)MAX_PAYLOAD); + miSetLength(payloadLength); miSetPayloadType(P_CUSTOM); - miSetLength(length); - memcpy(data, value, min(length, MAX_PAYLOAD)); + memcpy(data, value, payloadLength); return *this; } -MyMessage& MyMessage::set(const char* value) { - uint8_t length = value == NULL ? 0 : min(strlen(value), MAX_PAYLOAD); +MyMessage& MyMessage::set(const char* value) +{ + uint8_t length = value == NULL ? 0 : min(strlen(value), (size_t)MAX_PAYLOAD); miSetLength(length); miSetPayloadType(P_STRING); - if (length) { + if (length) { strncpy(data, value, length); } // null terminate string @@ -211,21 +249,24 @@ MyMessage& MyMessage::set(const char* value) { return *this; } -MyMessage& MyMessage::set(bool value) { +MyMessage& MyMessage::set(bool value) +{ miSetLength(1); miSetPayloadType(P_BYTE); data[0] = value; return *this; } -MyMessage& MyMessage::set(uint8_t value) { +MyMessage& MyMessage::set(uint8_t value) +{ miSetLength(1); miSetPayloadType(P_BYTE); data[0] = value; return *this; } -MyMessage& MyMessage::set(float value, uint8_t decimals) { +MyMessage& MyMessage::set(float value, uint8_t decimals) +{ miSetLength(5); // 32 bit float + persi miSetPayloadType(P_FLOAT32); fValue=value; @@ -233,28 +274,32 @@ MyMessage& MyMessage::set(float value, uint8_t decimals) { return *this; } -MyMessage& MyMessage::set(uint32_t value) { +MyMessage& MyMessage::set(uint32_t value) +{ miSetPayloadType(P_ULONG32); miSetLength(4); ulValue = value; return *this; } -MyMessage& MyMessage::set(int32_t value) { +MyMessage& MyMessage::set(int32_t value) +{ miSetPayloadType(P_LONG32); miSetLength(4); lValue = value; return *this; } -MyMessage& MyMessage::set(uint16_t value) { +MyMessage& MyMessage::set(uint16_t value) +{ miSetPayloadType(P_UINT16); miSetLength(2); uiValue = value; return *this; } -MyMessage& MyMessage::set(int16_t value) { +MyMessage& MyMessage::set(int16_t value) +{ miSetPayloadType(P_INT16); miSetLength(2); iValue = value; diff --git a/core/MyMessage.h b/core/MyMessage.h index 2d37f78d6..822dc19df 100644 --- a/core/MyMessage.h +++ b/core/MyMessage.h @@ -32,13 +32,12 @@ #ifdef __cplusplus #include -#include #include #endif -#define PROTOCOL_VERSION 2 //!< The version of the protocol -#define MAX_MESSAGE_LENGTH 32 //!< The maximum size of a message (including header) -#define HEADER_SIZE 7 //!< The size of the header +#define PROTOCOL_VERSION (2u) //!< The version of the protocol +#define MAX_MESSAGE_LENGTH (32u) //!< The maximum size of a message (including header) +#define HEADER_SIZE (7u) //!< The size of the header #define MAX_PAYLOAD (MAX_MESSAGE_LENGTH - HEADER_SIZE) //!< The maximum size of a payload depends on #MAX_MESSAGE_LENGTH and #HEADER_SIZE /// @brief The command field (message-type) defines the overall properties of a message @@ -53,11 +52,11 @@ typedef enum { /// @brief Type of sensor (used when presenting sensors) typedef enum { S_DOOR = 0, //!< Door sensor, V_TRIPPED, V_ARMED - S_MOTION = 1, //!< Motion sensor, V_TRIPPED, V_ARMED + S_MOTION = 1, //!< Motion sensor, V_TRIPPED, V_ARMED S_SMOKE = 2, //!< Smoke sensor, V_TRIPPED, V_ARMED S_BINARY = 3, //!< Binary light or relay, V_STATUS, V_WATT S_LIGHT = 3, //!< \deprecated Same as S_BINARY, **** DEPRECATED, DO NOT USE **** - S_DIMMER = 4, //!< Dimmable light or fan device, V_STATUS (on/off), V_PERCENTAGE (dimmer level 0-100), V_WATT + S_DIMMER = 4, //!< Dimmable light or fan device, V_STATUS (on/off), V_PERCENTAGE (dimmer level 0-100), V_WATT S_COVER = 5, //!< Blinds or window cover, V_UP, V_DOWN, V_STOP, V_PERCENTAGE (open/close to a percentage) S_TEMP = 6, //!< Temperature sensor, V_TEMP S_HUM = 7, //!< Humidity sensor, V_HUM @@ -71,38 +70,38 @@ typedef enum { S_DISTANCE = 15, //!< Distance sensor, V_DISTANCE S_LIGHT_LEVEL = 16, //!< Light level sensor, V_LIGHT_LEVEL (uncalibrated in percentage), V_LEVEL (light level in lux) S_ARDUINO_NODE = 17, //!< Used (internally) for presenting a non-repeating Arduino node - S_ARDUINO_REPEATER_NODE = 18, //!< Used (internally) for presenting a repeating Arduino node + S_ARDUINO_REPEATER_NODE = 18, //!< Used (internally) for presenting a repeating Arduino node S_LOCK = 19, //!< Lock device, V_LOCK_STATUS S_IR = 20, //!< IR device, V_IR_SEND, V_IR_RECEIVE S_WATER = 21, //!< Water meter, V_FLOW, V_VOLUME S_AIR_QUALITY = 22, //!< Air quality sensor, V_LEVEL - S_CUSTOM = 23, //!< Custom sensor + S_CUSTOM = 23, //!< Custom sensor S_DUST = 24, //!< Dust sensor, V_LEVEL - S_SCENE_CONTROLLER = 25, //!< Scene controller device, V_SCENE_ON, V_SCENE_OFF. - S_RGB_LIGHT = 26, //!< RGB light. Send color component data using V_RGB. Also supports V_WATT + S_SCENE_CONTROLLER = 25, //!< Scene controller device, V_SCENE_ON, V_SCENE_OFF. + S_RGB_LIGHT = 26, //!< RGB light. Send color component data using V_RGB. Also supports V_WATT S_RGBW_LIGHT = 27, //!< RGB light with an additional White component. Send data using V_RGBW. Also supports V_WATT S_COLOR_SENSOR = 28, //!< Color sensor, send color information using V_RGB S_HVAC = 29, //!< Thermostat/HVAC device. V_HVAC_SETPOINT_HEAT, V_HVAC_SETPOINT_COLD, V_HVAC_FLOW_STATE, V_HVAC_FLOW_MODE, V_TEMP - S_MULTIMETER = 30, //!< Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE + S_MULTIMETER = 30, //!< Multimeter device, V_VOLTAGE, V_CURRENT, V_IMPEDANCE S_SPRINKLER = 31, //!< Sprinkler, V_STATUS (turn on/off), V_TRIPPED (if fire detecting device) S_WATER_LEAK = 32, //!< Water leak sensor, V_TRIPPED, V_ARMED S_SOUND = 33, //!< Sound sensor, V_TRIPPED, V_ARMED, V_LEVEL (sound level in dB) S_VIBRATION = 34, //!< Vibration sensor, V_TRIPPED, V_ARMED, V_LEVEL (vibration in Hz) - S_MOISTURE = 35, //!< Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?) + S_MOISTURE = 35, //!< Moisture sensor, V_TRIPPED, V_ARMED, V_LEVEL (water content or moisture in percentage?) S_INFO = 36, //!< LCD text device / Simple information device on controller, V_TEXT S_GAS = 37, //!< Gas meter, V_FLOW, V_VOLUME S_GPS = 38, //!< GPS Sensor, V_POSITION - S_WATER_QUALITY = 39 //!< V_TEMP, V_PH, V_ORP, V_EC, V_STATUS + S_WATER_QUALITY = 39 //!< V_TEMP, V_PH, V_ORP, V_EC, V_STATUS } mysensor_sensor; /// @brief Type of sensor data (for set/req/ack messages) typedef enum { V_TEMP = 0, //!< S_TEMP. Temperature S_TEMP, S_HEATER, S_HVAC V_HUM = 1, //!< S_HUM. Humidity - V_STATUS = 2, //!< S_BINARY, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting/reporting binary (on/off) status. 1=on, 0=off + V_STATUS = 2, //!< S_BINARY, S_DIMMER, S_SPRINKLER, S_HVAC, S_HEATER. Used for setting/reporting binary (on/off) status. 1=on, 0=off V_LIGHT = 2, //!< \deprecated Same as V_STATUS, **** DEPRECATED, DO NOT USE **** - V_PERCENTAGE = 3, //!< S_DIMMER. Used for sending a percentage value 0-100 (%). - V_DIMMER = 3, //!< \deprecated Same as V_PERCENTAGE, **** DEPRECATED, DO NOT USE **** + V_PERCENTAGE = 3, //!< S_DIMMER. Used for sending a percentage value 0-100 (%). + V_DIMMER = 3, //!< \deprecated Same as V_PERCENTAGE, **** DEPRECATED, DO NOT USE **** V_PRESSURE = 4, //!< S_BARO. Atmospheric Pressure V_FORECAST = 5, //!< S_BARO. Whether forecast. string of "stable", "sunny", "cloudy", "unstable", "thunderstorm" or "unknown" V_RAIN = 6, //!< S_RAIN. Amount of rain @@ -120,12 +119,12 @@ typedef enum { V_KWH = 18, //!< S_POWER. Accumulated number of KWH for a power meter V_SCENE_ON = 19, //!< S_SCENE_CONTROLLER. Turn on a scene V_SCENE_OFF = 20, //!< S_SCENE_CONTROLLER. Turn of a scene - V_HVAC_FLOW_STATE = 21, //!< S_HEATER, S_HVAC. HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver") + V_HVAC_FLOW_STATE = 21, //!< S_HEATER, S_HVAC. HVAC flow state ("Off", "HeatOn", "CoolOn", or "AutoChangeOver") V_HEATER = 21, //!< \deprecated Same as V_HVAC_FLOW_STATE, **** DEPRECATED, DO NOT USE **** - V_HVAC_SPEED = 22, //!< S_HVAC, S_HEATER. HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto") + V_HVAC_SPEED = 22, //!< S_HVAC, S_HEATER. HVAC/Heater fan speed ("Min", "Normal", "Max", "Auto") V_LIGHT_LEVEL = 23, //!< S_LIGHT_LEVEL. Uncalibrated light level. 0-100%. Use V_LEVEL for light level in lux - V_VAR1 = 24, //!< VAR1 - V_VAR2 = 25, //!< VAR2 + V_VAR1 = 24, //!< VAR1 + V_VAR2 = 25, //!< VAR2 V_VAR3 = 26, //!< VAR3 V_VAR4 = 27, //!< VAR4 V_VAR5 = 28, //!< VAR5 @@ -138,11 +137,11 @@ typedef enum { V_VOLUME = 35, //!< S_WATER. Water volume V_LOCK_STATUS = 36, //!< S_LOCK. Set or get lock status. 1=Locked, 0=Unlocked V_LEVEL = 37, //!< S_DUST, S_AIR_QUALITY, S_SOUND (dB), S_VIBRATION (hz), S_LIGHT_LEVEL (lux) - V_VOLTAGE = 38, //!< S_MULTIMETER + V_VOLTAGE = 38, //!< S_MULTIMETER V_CURRENT = 39, //!< S_MULTIMETER V_RGB = 40, //!< S_RGB_LIGHT, S_COLOR_SENSOR. Sent as ASCII hex: RRGGBB (RR=red, GG=green, BB=blue component) V_RGBW = 41, //!< S_RGBW_LIGHT. Sent as ASCII hex: RRGGBBWW (WW=white component) - V_ID = 42, //!< Used for sending in sensors hardware ids (i.e. OneWire DS1820b). + V_ID = 42, //!< Used for sending in sensors hardware ids (i.e. OneWire DS1820b). V_UNIT_PREFIX = 43, //!< Allows sensors to send in a string representing the unit prefix to be displayed in GUI, not parsed by controller! E.g. cm, m, km, inch. V_HVAC_SETPOINT_COOL = 44, //!< S_HVAC. HVAC cool setpoint (Integer between 0-100) V_HVAC_SETPOINT_HEAT = 45, //!< S_HEATER, S_HVAC. HVAC/Heater setpoint (Integer between 0-100) @@ -152,24 +151,24 @@ typedef enum { V_POSITION = 49, //!< GPS position and altitude. Payload: latitude;longitude;altitude(m). E.g. "55.722526;13.017972;18" V_IR_RECORD = 50, //!< Record IR codes S_IR for playback V_PH = 51, //!< S_WATER_QUALITY, water PH - V_ORP = 52, //!< S_WATER_QUALITY, water ORP : redox potential in mV - V_EC = 53, //!< S_WATER_QUALITY, water electric conductivity μS/cm (microSiemens/cm) - V_VAR = 54, //!< S_POWER, Reactive power: volt-ampere reactive (var) - V_VA = 55, //!< S_POWER, Apparent power: volt-ampere (VA) - V_POWER_FACTOR = 56, //!< S_POWER, Ratio of real power to apparent power: floating point value in the range [-1,..,1] + V_ORP = 52, //!< S_WATER_QUALITY, water ORP : redox potential in mV + V_EC = 53, //!< S_WATER_QUALITY, water electric conductivity μS/cm (microSiemens/cm) + V_VAR = 54, //!< S_POWER, Reactive power: volt-ampere reactive (var) + V_VA = 55, //!< S_POWER, Apparent power: volt-ampere (VA) + V_POWER_FACTOR = 56, //!< S_POWER, Ratio of real power to apparent power: floating point value in the range [-1,..,1] } mysensor_data; /// @brief Type of internal messages (for internal messages) typedef enum { I_BATTERY_LEVEL = 0, //!< Battery level - I_TIME = 1, //!< Time + I_TIME = 1, //!< Time (request/response) I_VERSION = 2, //!< Version I_ID_REQUEST = 3, //!< ID request I_ID_RESPONSE = 4, //!< ID response I_INCLUSION_MODE = 5, //!< Inclusion mode - I_CONFIG = 6, //!< Config - I_FIND_PARENT = 7, //!< Find parent + I_CONFIG = 6, //!< Config (request/response) + I_FIND_PARENT_REQUEST = 7, //!< Find parent I_FIND_PARENT_RESPONSE = 8, //!< Find parent response I_LOG_MESSAGE = 9, //!< Log message I_CHILDREN = 10, //!< Children @@ -180,9 +179,9 @@ typedef enum { I_SIGNING_PRESENTATION = 15, //!< Provides signing related preferences (first byte is preference version) I_NONCE_REQUEST = 16, //!< Request for a nonce I_NONCE_RESPONSE = 17, //!< Payload is nonce data - I_HEARTBEAT = 18, //!< Heartbeat request + I_HEARTBEAT_REQUEST = 18, //!< Heartbeat request I_PRESENTATION = 19, //!< Presentation message - I_DISCOVER = 20, //!< Discover request + I_DISCOVER_REQUEST = 20, //!< Discover request I_DISCOVER_RESPONSE = 21, //!< Discover response I_HEARTBEAT_RESPONSE = 22, //!< Heartbeat response I_LOCKED = 23, //!< Node is locked (reason in string-payload) @@ -229,26 +228,26 @@ typedef enum { #define BF_SET(y, x, start, len) ( y= ((y) &~ BF_MASK(start, len)) | BF_PREP(x, start, len) ) //!< Insert a new bitfield value 'x' into 'y' // Getters/setters for special bit fields in header -#define mSetVersion(_msg,_version) BF_SET(_msg.version_length, _version, 0, 2) //!< Set version field -#define mGetVersion(_msg) ((uint8_t)BF_GET(_msg.version_length, 0, 2)) //!< Get version field +#define mSetVersion(_message,_version) BF_SET(_message.version_length, _version, 0, 2) //!< Set version field +#define mGetVersion(_message) ((uint8_t)BF_GET(_message.version_length, 0, 2)) //!< Get version field -#define mSetSigned(_msg,_signed) BF_SET(_msg.version_length, _signed, 2, 1) //!< Set signed field -#define mGetSigned(_msg) ((bool)BF_GET(_msg.version_length, 2, 1)) //!< Get versignedsion field +#define mSetSigned(_message,_signed) BF_SET(_message.version_length, _signed, 2, 1) //!< Set signed field +#define mGetSigned(_message) ((bool)BF_GET(_message.version_length, 2, 1)) //!< Get versignedsion field -#define mSetLength(_msg,_length) BF_SET(_msg.version_length, _length, 3, 5) //!< Set length field -#define mGetLength(_msg) ((uint8_t)BF_GET(_msg.version_length, 3, 5)) //!< Get length field +#define mSetLength(_message,_length) BF_SET(_message.version_length, _length, 3, 5) //!< Set length field +#define mGetLength(_message) ((uint8_t)BF_GET(_message.version_length, 3, 5)) //!< Get length field -#define mSetCommand(_msg,_command) BF_SET(_msg.command_ack_payload, _command, 0, 3) //!< Set command field -#define mGetCommand(_msg) ((uint8_t)BF_GET(_msg.command_ack_payload, 0, 3)) //!< Get command field +#define mSetCommand(_message,_command) BF_SET(_message.command_ack_payload, _command, 0, 3) //!< Set command field +#define mGetCommand(_message) ((uint8_t)BF_GET(_message.command_ack_payload, 0, 3)) //!< Get command field -#define mSetRequestAck(_msg,_rack) BF_SET(_msg.command_ack_payload, _rack, 3, 1) //!< Set ack-request field -#define mGetRequestAck(_msg) ((bool)BF_GET(_msg.command_ack_payload, 3, 1)) //!< Get ack-request field +#define mSetRequestAck(_message,_rack) BF_SET(_message.command_ack_payload, _rack, 3, 1) //!< Set ack-request field +#define mGetRequestAck(_message) ((bool)BF_GET(_message.command_ack_payload, 3, 1)) //!< Get ack-request field -#define mSetAck(_msg,_ackMsg) BF_SET(_msg.command_ack_payload, _ackMsg, 4, 1) //!< Set ack field -#define mGetAck(_msg) ((bool)BF_GET(_msg.command_ack_payload, 4, 1)) //!< Get ack field +#define mSetAck(_message,_ackMsg) BF_SET(_message.command_ack_payload, _ackMsg, 4, 1) //!< Set ack field +#define mGetAck(_message) ((bool)BF_GET(_message.command_ack_payload, 4, 1)) //!< Get ack field -#define mSetPayloadType(_msg, _pt) BF_SET(_msg.command_ack_payload, _pt, 5, 3) //!< Set payload type field -#define mGetPayloadType(_msg) ((uint8_t)BF_GET(_msg.command_ack_payload, 5, 3)) //!< Get payload type field +#define mSetPayloadType(_message, _pt) BF_SET(_message.command_ack_payload, _pt, 5, 3) //!< Set payload type field +#define mGetPayloadType(_message) ((uint8_t)BF_GET(_message.command_ack_payload, 5, 3)) //!< Get payload type field // internal access for special fields @@ -257,6 +256,9 @@ typedef enum { #define miSetLength(_length) BF_SET(version_length, _length, 3, 5) //!< Internal setter for length field #define miGetLength() ((uint8_t)BF_GET(version_length, 3, 5)) //!< Internal getter for length field +#define miSetVersion(_version) BF_SET(version_length, _version, 0, 2) //!< Internal setter for version field +#define miGetVersion() ((uint8_t)BF_GET(version_length, 0, 2)) //!< Internal getter for version field + #define miSetRequestAck(_rack) BF_SET(command_ack_payload, _rack, 3, 1) //!< Internal setter for ack-request field #define miGetRequestAck() ((bool)BF_GET(command_ack_payload, 3, 1)) //!< Internal getter for ack-request field @@ -282,6 +284,11 @@ class MyMessage char i2h(uint8_t i) const; + /** + * Clear message contents. + */ + void clear(); + /** * If payload is something else than P_STRING you can have the payload value converted * into string representation by supplying a buffer with the minimum size of @@ -324,8 +331,7 @@ class MyMessage #else typedef union { -struct -{ + struct { #endif uint8_t last; // 8 bit - Id of last node this message passed @@ -333,12 +339,12 @@ struct uint8_t destination; // 8 bit - Id of destination node uint8_t version_length; // 2 bit - Protocol version - // 1 bit - Signed flag - // 5 bit - Length of payload + // 1 bit - Signed flag + // 5 bit - Length of payload uint8_t command_ack_payload; // 3 bit - Command type - // 1 bit - Request an ack - Indicator that receiver should send an ack back. - // 1 bit - Is ack messsage - Indicator that this is the actual ack message. - // 3 bit - Payload data type + // 1 bit - Request an ack - Indicator that receiver should send an ack back. + // 1 bit - Is ack messsage - Indicator that this is the actual ack message. + // 3 bit - Payload data type uint8_t type; // 8 bit - Type varies depending on command uint8_t sensor; // 8 bit - Id of sensor that this message concerns. @@ -357,7 +363,7 @@ struct }; struct { // Presentation messages uint8_t version; // Library version - uint8_t sensorType; // Sensor type hint for controller, see table above + uint8_t sensorType; // Sensor type hint for controller, see table above }; char data[MAX_PAYLOAD + 1]; } __attribute__((packed)); @@ -365,7 +371,7 @@ struct } __attribute__((packed)); #else }; -uint8_t array[HEADER_SIZE + MAX_PAYLOAD + 1]; +uint8_t array[HEADER_SIZE + MAX_PAYLOAD + 1]; } __attribute__((packed)) MyMessage; #endif #endif diff --git a/core/MyOTAFirmwareUpdate.cpp b/core/MyOTAFirmwareUpdate.cpp index 73f7488b3..1757c6b8f 100644 --- a/core/MyOTAFirmwareUpdate.cpp +++ b/core/MyOTAFirmwareUpdate.cpp @@ -16,134 +16,160 @@ * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. */ - + #include "MyOTAFirmwareUpdate.h" +// global variables +extern MyMessage _msg; +extern MyMessage _msgTmp; + +// local variables SPIFlash _flash(MY_OTA_FLASH_SS, MY_OTA_FLASH_JDECID); -NodeFirmwareConfig _fc; -bool _fwUpdateOngoing; -unsigned long _fwLastRequestTime; -uint16_t _fwBlock; -uint8_t _fwRetry; +nodeFirmwareConfig_t _nodeFirmwareConfig; +bool _firmwareUpdateOngoing; +uint32_t _firmwareLastRequest; +uint16_t _firmwareBlock; +uint8_t _firmwareRetry; -inline void readFirmwareSettings() { - hwReadConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); +void readFirmwareSettings(void) +{ + hwReadConfigBlock((void*)&_nodeFirmwareConfig, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, + sizeof(nodeFirmwareConfig_t)); } -inline void firmwareOTAUpdateRequest() { - unsigned long enter = hwMillis(); - if (_fwUpdateOngoing && (enter - _fwLastRequestTime > MY_OTA_RETRY_DELAY)) { - if (!_fwRetry) { - setIndication(INDICATION_ERR_FW_TIMEOUT); - debug(PSTR("fw upd fail\n")); +void firmwareOTAUpdateRequest(void) +{ + const uint32_t enterMS = hwMillis(); + if (_firmwareUpdateOngoing && (enterMS - _firmwareLastRequest > MY_OTA_RETRY_DELAY)) { + if (!_firmwareRetry) { + setIndication(INDICATION_ERR_FW_TIMEOUT); + OTA_DEBUG(PSTR("!OTA:FRQ:FW UPD FAIL\n")); // fw update failed // Give up. We have requested MY_OTA_RETRY times without any packet in return. - _fwUpdateOngoing = false; + _firmwareUpdateOngoing = false; return; } - _fwRetry--; - _fwLastRequestTime = enter; + _firmwareRetry--; + _firmwareLastRequest = enterMS; // Time to (re-)request firmware block from controller - RequestFWBlock firmwareRequest; - firmwareRequest.type = _fc.type; - firmwareRequest.version = _fc.version; - firmwareRequest.block = (_fwBlock - 1); - debug(PSTR("req FW: T=%02X, V=%02X, B=%04X\n"),_fc.type,_fc.version,_fwBlock - 1); - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, false).set(&firmwareRequest,sizeof(RequestFWBlock))); + requestFirmwareBlock_t firmwareRequest; + firmwareRequest.type = _nodeFirmwareConfig.type; + firmwareRequest.version = _nodeFirmwareConfig.version; + firmwareRequest.block = (_firmwareBlock - 1); + OTA_DEBUG(PSTR("OTA:FRQ:FW REQ,T=%04X,V=%04X,B=%04X\n"), _nodeFirmwareConfig.type, + _nodeFirmwareConfig.version, _firmwareBlock - 1); // request FW update block + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_REQUEST, + false).set(&firmwareRequest, sizeof(requestFirmwareBlock_t))); } } -inline bool firmwareOTAUpdateProcess() { +bool firmwareOTAUpdateProcess(void) +{ if (_msg.type == ST_FIRMWARE_CONFIG_RESPONSE) { - NodeFirmwareConfig *firmwareConfigResponse = (NodeFirmwareConfig *)_msg.data; - // compare with current node configuration, if they differ, start fw fetch process - if (memcmp(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig))) { - setIndication(INDICATION_FW_UPDATE_START); - debug(PSTR("fw update\n")); + nodeFirmwareConfig_t *firmwareConfigResponse = (nodeFirmwareConfig_t *)_msg.data; + // compare with current node configuration, if they differ, start FW fetch process + if (memcmp(&_nodeFirmwareConfig, firmwareConfigResponse, sizeof(nodeFirmwareConfig_t))) { + setIndication(INDICATION_FW_UPDATE_START); + OTA_DEBUG(PSTR("OTA:FWP:UPDATE\n")); // FW update initiated // copy new FW config - memcpy(&_fc,firmwareConfigResponse,sizeof(NodeFirmwareConfig)); + (void)memcpy(&_nodeFirmwareConfig, firmwareConfigResponse, sizeof(nodeFirmwareConfig_t)); // Init flash if (!_flash.initialize()) { - setIndication(INDICATION_ERR_FW_FLASH_INIT); - debug(PSTR("flash init fail\n")); - _fwUpdateOngoing = false; + setIndication(INDICATION_ERR_FW_FLASH_INIT); + OTA_DEBUG(PSTR("!OTA:FWP:FLASH INIT FAIL\n")); // failed to initialise flash + _firmwareUpdateOngoing = false; } else { // erase lower 32K -> max flash size for ATMEGA328 _flash.blockErase32K(0); // wait until flash erased - while ( _flash.busy() ); - _fwBlock = _fc.blocks; - _fwUpdateOngoing = true; + while ( _flash.busy() ) {} + _firmwareBlock = _nodeFirmwareConfig.blocks; + _firmwareUpdateOngoing = true; // reset flags - _fwRetry = MY_OTA_RETRY+1; - _fwLastRequestTime = 0; + _firmwareRetry = MY_OTA_RETRY + 1; + _firmwareLastRequest = 0; } return true; } - debug(PSTR("fw update skipped\n")); + OTA_DEBUG(PSTR("OTA:FWP:UPDATE SKIPPED\n")); // FW update skipped, no newer version available } else if (_msg.type == ST_FIRMWARE_RESPONSE) { - if (_fwUpdateOngoing) { + if (_firmwareUpdateOngoing) { // Save block to flash - setIndication(INDICATION_FW_UPDATE_RX); - debug(PSTR("fw block %d\n"), _fwBlock); + setIndication(INDICATION_FW_UPDATE_RX); + OTA_DEBUG(PSTR("OTA:FWP:RECV B=%04X\n"), _firmwareBlock); // received FW block // extract FW block - ReplyFWBlock *firmwareResponse = (ReplyFWBlock *)_msg.data; + replyFirmwareBlock_t *firmwareResponse = (replyFirmwareBlock_t *)_msg.data; // write to flash - _flash.writeBytes( ((_fwBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, firmwareResponse->data, FIRMWARE_BLOCK_SIZE); + _flash.writeBytes( ((_firmwareBlock - 1) * FIRMWARE_BLOCK_SIZE) + FIRMWARE_START_OFFSET, + firmwareResponse->data, FIRMWARE_BLOCK_SIZE); // wait until flash written - while ( _flash.busy() ); - _fwBlock--; - if (!_fwBlock) { + while (_flash.busy()) {} + _firmwareBlock--; + if (!_firmwareBlock) { // We're finished! Do a checksum and reboot. - _fwUpdateOngoing = false; + OTA_DEBUG(PSTR("OTA:FWP:FW END\n")); // received FW block + _firmwareUpdateOngoing = false; if (transportIsValidFirmware()) { - debug(PSTR("fw checksum ok\n")); - // All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it) - uint16_t fwsize = FIRMWARE_BLOCK_SIZE * _fc.blocks; - uint8_t OTAbuffer[10] = {'F','L','X','I','M','G',':',(uint8_t)(fwsize >> 8),(uint8_t)(fwsize & 0xff),':'}; - _flash.writeBytes(0, OTAbuffer, 10); + OTA_DEBUG(PSTR("OTA:FWP:CRC OK\n")); // FW checksum ok // Write the new firmware config to eeprom - hwWriteConfigBlock((void*)&_fc, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, sizeof(NodeFirmwareConfig)); + hwWriteConfigBlock((void*)&_nodeFirmwareConfig, (void*)EEPROM_FIRMWARE_TYPE_ADDRESS, + sizeof(nodeFirmwareConfig_t)); + // All seems ok, write size and signature to flash (DualOptiboot will pick this up and flash it) + const uint16_t firmwareSize = FIRMWARE_BLOCK_SIZE * _nodeFirmwareConfig.blocks; + const uint8_t OTAbuffer[FIRMWARE_START_OFFSET] = {'F','L','X','I','M','G',':', (uint8_t)(firmwareSize >> 8), (uint8_t)(firmwareSize & 0xff),':'}; + _flash.writeBytes(0, OTAbuffer, FIRMWARE_START_OFFSET); + // wait until flash ready + while (_flash.busy()) {} hwReboot(); } else { - setIndication(INDICATION_ERR_FW_CHECKSUM); - debug(PSTR("fw checksum fail\n")); + setIndication(INDICATION_ERR_FW_CHECKSUM); + OTA_DEBUG(PSTR("!OTA:FWP:CRC FAIL\n")); } } // reset flags - _fwRetry = MY_OTA_RETRY+1; - _fwLastRequestTime = 0; + _firmwareRetry = MY_OTA_RETRY + 1; + _firmwareLastRequest = 0; } else { - debug(PSTR("No fw update ongoing\n")); + OTA_DEBUG(PSTR("!OTA:FWP:NO UPDATE\n")); } return true; } return false; } -inline void presentBootloaderInformation(){ - RequestFirmwareConfig *reqFWConfig = (RequestFirmwareConfig *)_msgTmp.data; - mSetLength(_msgTmp, sizeof(RequestFirmwareConfig)); +void presentBootloaderInformation(void) +{ + requestFirmwareConfig_t *requestFirmwareConfig = (requestFirmwareConfig_t *)_msgTmp.data; + mSetLength(_msgTmp, sizeof(requestFirmwareConfig_t)); mSetCommand(_msgTmp, C_STREAM); - mSetPayloadType(_msgTmp,P_CUSTOM); + mSetPayloadType(_msgTmp, P_CUSTOM); // copy node settings to reqFWConfig - memcpy(reqFWConfig,&_fc,sizeof(NodeFirmwareConfig)); + (void)memcpy(requestFirmwareConfig, &_nodeFirmwareConfig, sizeof(nodeFirmwareConfig_t)); // add bootloader information - reqFWConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION; - _fwUpdateOngoing = false; - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, ST_FIRMWARE_CONFIG_REQUEST, false)); + requestFirmwareConfig->BLVersion = MY_OTA_BOOTLOADER_VERSION; + _firmwareUpdateOngoing = false; + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_STREAM, + ST_FIRMWARE_CONFIG_REQUEST, false)); +} +bool isFirmwareUpdateOngoing(void) +{ + return _firmwareUpdateOngoing; } // do a crc16 on the whole received firmware -inline bool transportIsValidFirmware() { +bool transportIsValidFirmware(void) +{ // init crc uint16_t crc = ~0; - for (uint16_t i = 0; i < _fc.blocks * FIRMWARE_BLOCK_SIZE; ++i) { + for (uint16_t i = 0; i < _nodeFirmwareConfig.blocks * FIRMWARE_BLOCK_SIZE; ++i) { crc ^= _flash.readByte(i + FIRMWARE_START_OFFSET); - for (int8_t j = 0; j < 8; ++j) { - if (crc & 1) - crc = (crc >> 1) ^ 0xA001; - else - crc = (crc >> 1); - } + for (int8_t j = 0; j < 8; ++j) { + if (crc & 1) { + crc = (crc >> 1) ^ 0xA001; + } else { + crc = (crc >> 1); + } + } } - return crc == _fc.crc; + OTA_DEBUG(PSTR("OTA:CRC:B=%04X,C=%04X,F=%04X\n"), _nodeFirmwareConfig.blocks,crc, + _nodeFirmwareConfig.crc); + return crc == _nodeFirmwareConfig.crc; } diff --git a/core/MyOTAFirmwareUpdate.h b/core/MyOTAFirmwareUpdate.h index 3c3921d44..9372fd2c9 100644 --- a/core/MyOTAFirmwareUpdate.h +++ b/core/MyOTAFirmwareUpdate.h @@ -17,58 +17,100 @@ * version 2 as published by the Free Software Foundation. */ +/** +* @file MyOTAFirmwareUpdate.h +* +* @defgroup MyOTAFirmwaregrp MyOTAFirmwareUpdate +* @ingroup internals +* @{ +* +* MyOTAFirmwareUpdate-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE +* - [!] Exclamation mark is prepended in case of error or warning +* - SYSTEM: +* - OTA messages emitted by MyOTAFirmwareUpdate +* - SUB SYSTEMS: +* - OTA:FRQ from @ref firmwareOTAUpdateRequest() +* - OTA:FWP from @ref firmwareOTAUpdateProcess() +* +* MyOTAFirmwareUpdate debug log messages: +* +* |E| SYS | SUB | Message | Comment +* |-|------|-------|-------------------------------------------|---------------------------------------------------------------------------- +* | | OTA | FWP | UPDATE | FW update initiated +* |!| OTA | FWP | FLASH INIT FAIL | Failed to initialise flash +* | | OTA | FWP | UPDATE SKIPPED | FW update skipped, no newer version available +* | | OTA | FWP | RECV B=%04X | Received FW block (B) +* | | OTA | FWP | FW END | FW received, proceed to CRC verification +* | | OTA | FWP | CRC OK | FW CRC verification OK +* |!| OTA | FWP | CRC FAIL | FW CRC verification failed +* | | OTA | FRQ | FW REQ,T=%04X,V=%04X,B=%04X | Request FW update, FW type (T), version (V), block (B) +* |!| OTA | FRQ | FW UPD FAIL | FW update failed +* | | OTA | CRC | B=%04X,C=%04X,F=%04X | FW CRC verification. FW blocks (B), calculated CRC (C), FW CRC (F) +* +* +* @brief API declaration for MyOTAFirmwareUpdate +*/ + + #ifndef MyOTAFirmwareUpdate_h #define MyOTAFirmwareUpdate_h #include "MySensorsCore.h" -// Size of each firmware block -#define FIRMWARE_BLOCK_SIZE 16 -// Number of times a firmware block should be requested before giving up -#define FIRMWARE_MAX_REQUESTS 5 -// Number of times to request a fw block before giving up -#define MY_OTA_RETRY 5 -// Number of millisecons before re-request a fw block -#define MY_OTA_RETRY_DELAY 500 -// Start offset for firmware in flash (DualOptiboot wants to keeps a signature first) -#define FIRMWARE_START_OFFSET 10 -// Bootloader version -#define MY_OTA_BOOTLOADER_MAJOR_VERSION 3 -#define MY_OTA_BOOTLOADER_MINOR_VERSION 0 -#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) +#define FIRMWARE_BLOCK_SIZE (16u) //!< Size of each firmware block +#define FIRMWARE_MAX_REQUESTS (5u) //!< Number of times a firmware block should be requested before giving up +#define MY_OTA_RETRY (5u) //!< Number of times to request a fw block before giving up +#define MY_OTA_RETRY_DELAY (500u) //!< Number of milliseconds before re-requesting a FW block +#define FIRMWARE_START_OFFSET (10u) //!< Start offset for firmware in flash (DualOptiboot wants to keeps a signature first) +#define MY_OTA_BOOTLOADER_MAJOR_VERSION (3u) //!< Bootloader version major +#define MY_OTA_BOOTLOADER_MINOR_VERSION (0u) //!< Bootloader version minor +#define MY_OTA_BOOTLOADER_VERSION (MY_OTA_BOOTLOADER_MINOR_VERSION * 256 + MY_OTA_BOOTLOADER_MAJOR_VERSION) //!< Bootloader version -/// @brief FW config structure, stored in eeprom +#if defined(MY_DEBUG) +#define OTA_DEBUG(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug +#else +#define OTA_DEBUG(x,...) //!< debug NULL +#endif +/** +* @brief FW config structure, stored in eeprom +*/ typedef struct { - uint16_t type; //!< Type of config - uint16_t version; //!< Version of config - uint16_t blocks; //!< Number of blocks - uint16_t crc; //!< CRC of block data -} __attribute__((packed)) NodeFirmwareConfig; + uint16_t type; //!< Type of config + uint16_t version; //!< Version of config + uint16_t blocks; //!< Number of blocks + uint16_t crc; //!< CRC of block data +} __attribute__((packed)) nodeFirmwareConfig_t; -/// @brief FW config request structure +/** +* @brief FW config request structure +*/ typedef struct { - uint16_t type; //!< Type of config - uint16_t version; //!< Version of config - uint16_t blocks; //!< Number of blocks - uint16_t crc; //!< CRC of block data - uint16_t BLVersion; //!< Bootloader version -} __attribute__((packed)) RequestFirmwareConfig; + uint16_t type; //!< Type of config + uint16_t version; //!< Version of config + uint16_t blocks; //!< Number of blocks + uint16_t crc; //!< CRC of block data + uint16_t BLVersion; //!< Bootloader version +} __attribute__((packed)) requestFirmwareConfig_t; -/// @brief FW block request structure +/** +* @brief FW block request structure +*/ typedef struct { - uint16_t type; //!< Type of config - uint16_t version; //!< Version of config - uint16_t block; //!< Block index -} __attribute__((packed)) RequestFWBlock; + uint16_t type; //!< Type of config + uint16_t version; //!< Version of config + uint16_t block; //!< Block index +} __attribute__((packed)) requestFirmwareBlock_t; -/// @brief FW block reply structure +/** +* @brief FW block reply structure +*/ typedef struct { - uint16_t type; //!< Type of config - uint16_t version; //!< Version of config - uint16_t block; //!< Block index - uint8_t data[FIRMWARE_BLOCK_SIZE]; //!< Block data -} __attribute__((packed)) ReplyFWBlock; + uint16_t type; //!< Type of config + uint16_t version; //!< Version of config + uint16_t block; //!< Block index + uint8_t data[FIRMWARE_BLOCK_SIZE]; //!< Block data +} __attribute__((packed)) replyFirmwareBlock_t; /** @@ -76,26 +118,28 @@ typedef struct { * * Current firmware settings (type, version, crc, blocks) are read into _fc */ -void readFirmwareSettings(); +void readFirmwareSettings(void); /** * @brief Handle OTA FW update requests */ - void firmwareOTAUpdateRequest(); +void firmwareOTAUpdateRequest(void); /** * @brief Handle OTA FW update responses * * This function handles incoming OTA FW packets and stores them to external flash (Sensebender) */ -bool firmwareOTAUpdateProcess(); +bool firmwareOTAUpdateProcess(void); /** * @brief Validate uploaded FW CRC * * This function verifies if uploaded FW CRC is valid */ -bool transportIsValidFirmware(); +bool transportIsValidFirmware(void); /** - * @brief Present bootloader/FW information upon startup + * @brief Present bootloader/FW information upon startup */ -void presentBootloaderInformation(); +void presentBootloaderInformation(void); + +#endif -#endif \ No newline at end of file +/** @}*/ diff --git a/core/MyProtocolMySensors.cpp b/core/MyProtocolMySensors.cpp old mode 100644 new mode 100755 index 347b9d2b5..0c7308560 --- a/core/MyProtocolMySensors.cpp +++ b/core/MyProtocolMySensors.cpp @@ -20,95 +20,190 @@ #include "MyConfig.h" #include "MyTransport.h" #include "MyProtocol.h" +#include uint8_t protocolH2i(char c); char _fmtBuffer[MY_GATEWAY_MAX_SEND_LENGTH]; char _convBuffer[MAX_PAYLOAD*2+1]; -bool protocolParse(MyMessage &message, char *inputString) { +bool protocolParse(MyMessage &message, char *inputString) +{ char *str, *p, *value=NULL; uint8_t bvalue[MAX_PAYLOAD]; uint8_t blen = 0; int i = 0; uint8_t command = 0; - uint8_t ack = 0; // Extract command data coming on serial line for (str = strtok_r(inputString, ";", &p); // split using semicolon - str && i < 6; // loop while str is not null an max 5 times - str = strtok_r(NULL, ";", &p) // get subsequent tokens - ) { + str && i < 6; // loop while str is not null an max 5 times + str = strtok_r(NULL, ";", &p) // get subsequent tokens + ) { switch (i) { - case 0: // Radioid (destination) - message.destination = atoi(str); - break; - case 1: // Childid - message.sensor = atoi(str); - break; - case 2: // Message type - command = atoi(str); - mSetCommand(message, command); - break; - case 3: // Should we request ack from destination? - ack = atoi(str); - break; - case 4: // Data type - message.type = atoi(str); - break; - case 5: // Variable value - if (command == C_STREAM) { - blen = 0; + case 0: // Radioid (destination) + message.destination = atoi(str); + break; + case 1: // Childid + message.sensor = atoi(str); + break; + case 2: // Message type + command = atoi(str); + mSetCommand(message, command); + break; + case 3: // Should we request ack from destination? + mSetRequestAck(message, atoi(str)?1:0); + break; + case 4: // Data type + message.type = atoi(str); + break; + case 5: // Variable value + if (command == C_STREAM) { + blen = 0; + while (*str) { uint8_t val; - while (*str) { - val = protocolH2i(*str++) << 4; - val += protocolH2i(*str++); - bvalue[blen] = val; - blen++; - } - } else { - value = str; - // Remove trailing carriage return and newline character (if it exists) - uint8_t lastCharacter = strlen(value)-1; - if (value[lastCharacter] == '\r') - value[lastCharacter] = 0; - if (value[lastCharacter] == '\n') - value[lastCharacter] = 0; + val = protocolH2i(*str++) << 4; + val += protocolH2i(*str++); + bvalue[blen] = val; + blen++; } - break; + } else { + value = str; + // Remove trailing carriage return and newline character (if it exists) + uint8_t lastCharacter = strlen(value)-1; + if (value[lastCharacter] == '\r') { + value[lastCharacter] = 0; + } + if (value[lastCharacter] == '\n') { + value[lastCharacter] = 0; + } + } + break; } i++; } //debug(PSTR("Received %d"), i); // Check for invalid input - if (i < 5) + if (i < 5) { return false; - + } message.sender = GATEWAY_ADDRESS; message.last = GATEWAY_ADDRESS; - mSetRequestAck(message, ack?1:0); - mSetAck(message, false); - if (command == C_STREAM) + mSetAck(message, false); + if (command == C_STREAM) { message.set(bvalue, blen); - else + } else { message.set(value); + } return true; } -char * protocolFormat(MyMessage &message) { - snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%d;%d;%d;%d;%d;%s\n"), message.sender, message.sensor, (uint8_t)mGetCommand(message), (uint8_t)mGetAck(message), message.type, message.getString(_convBuffer)); +char * protocolFormat(MyMessage &message) +{ + snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%d;%d;%d;%d;%d;%s\n"), message.sender, + message.sensor, (uint8_t)mGetCommand(message), (uint8_t)mGetAck(message), message.type, + message.getString(_convBuffer)); + return _fmtBuffer; +} + +char * protocolFormatMQTTTopic(const char* prefix, MyMessage &message) +{ + snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/%d/%d/%d/%d/%d"), prefix, + message.sender, message.sensor, mGetCommand(message), mGetAck(message), message.type); + return _fmtBuffer; +} + +char * protocolFormatMQTTSubscribe(const char* prefix) +{ + snprintf_P(_fmtBuffer, MY_GATEWAY_MAX_SEND_LENGTH, PSTR("%s/+/+/+/+/+"), prefix); return _fmtBuffer; } -uint8_t protocolH2i(char c) { +#ifdef MY_GATEWAY_MQTT_CLIENT +bool protocolMQTTParse(MyMessage &message, char* topic, uint8_t* payload, unsigned int length) +{ + char *str, *p; uint8_t i = 0; - if (c <= '9') + uint8_t bvalue[MAX_PAYLOAD]; + uint8_t blen = 0; + uint8_t command = 0; + if (topic != strstr(topic, MY_MQTT_SUBSCRIBE_TOPIC_PREFIX)) { + // Prefix doesn't match incoming topic + return false; + } + for (str = strtok_r(topic + strlen(MY_MQTT_SUBSCRIBE_TOPIC_PREFIX) + 1, "/", &p); str && i <= 5; + str = strtok_r(NULL, "/", &p)) { + switch (i) { + case 0: { + // Node id + message.destination = atoi(str); + break; + } + case 1: { + // Sensor id + message.sensor = atoi(str); + break; + } + case 2: { + // Command type + command = atoi(str); + mSetCommand(message, command); + break; + } + case 3: { + // Ack flag + mSetRequestAck(message, atoi(str)?1:0); + break; + } + case 4: { + // Sub type + message.type = atoi(str); + break; + } + } + i++; + } + + if (i != 5) { + return false; + } + + message.sender = GATEWAY_ADDRESS; + message.last = GATEWAY_ADDRESS; + mSetAck(message, false); + + // Add payload + if (command == C_STREAM) { + blen = 0; + uint8_t val; + while (*payload) { + val = protocolH2i(*payload++) << 4; + val += protocolH2i(*payload++); + bvalue[blen] = val; + blen++; + } + message.set(bvalue, blen); + } else { + char* ca; + ca = (char *) payload; + ca += length; + *ca = '\0'; + message.set((const char*) payload); + } + + return true; +} +#endif + +uint8_t protocolH2i(char c) +{ + uint8_t i = 0; + if (c <= '9') { i += c - '0'; - else if (c >= 'a') + } else if (c >= 'a') { i += c - 'a' + 10; - else + } else { i += c - 'A' + 10; + } return i; } - - diff --git a/core/MySensorsCore.cpp b/core/MySensorsCore.cpp index 7ab8b20b6..3dfed8739 100644 --- a/core/MySensorsCore.cpp +++ b/core/MySensorsCore.cpp @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad - * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2013-2016 Sensnology AB * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -19,544 +19,671 @@ #include "MySensorsCore.h" -ControllerConfig _cc; // Configuration coming from controller -NodeConfig _nc; // Essential settings for node to work -MyMessage _msg; // Buffer for incoming messages. -MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others). +#if defined(__linux__) +#include +#include +#endif + +// message buffers +MyMessage _msg; // Buffer for incoming messages +MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others) -bool _nodeRegistered = false; +// core configuration +static coreConfig_t _coreConfig; #if defined(MY_DEBUG) - char _convBuf[MAX_PAYLOAD*2+1]; +char _convBuf[MAX_PAYLOAD*2+1]; #endif -void (*_timeCallback)(unsigned long); // Callback for requested time messages - -void _process() { - hwWatchdogReset(); +// Callback for transport=ok transition +void _callbackTransportReady(void) +{ + if (!_coreConfig.presentationSent) { +#if !defined(MY_GATEWAY_FEATURE) // GW calls presentNode() when client connected + presentNode(); +#endif + _registerNode(); + _coreConfig.presentationSent = true; + } +} - #if defined (MY_LEDS_BLINKING_FEATURE) - ledsProcess(); - #endif +void _process(void) +{ + doYield(); - #if defined(MY_INCLUSION_MODE_FEATURE) - inclusionProcess(); - #endif +#if defined(MY_INCLUSION_MODE_FEATURE) + inclusionProcess(); +#endif - #if defined(MY_GATEWAY_FEATURE) - gatewayTransportProcess(); - #endif +#if defined(MY_GATEWAY_FEATURE) + gatewayTransportProcess(); +#endif - #if defined(MY_RADIO_FEATURE) - transportProcess(); - #endif +#if defined(MY_SENSOR_NETWORK) + transportProcess(); +#endif +#if defined(__linux__) + // To avoid high cpu usage + usleep(10000); // 10ms +#endif } -void _infiniteLoop() { +void _infiniteLoop(void) +{ while(1) { - #if defined(ARDUINO_ARCH_ESP8266) - yield(); - #endif - #if defined (MY_LEDS_BLINKING_FEATURE) - ledsProcess(); - #endif + doYield(); +#if defined(__linux__) + exit(1); +#endif } } -void _begin() { - #if !defined(MY_DISABLED_SERIAL) - hwInit(); - #endif +void _begin(void) +{ + // reset wdt + hwWatchdogReset(); - // Call before() in sketch (if it exists) - if (before) - before(); + if (preHwInit) { + preHwInit(); + } + + hwInit(); + + CORE_DEBUG(PSTR("MCO:BGN:INIT " MY_NODE_TYPE ",CP=" MY_CAPABILITIES ",VER=" + MYSENSORS_LIBRARY_VERSION "\n")); + + // set defaults + _coreConfig.presentationSent = false; - debug(PSTR("Starting " MY_NODE_TYPE " (" MY_CAPABILITIES ", " MYSENSORS_LIBRARY_VERSION ")\n")); + // Call sketch before() (if defined) + if (before) { + CORE_DEBUG(PSTR("MCO:BGN:BFR\n")); // before callback + before(); + } - #if defined(MY_LEDS_BLINKING_FEATURE) - ledsInit(); - #endif +#if defined(MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) + ledsInit(); +#endif signerInit(); // Read latest received controller configuration from EEPROM - hwReadConfigBlock((void*)&_cc, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, sizeof(ControllerConfig)); - // isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true - - #if defined(MY_OTA_FIRMWARE_FEATURE) - // Read firmware config from EEPROM, i.e. type, version, CRC, blocks - readFirmwareSettings(); - #endif - - #if defined(MY_RADIO_FEATURE) - // Save static parent id in eeprom (used by bootloader) - hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, MY_PARENT_NODE_ID); - transportInitialize(); - while (!isTransportOK()) { - hwWatchdogReset(); - transportProcess(); - #if defined(ARDUINO_ARCH_ESP8266) - yield(); - #endif - } - #endif - - - - #ifdef MY_NODE_LOCK_FEATURE - // Check if node has been locked down - if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0) { - // Node is locked, check if unlock pin is asserted, else hang the node - pinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP); - // Make a short delay so we are sure any large external nets are fully pulled - unsigned long enter = hwMillis(); - while (hwMillis() - enter < 2); - if (digitalRead(MY_NODE_UNLOCK_PIN) == 0) { - // Pin is grounded, reset lock counter - hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX); - // Disable pullup - pinMode(MY_NODE_UNLOCK_PIN, INPUT); - setIndication(INDICATION_ERR_LOCKED); - debug(PSTR("Node is unlocked.\n")); - } else { - // Disable pullup - pinMode(MY_NODE_UNLOCK_PIN, INPUT); - nodeLock("LDB"); //Locked during boot - } - } else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0xFF) { - // Reset walue - hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX); - } - #endif - - #if defined(MY_GATEWAY_FEATURE) - #if defined(MY_INCLUSION_BUTTON_FEATURE) - inclusionInit(); - #endif - - // initialize the transport driver - if (!gatewayTransportInit()) { - setIndication(INDICATION_ERR_INIT_GWTRANSPORT); - debug(PSTR("Transport driver init fail\n")); - // Nothing more we can do - _infiniteLoop(); - } - #endif - - // Call sketch setup - if (setup) - setup(); + // Note: _coreConfig.isMetric is bool, hence empty EEPROM (=0xFF) evaluates to true (default) + hwReadConfigBlock((void*)&_coreConfig.controllerConfig, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, + sizeof(controllerConfig_t)); - #if defined(MY_RADIO_FEATURE) - presentNode(); - #endif - - // register node - _registerNode(); +#if defined(MY_OTA_FIRMWARE_FEATURE) + // Read firmware config from EEPROM, i.e. type, version, CRC, blocks + readFirmwareSettings(); +#endif - debug(PSTR("Init complete, id=%d, parent=%d, distance=%d, registration=%d\n"), _nc.nodeId, _nc.parentNodeId, _nc.distance, _nodeRegistered); -} +#if defined(MY_SENSOR_NETWORK) + // Save static parent ID in eeprom (used by bootloader) + hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, MY_PARENT_NODE_ID); + // Initialise transport layer + transportInitialise(); + // Register transport=ready callback + transportRegisterReadyCallback(_callbackTransportReady); + // wait until transport is ready + (void)transportWaitUntilReady(MY_TRANSPORT_WAIT_READY_MS); +#endif + + _checkNodeLock(); + +#if defined(MY_GATEWAY_FEATURE) +#if defined(MY_INCLUSION_BUTTON_FEATURE) + inclusionInit(); +#endif + + // initialise the transport driver + if (!gatewayTransportInit()) { + setIndication(INDICATION_ERR_INIT_GWTRANSPORT); + CORE_DEBUG(PSTR("!MCO:BGN:TSP FAIL\n")); + // Nothing more we can do + _infiniteLoop(); + } +#endif + // Call sketch setup() (if defined) + if (setup) { + CORE_DEBUG(PSTR("MCO:BGN:STP\n")); // setup callback + setup(); + } +#if defined(MY_SENSOR_NETWORK) + CORE_DEBUG(PSTR("MCO:BGN:INIT OK,TSP=%d\n"), isTransportReady()); +#else + // no sensor network defined, call presentation & registration + _callbackTransportReady(); + CORE_DEBUG(PSTR("MCO:BGN:INIT OK,TSP=NA\n")); +#endif + // reset wdt before handing over to loop + hwWatchdogReset(); +} -void _registerNode() { - #if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE) - debug(PSTR("Request registration...\n")); // registration request - setIndication(INDICATION_REQ_REGISTRATION); - _nodeRegistered = MY_REGISTRATION_DEFAULT; - uint8_t counter = MY_REGISTRATION_RETRIES; - // only proceed if register response received or retries exceeded - do { - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_REGISTRATION_REQUEST, false).set(MY_CORE_VERSION)); - } while (!wait(2000, C_INTERNAL, I_REGISTRATION_RESPONSE) && counter--); - #else - _nodeRegistered = true; - debug(PSTR("No registration required\n")); - #endif +void _registerNode(void) +{ +#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE) + CORE_DEBUG(PSTR("MCO:REG:REQ\n")); // registration request + setIndication(INDICATION_REQ_REGISTRATION); + _coreConfig.nodeRegistered = MY_REGISTRATION_DEFAULT; + uint8_t counter = MY_REGISTRATION_RETRIES; + // only proceed if register response received or retries exceeded + do { + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, + I_REGISTRATION_REQUEST).set(MY_CORE_VERSION)); + } while (!wait(2000, C_INTERNAL, I_REGISTRATION_RESPONSE) && counter--); +#else + _coreConfig.nodeRegistered = true; + CORE_DEBUG(PSTR("MCO:REG:NOT NEEDED\n")); +#endif } -void presentNode() { +void presentNode(void) +{ setIndication(INDICATION_PRESENT); // Present node and request config - #if defined(MY_GATEWAY_FEATURE) - // Send presentation for this gateway device - #if defined(MY_REPEATER_FEATURE) - present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE); - #else - present(NODE_SENSOR_ID, S_ARDUINO_NODE); - #endif - #else - - #if defined(MY_OTA_FIRMWARE_FEATURE) - presentBootloaderInformation(); - #endif - - // Send signing preferences for this node to the GW - signerPresentation(_msgTmp, GATEWAY_ADDRESS); - - // Send presentation for this radio node - #if defined(MY_REPEATER_FEATURE) - present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE); - #else - present(NODE_SENSOR_ID, S_ARDUINO_NODE); - #endif - - // Send a configuration exchange request to controller - // Node sends parent node. Controller answers with latest node configuration - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CONFIG, false).set(_nc.parentNodeId)); - - // Wait configuration reply. - wait(2000, C_INTERNAL, I_CONFIG); - - #endif - - if (presentation) - presentation(); +#if defined(MY_GATEWAY_FEATURE) + // Send presentation for this gateway device +#if defined(MY_REPEATER_FEATURE) + (void)present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE); +#else + (void)present(NODE_SENSOR_ID, S_ARDUINO_NODE); +#endif +#else + +#if defined(MY_OTA_FIRMWARE_FEATURE) + presentBootloaderInformation(); +#endif + // Send signing preferences for this node to the GW + signerPresentation(_msgTmp, GATEWAY_ADDRESS); + + // Send presentation for this radio node +#if defined(MY_REPEATER_FEATURE) + (void)present(NODE_SENSOR_ID, S_ARDUINO_REPEATER_NODE); +#else + (void)present(NODE_SENSOR_ID, S_ARDUINO_NODE); +#endif + + // Send a configuration exchange request to controller + // Node sends parent node. Controller answers with latest node configuration + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, + I_CONFIG).set(getParentNodeId())); + + // Wait configuration reply. + (void)wait(2000, C_INTERNAL, I_CONFIG); + +#endif + + if (presentation) { + presentation(); + } } -uint8_t getNodeId() { - return _nc.nodeId; +uint8_t getNodeId(void) +{ + uint8_t result; +#if defined(MY_GATEWAY_FEATURE) + result = GATEWAY_ADDRESS; +#elif defined(MY_SENSOR_NETWORK) + result = transportGetNodeId(); +#else + result = VALUE_NOT_DEFINED; +#endif + return result; } -uint8_t getParentNodeId() { - return _nc.parentNodeId; +uint8_t getParentNodeId(void) +{ + uint8_t result; +#if defined(MY_GATEWAY_FEATURE) + result = VALUE_NOT_DEFINED; // GW doesn't have a parent +#elif defined(MY_SENSOR_NETWORK) + result = transportGetParentNodeId(); +#else + result = VALUE_NOT_DEFINED; +#endif + return result; } -ControllerConfig getConfig() { - return _cc; +uint8_t getDistanceGW(void) +{ + uint8_t result; +#if defined(MY_GATEWAY_FEATURE) + result = 0; +#elif defined(MY_SENSOR_NETWORK) + result = transportGetDistanceGW(); +#else + result = VALUE_NOT_DEFINED; +#endif + return result; } +controllerConfig_t getConfig(void) +{ + return _coreConfig.controllerConfig; +} -bool _sendRoute(MyMessage &message) { - #if defined(MY_CORE_ONLY) - (void)message; - #endif - #if defined(MY_GATEWAY_FEATURE) - if (message.destination == _nc.nodeId) { - // This is a message sent from a sensor attached on the gateway node. - // Pass it directly to the gateway transport layer. - return gatewayTransportSend(message); - } - #endif - #if defined(MY_RADIO_FEATURE) - return transportSendRoute(message); - #else - return false; - #endif + +bool _sendRoute(MyMessage &message) +{ +#if defined(MY_CORE_ONLY) + (void)message; +#endif +#if defined(MY_GATEWAY_FEATURE) + if (message.destination == getNodeId()) { + // This is a message sent from a sensor attached on the gateway node. + // Pass it directly to the gateway transport layer. + return gatewayTransportSend(message); + } +#endif +#if defined(MY_SENSOR_NETWORK) + return transportSendRoute(message); +#else + return false; +#endif } -bool send(MyMessage &message, bool enableAck) { - message.sender = _nc.nodeId; +bool send(MyMessage &message, const bool enableAck) +{ + message.sender = getNodeId(); mSetCommand(message, C_SET); mSetRequestAck(message, enableAck); - #if defined(MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE) - if (_nodeRegistered) { - return _sendRoute(message); - } - else { - debug(PSTR("NODE:!REG\n")); - return false; - } - #else +#if defined(MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE) + if (_coreConfig.nodeRegistered) { return _sendRoute(message); - #endif + } else { + CORE_DEBUG(PSTR("!MCO:SND:NODE NOT REG\n")); // node not registered + return false; } +#else + return _sendRoute(message); +#endif +} -void sendBatteryLevel(uint8_t value, bool enableAck) { - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, enableAck).set(value)); +bool sendBatteryLevel(const uint8_t value, const bool ack) +{ + return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_BATTERY_LEVEL, + ack).set(value)); } -void sendHeartbeat(void) { - #if defined(MY_RADIO_NRF24) || defined(MY_RADIO_RFM69) || defined(MY_RS485) - uint32_t heartbeat = transportGetHeartbeat(); - #else - uint32_t heartbeat = hwMillis(); - #endif - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE, false).set(heartbeat)); +bool sendHeartbeat(const bool ack) +{ +#if defined(MY_SENSOR_NETWORK) + const uint32_t heartbeat = transportGetHeartbeat(); + return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_HEARTBEAT_RESPONSE, + ack).set(heartbeat)); +#else + (void)ack; + return false; +#endif } -void present(uint8_t childSensorId, uint8_t sensorType, const char *description, bool enableAck) { - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, enableAck).set(childSensorId==NODE_SENSOR_ID?MYSENSORS_LIBRARY_VERSION:description)); +bool present(const uint8_t childSensorId, const uint8_t sensorType, const char *description, + const bool ack) +{ + return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, childSensorId, C_PRESENTATION, sensorType, + ack).set(childSensorId==NODE_SENSOR_ID?MYSENSORS_LIBRARY_VERSION:description)); } -void sendSketchInfo(const char *name, const char *version, bool enableAck) { - if (name) _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME, enableAck).set(name)); - if (version) _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION, enableAck).set(version)); +bool sendSketchInfo(const char *name, const char *version, const bool ack) +{ + bool result = true; + if (name) { + result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_NAME, + ack).set(name)); + } + if (version) { + result &= _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_SKETCH_VERSION, + ack).set(version)); + } + return result; } -void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination) { - _sendRoute(build(_msgTmp, _nc.nodeId, destination, childSensorId, C_REQ, variableType, false).set("")); +bool request(const uint8_t childSensorId, const uint8_t variableType, const uint8_t destination) +{ + return _sendRoute(build(_msgTmp, destination, childSensorId, C_REQ, variableType).set("")); } -void requestTime() { - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, false).set("")); +bool requestTime(const bool ack) +{ + return _sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_TIME, ack).set("")); } // Message delivered through _msg -bool _processInternalMessages() { - uint8_t type = _msg.type; +bool _processInternalMessages(void) +{ + const uint8_t type = _msg.type; if (_msg.sender == GATEWAY_ADDRESS) { if (type == I_REBOOT) { - #if !defined(MY_DISABLE_REMOTE_RESET) - // Requires MySensors or other bootloader with watchdogs enabled - setIndication(INDICATION_REBOOT); - hwReboot(); - #endif - } - else if (type == I_REGISTRATION_RESPONSE) { - #if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE) - _nodeRegistered = _msg.getBool(); - setIndication(INDICATION_GOT_REGISTRATION); - debug(PSTR("Node registration=%d\n"), _nodeRegistered); - #endif - } - else if (type == I_CONFIG) { +#if !defined(MY_DISABLE_REMOTE_RESET) + // Requires MySensors or other bootloader with watchdogs enabled + setIndication(INDICATION_REBOOT); + hwReboot(); +#endif + } else if (type == I_REGISTRATION_RESPONSE) { +#if defined (MY_REGISTRATION_FEATURE) && !defined(MY_GATEWAY_FEATURE) + _coreConfig.nodeRegistered = _msg.getBool(); + setIndication(INDICATION_GOT_REGISTRATION); + CORE_DEBUG(PSTR("MCO:PIM:NODE REG=%d\n"), _coreConfig.nodeRegistered); // node registration +#endif + } else if (type == I_CONFIG) { // Pick up configuration from controller (currently only metric/imperial) and store it in eeprom if changed - _cc.isMetric = _msg.data[0] == 0x00 || _msg.data[0] == 'M'; // metric if null terminated or M - hwWriteConfig(EEPROM_CONTROLLER_CONFIG_ADDRESS, _cc.isMetric); - } - else if (type == I_PRESENTATION) { + _coreConfig.controllerConfig.isMetric = _msg.data[0] == 0x00 || + _msg.data[0] == 'M'; // metric if null terminated or M + hwWriteConfigBlock((void*)&_coreConfig.controllerConfig, (void*)EEPROM_CONTROLLER_CONFIG_ADDRESS, + sizeof(controllerConfig_t)); + } else if (type == I_PRESENTATION) { // Re-send node presentation to controller - #if defined(MY_RADIO_FEATURE) - presentNode(); - #endif - } - else if (type == I_HEARTBEAT) { - sendHeartbeat(); - } - else if (type == I_TIME) { + presentNode(); + } else if (type == I_HEARTBEAT_REQUEST) { + (void)sendHeartbeat(); + } else if (type == I_TIME) { // Deliver time to callback - if (receiveTime) + if (receiveTime) { receiveTime(_msg.getULong()); - } - else if (type == I_CHILDREN) { - #if defined(MY_REPEATER_FEATURE) - if (_msg.data[0] == 'C') { - // Clears child relay data for this node - setIndication(INDICATION_CLEAR_ROUTING); - transportClearRoutingTable(); - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN, false).set("ok")); - } - #endif - } - else if (type == I_DEBUG) { - #if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) - char debug_msg = _msg.data[0]; - if (debug_msg == 'R') { // routing table - #if defined(MY_REPEATER_FEATURE) - for (uint8_t cnt = 0; cnt != 255; cnt++) { - uint8_t route = hwReadConfig(EEPROM_ROUTES_ADDRESS + cnt); - if (route != BROADCAST_ADDRESS) { - debug(PSTR("ID: %d via %d\n"), cnt, route); - uint8_t OutBuf[2] = { cnt,route }; - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(OutBuf, 2)); - wait(200); - } + } + } else if (type == I_CHILDREN) { +#if defined(MY_REPEATER_FEATURE) + if (_msg.data[0] == 'C') { + // Clears child relay data for this node + setIndication(INDICATION_CLEAR_ROUTING); + transportClearRoutingTable(); + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_CHILDREN).set("OK")); + } +#endif + } else if (type == I_DEBUG) { +#if defined(MY_DEBUG) || defined(MY_SPECIAL_DEBUG) + const char debug_msg = _msg.data[0]; + if (debug_msg == 'R') { // routing table +#if defined(MY_REPEATER_FEATURE) + for (uint16_t cnt = 0; cnt < SIZE_ROUTES; cnt++) { + const uint8_t route = transportGetRoute(cnt); + if (route != BROADCAST_ADDRESS) { + CORE_DEBUG(PSTR("MCO:PIM:ROUTE N=%d,R=%d\n"), cnt, route); + uint8_t outBuf[2] = { (uint8_t)cnt,route }; + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG).set(outBuf, + 2)); + wait(200); } - #endif - } - else if (debug_msg == 'V') { // CPU voltage - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUVoltage())); - } - else if (debug_msg == 'F') { // CPU frequency in 1/10Mhz - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwCPUFrequency())); - } - else if (debug_msg == 'M') { // free memory - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set(hwFreeMem())); } - else if (debug_msg == 'E') { // clear MySensors eeprom area and reboot - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_DEBUG, false).set("ok")); - for (int i = EEPROM_START; i= MY_CORE_MIN_VERSION); - #endif - _sendRoute(build(_msgTmp, _nc.nodeId, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_REGISTRATION_RESPONSE, false).set(approveRegistration)); - #else - return false; // processing of this request via controller - #endif - #endif +#if defined(MY_GATEWAY_FEATURE) + // registeration requests are exclusively handled by GW/Controller +#if !defined(MY_REGISTRATION_CONTROLLER) + bool approveRegistration; + +#if defined(MY_CORE_COMPATIBILITY_CHECK) + approveRegistration = (_msg.getByte() >= MY_CORE_MIN_VERSION); +#else + // auto registration if version compatible + approveRegistration = true; +#endif + +#if (F_CPU>16000000) + // delay for fast GW and slow nodes + delay(5); +#endif + (void)_sendRoute(build(_msgTmp, _msg.sender, NODE_SENSOR_ID, C_INTERNAL, + I_REGISTRATION_RESPONSE).set(approveRegistration)); +#else + return false; // processing of this request via controller +#endif +#endif + } else { + return false; } - else return false; } return true; } -void saveState(uint8_t pos, uint8_t value) { +void saveState(const uint8_t pos, const uint8_t value) +{ hwWriteConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos, value); } -uint8_t loadState(uint8_t pos) { +uint8_t loadState(const uint8_t pos) +{ return hwReadConfig(EEPROM_LOCAL_CONFIG_ADDRESS+pos); } -void wait(unsigned long ms) { - unsigned long enter = hwMillis(); - while (hwMillis() - enter < ms) { +void wait(const uint32_t waitingMS) +{ + const uint32_t enteringMS = hwMillis(); + while (hwMillis() - enteringMS < waitingMS) { _process(); - #if defined(ARDUINO_ARCH_ESP8266) - yield(); - #endif } } -bool wait(unsigned long ms, uint8_t cmd, uint8_t msgtype) { - unsigned long enter = hwMillis(); +bool wait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgType) +{ + const uint32_t enteringMS = hwMillis(); // invalidate msg type - _msg.type = !msgtype; + _msg.type = !msgType; bool expectedResponse = false; - while ( (hwMillis() - enter < ms) && !expectedResponse ) { + while ( (hwMillis() - enteringMS < waitingMS) && !expectedResponse ) { _process(); - #if defined(ARDUINO_ARCH_ESP8266) - yield(); - #endif - expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype); + expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgType); } return expectedResponse; } +void doYield(void) +{ + hwWatchdogReset(); -int8_t sleep(unsigned long ms) { - #if defined(MY_OTA_FIRMWARE_FEATURE) - if (_fwUpdateOngoing) { - // Do not sleep node while fw update is ongoing - wait(ms); - return -1; + yield(); + +#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) + ledsProcess(); +#endif +} + +int8_t _sleep(const uint32_t sleepingMS, const bool smartSleep, const uint8_t interrupt1, + const uint8_t mode1, const uint8_t interrupt2, const uint8_t mode2) +{ + CORE_DEBUG(PSTR("MCO:SLP:MS=%lu,SMS=%d,I1=%d,M1=%d,I2=%d,M2=%d\n"), sleepingMS, smartSleep, + interrupt1, mode1, interrupt2, mode2); + // OTA FW feature: do not sleep if FW update ongoing +#if defined(MY_OTA_FIRMWARE_FEATURE) + if (isFirmwareUpdateOngoing()) { + debug(PSTR("!MCO:SLP:FWUPD\n")); // sleeping not possible, FW update ongoing + wait(sleepingMS); + return MY_SLEEP_NOT_POSSIBLE; } - #endif - // if repeater, do not sleep - #if defined(MY_REPEATER_FEATURE) - wait(ms); - return -1; - #else - #if defined(MY_RADIO_FEATURE) - transportPowerDown(); - #endif - setIndication(INDICATION_SLEEP); - const int8_t res = hwSleep(ms); - setIndication(INDICATION_WAKEUP); - return res; - #endif +#endif + // repeater feature: sleeping not possible +#if defined(MY_REPEATER_FEATURE) + (void)smartSleep; + (void)interrupt1; + (void)mode1; + (void)interrupt2; + (void)mode2; + + CORE_DEBUG(PSTR("!MCO:SLP:REP\n")); // sleeping not possible, repeater feature enabled + wait(sleepingMS); + return MY_SLEEP_NOT_POSSIBLE; +#else + uint32_t sleepingTimeMS = sleepingMS; +#if defined(MY_SENSOR_NETWORK) + // Do not sleep if transport not ready + if (!isTransportReady()) { + CORE_DEBUG(PSTR("!MCO:SLP:TNR\n")); // sleeping not possible, transport not ready + const uint32_t sleepEnterMS = hwMillis(); + uint32_t sleepDeltaMS = 0; + while (!isTransportReady() && (sleepDeltaMS < sleepingTimeMS) && + (sleepDeltaMS < MY_SLEEP_TRANSPORT_RECONNECT_TIMEOUT_MS)) { + _process(); + sleepDeltaMS = hwMillis() - sleepEnterMS; + } + // sleep remainder + if (sleepDeltaMS < sleepingTimeMS) { + sleepingTimeMS -= sleepDeltaMS; // calculate remaining sleeping time + CORE_DEBUG(PSTR("MCO:SLP:MS=%lu\n"), sleepingTimeMS); + } else { + // no sleeping time left + return MY_SLEEP_NOT_POSSIBLE; + } + } +#endif + + if (smartSleep) { + // notify controller about going to sleep + (void)sendHeartbeat(); + wait(MY_SMART_SLEEP_WAIT_DURATION_MS); // listen for incoming messages + } + +#if defined(MY_SENSOR_NETWORK) + CORE_DEBUG(PSTR("MCO:SLP:TPD\n")); // sleep, power down transport + transportPowerDown(); +#endif + +#if defined (MY_DEFAULT_TX_LED_PIN) || defined(MY_DEFAULT_RX_LED_PIN) || defined(MY_DEFAULT_ERR_LED_PIN) + // Wait until leds finish their blinking pattern + while (ledsBlinking()) { + doYield(); + } +#endif + + setIndication(INDICATION_SLEEP); + + int8_t result = MY_SLEEP_NOT_POSSIBLE; // default + + if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 != INTERRUPT_NOT_DEFINED) { + // both IRQs + result = hwSleep(interrupt1, mode1, interrupt2, mode2, sleepingTimeMS); + } else if (interrupt1 != INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) { + // one IRQ + result = hwSleep(interrupt1, mode1, sleepingTimeMS); + } else if (interrupt1 == INTERRUPT_NOT_DEFINED && interrupt2 == INTERRUPT_NOT_DEFINED) { + // no IRQ + result = hwSleep(sleepingTimeMS); + } + + setIndication(INDICATION_WAKEUP); + CORE_DEBUG(PSTR("MCO:SLP:WUP=%d\n"), result); // sleep wake-up + return result; +#endif } -int8_t smartSleep(unsigned long ms) { - int8_t ret = sleep(ms); - // notifiy controller about wake up - sendHeartbeat(); - // listen for incoming messages - wait(MY_SMART_SLEEP_WAIT_DURATION); - return ret; +// sleep functions +int8_t sleep(const uint32_t sleepingMS, const bool smartSleep) +{ + return _sleep(sleepingMS, smartSleep); } -int8_t sleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { - #if defined(MY_OTA_FIRMWARE_FEATURE) - if (_fwUpdateOngoing) { - // not supported - return -2; - } - #endif - #if defined(MY_REPEATER_FEATURE) - // not supported - (void)interrupt; - (void)mode; - (void)ms; - return -2; - #else - #if defined(MY_RADIO_FEATURE) - transportPowerDown(); - #endif - setIndication(INDICATION_SLEEP); - const int8_t res = hwSleep(interrupt, mode, ms); - setIndication(INDICATION_WAKEUP); - return res; - #endif +int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS, + const bool smartSleep) +{ + return _sleep(sleepingMS, smartSleep, interrupt, mode); } -int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms) { - int8_t ret = sleep(interrupt, mode, ms); - // notifiy controller about wake up - sendHeartbeat(); - // listen for incoming messages - wait(MY_SMART_SLEEP_WAIT_DURATION); - return ret; +int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, + const uint8_t mode2, const uint32_t sleepingMS, const bool smartSleep) +{ + return _sleep(sleepingMS, smartSleep, interrupt1, mode1, interrupt2, mode2); } -int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { - #if defined(MY_OTA_FIRMWARE_FEATURE) - if (_fwUpdateOngoing) { - // not supported - return -2; - } - #endif - #if defined(MY_REPEATER_FEATURE) - // not supported - (void)interrupt1; - (void)mode1; - (void)interrupt2; - (void)mode2; - (void)ms; - return -2; - #else - #if defined(MY_RADIO_FEATURE) - transportPowerDown(); - #endif - setIndication(INDICATION_SLEEP); - const int8_t res = hwSleep(interrupt1, mode1, interrupt2, mode2, ms); - setIndication(INDICATION_WAKEUP); - return res; - #endif +// deprecated smartSleep() functions +int8_t smartSleep(const uint32_t sleepingMS) +{ + // compatibility + return _sleep(sleepingMS, true); } -int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms) { - int8_t ret = sleep(interrupt1, mode1, interrupt2, mode2, ms); - // notifiy controller about wake up - sendHeartbeat(); - // listen for incoming messages - wait(MY_SMART_SLEEP_WAIT_DURATION); - return ret; +int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS) +{ + // compatibility + return _sleep(sleepingMS, true, interrupt, mode); } +int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, + const uint8_t mode2, const uint32_t sleepingMS) +{ + // compatibility + return _sleep(sleepingMS, true, interrupt1, mode1, interrupt2, mode2); +} + + + +void _nodeLock(const char* str) +{ #ifdef MY_NODE_LOCK_FEATURE -void nodeLock(const char* str) { // Make sure EEPROM is updated to locked status hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, 0); while (1) { setIndication(INDICATION_ERR_LOCKED); - debug(PSTR("Node is locked. Ground pin %d and reset to unlock.\n"), MY_NODE_UNLOCK_PIN); - #if defined(ARDUINO_ARCH_ESP8266) - yield(); - #endif - _sendRoute(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED, false).set(str)); - #if defined(MY_RADIO_FEATURE) - transportPowerDown(); - #endif + CORE_DEBUG(PSTR("MCO:NLK:NODE LOCKED. TO UNLOCK, GND PIN %d AND RESET\n"), MY_NODE_UNLOCK_PIN); + doYield(); + (void)_sendRoute(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID,C_INTERNAL, I_LOCKED).set(str)); +#if defined(MY_SENSOR_NETWORK) + transportPowerDown(); + CORE_DEBUG(PSTR("MCO:NLK:TPD\n")); // power down transport +#endif setIndication(INDICATION_SLEEP); (void)hwSleep((unsigned long)1000*60*30); // Sleep for 30 min before resending LOCKED message setIndication(INDICATION_WAKEUP); } -} +#else + (void)str; #endif +} +void _checkNodeLock(void) +{ +#ifdef MY_NODE_LOCK_FEATURE + // Check if node has been locked down + if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0) { + // Node is locked, check if unlock pin is asserted, else hang the node + hwPinMode(MY_NODE_UNLOCK_PIN, INPUT_PULLUP); + // Make a short delay so we are sure any large external nets are fully pulled + unsigned long enter = hwMillis(); + while (hwMillis() - enter < 2) {} + if (hwDigitalRead(MY_NODE_UNLOCK_PIN) == 0) { + // Pin is grounded, reset lock counter + hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX); + // Disable pullup + hwPinMode(MY_NODE_UNLOCK_PIN, INPUT); + setIndication(INDICATION_ERR_LOCKED); + CORE_DEBUG(PSTR("MCO:BGN:NODE UNLOCKED\n")); + } else { + // Disable pullup + hwPinMode(MY_NODE_UNLOCK_PIN, INPUT); + _nodeLock("LDB"); //Locked during boot + } + } else if (hwReadConfig(EEPROM_NODE_LOCK_COUNTER) == 0xFF) { + // Reset walue + hwWriteConfig(EEPROM_NODE_LOCK_COUNTER, MY_NODE_LOCK_COUNTER_MAX); + } +#endif +} diff --git a/core/MySensorsCore.h b/core/MySensorsCore.h index cde376566..f0234dcc7 100644 --- a/core/MySensorsCore.h +++ b/core/MySensorsCore.h @@ -17,6 +17,52 @@ * version 2 as published by the Free Software Foundation. */ +/** +* @file MySensorsCore.h +* +* @defgroup MySensorsCoregrp MySensorsCore +* @ingroup internals +* @{ +* +* MySensorsCore-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE +* - [!] Exclamation mark is prepended in case of error or warning +* - SYSTEM: +* - MCO messages emitted by MySensorsCore +* - SUB SYSTEMS: +* - MCO:BGN from @ref _begin() +* - MCO:REG from @ref _registerNode() +* - MCO:SND from @ref send() +* - MCO:PIM from @ref _processInternalMessages() +* - MCO:NLK from nodeLock() +* +* MySensorsCore debug log messages: +* +* |E| SYS | SUB | Message | Comment +* |-|------|-------|-----------------------------------------------|---------------------------------------------------------------------------- +* | | MCO | BGN | INIT %%s,CP=%%s,LIB=%%s | Core initialization, capabilities (CP), library version (VER) +* | | MCO | BGN | BFR | Callback before() +* | | MCO | BGN | MTR | MY_TRANSPORT_RELAXED enabled +* | | MCO | BGN | STP | Callback setup() +* | | MCO | BGN | INIT OK,TSP=%%d | Core initialised, transport status (TSP), 1=initialised, 0=not initialised, NA=not available +* | | MCO | BGN | NODE UNLOCKED | Node successfully unlocked (see signing chapter) +* |!| MCO | BGN | TSP FAIL | Transport initialization failed +* | | MCO | REG | REQ | Registration request +* | | MCO | REG | NOT NEEDED | No registration needed (i.e. GW) +* |!| MCO | SND | NODE NOT REG | Node is not registered, cannot send message +* | | MCO | PIM | NODE REG=%%d | Registration response received, registration status (REG) +* | | MCO | PIM | ROUTE N=%%d,R=%%d | Routing table, messages to node (N) are routed via node (R) +* | | MCO | SLP | MS=%%lu,SMS=%%d,I1=%%d,M1=%%d,I2=%%d,M2=%%d | Sleep node, time (MS), smartSleep (SMS), Int1/M1, Int2/M2 +* | | MCO | SLP | TPD | Sleep node, powerdown transport +* | | MCO | SLP | WUP=%%d | Node woke-up, reason/IRQ (WUP) +* |!| MCO | SLP | FWUPD | Sleeping not possible, FW update ongoing +* |!| MCO | SLP | REP | Sleeping not possible, repeater feature enabled +* | | MCO | NLK | NODE LOCKED. UNLOCK: GND PIN %%d AND RESET | Node locked during booting, see signing chapter for additional information +* | | MCO | NLK | TPD | Powerdown transport +* +* +* @brief API declaration for MySensorsCore +*/ + #ifndef MySensorsCore_h #define MySensorsCore_h @@ -28,54 +74,64 @@ #include #include +#define GATEWAY_ADDRESS ((uint8_t)0) //!< Node ID for GW sketch +#define NODE_SENSOR_ID ((uint8_t)255) //!< Node child is always created/presented when a node is started +#define MY_CORE_VERSION ((uint8_t)2) //!< core version +#define MY_CORE_MIN_VERSION ((uint8_t)2) //!< min core version required for compatibility + +#define MY_WAKE_UP_BY_TIMER ((int8_t)-1) //!< Sleeping wake up by timer +#define MY_SLEEP_NOT_POSSIBLE ((int8_t)-2) //!< Sleeping not possible +#define INTERRUPT_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no interrupt defined +#define MODE_NOT_DEFINED ((uint8_t)255) //!< _sleep() param: no mode defined +#define VALUE_NOT_DEFINED ((uint8_t)255) //!< Value not defined + + #ifdef MY_DEBUG - #define debug(x,...) hwDebugPrint(x, ##__VA_ARGS__) +#define debug(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug, to be removed (follow-up PR) +#define CORE_DEBUG(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug #else - #define debug(x,...) +#define debug(x,...) //!< debug NULL, to be removed (follow-up PR) +#define CORE_DEBUG(x,...) //!< debug NULL #endif -#define GATEWAY_ADDRESS ((uint8_t)0) //!< Node ID for GW sketch -#define NODE_SENSOR_ID 0xFF //!< Node child is always created/presented when a node is started -#define MY_CORE_VERSION ((uint8_t)2) //!< core version -#define MY_CORE_MIN_VERSION ((uint8_t)2) //!< min core version required for compatibility - /** - * @brief Node configuration + * @brief Controller configuration * - * This structure stores node-related configurations + * This structure stores controller-related configurations */ -struct NodeConfig { - uint8_t nodeId; //!< Current node id - uint8_t parentNodeId; //!< Where this node sends its messages - uint8_t distance; //!< This nodes distance to sensor net gateway (number of hops) -}; +typedef struct { + uint8_t isMetric; //!< Flag indicating if metric or imperial measurements are used +} controllerConfig_t; /** - * @brief Controller configuration - * - * This structure stores controllerrelated configurations - */ -struct ControllerConfig { - uint8_t isMetric; //!< Flag indicating if metric or imperial measurements are used -}; +* @brief Node core configuration +*/ +typedef struct { + controllerConfig_t controllerConfig; //!< Controller config + // 8 bit + bool nodeRegistered : 1; //!< Flag node registered + bool presentationSent : 1; //!< Flag presentation sent + uint8_t reserved : 6; //!< reserved +} coreConfig_t; +// **** public functions ******** /** * Return this nodes id. */ -uint8_t getNodeId(); +uint8_t getNodeId(void); /** * Return the parent node id. */ -uint8_t getParentNodeId(); +uint8_t getParentNodeId(void); /** * Sends node information to the gateway. */ -void presentNode(); +void presentNode(void); /** * Each node must present all attached sensors before any values can be handled correctly by the controller. @@ -86,17 +142,19 @@ void presentNode(); * @param description A textual description of the sensor. * @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack. * @param description A textual description of the sensor. +* @return true Returns true if message reached the first stop on its way to destination. */ -void present(uint8_t sensorId, uint8_t sensorType, const char *description="", bool ack=false); +bool present(const uint8_t sensorId, const uint8_t sensorType, const char *description="", + const bool ack = false); /** * Sends sketch meta information to the gateway. Not mandatory but a nice thing to do. * @param name String containing a short Sketch name or NULL if not applicable * @param version String containing a short Sketch version or NULL if not applicable * @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack. - * + * @return true Returns true if message reached the first stop on its way to destination. */ -void sendSketchInfo(const char *name, const char *version, bool ack=false); +bool sendSketchInfo(const char *name, const char *version, const bool ack = false); /** * Sends a message to gateway or one of the other nodes in the radio network @@ -105,22 +163,24 @@ void sendSketchInfo(const char *name, const char *version, bool ack=false); * @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack. * @return true Returns true if message reached the first stop on its way to destination. */ -bool send(MyMessage &msg, bool ack=false); +bool send(MyMessage &msg, const bool ack = false); /** * Send this nodes battery level to gateway. * @param level Level between 0-100(%) * @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack. - * + * @return true Returns true if message reached the first stop on its way to destination. */ -void sendBatteryLevel(uint8_t level, bool ack=false); +bool sendBatteryLevel(const uint8_t level, const bool ack = false); /** * Send a heartbeat message (I'm alive!) to the gateway/controller. * The payload will be an incremental 16 bit integer value starting at 1 when sensor is powered on. + * @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack. + * @return true Returns true if message reached the first stop on its way to destination. */ -void sendHeartbeat(void); +bool sendHeartbeat(const bool ack = false); /** * Requests a value from gateway or some other sensor in the radio network. @@ -129,21 +189,22 @@ void sendHeartbeat(void); * @param childSensorId The unique child id for the different sensors connected to this Arduino. 0-254. * @param variableType The variableType to fetch * @param destination The nodeId of other node in radio network. Default is gateway +* @return true Returns true if message reached the first stop on its way to destination. */ -void request(uint8_t childSensorId, uint8_t variableType, uint8_t destination=GATEWAY_ADDRESS); +bool request(const uint8_t childSensorId, const uint8_t variableType, + const uint8_t destination = GATEWAY_ADDRESS); /** * Requests time from controller. Answer will be delivered to receiveTime function in sketch. - * + * @param ack Set this to true if you want destination node to send ack back to this node. Default is not to request any ack. + * @return true Returns true if message reached the first stop on its way to destination. */ -void requestTime(); - - +bool requestTime(const bool ack = false); /** * Returns the most recent node configuration received from controller */ -ControllerConfig getConfig(); +controllerConfig_t getControllerConfig(void); /** * Save a state (in local EEPROM). Good for actuators to "remember" state between @@ -155,7 +216,7 @@ ControllerConfig getConfig(); * @param pos The position to store value in (0-255) * @param value to store in position */ -void saveState(uint8_t pos, uint8_t value); +void saveState(const uint8_t pos, const uint8_t value); /** * Load a state (from local EEPROM). @@ -163,36 +224,43 @@ void saveState(uint8_t pos, uint8_t value); * @param pos The position to fetch value from (0-255) * @return Value to store in position */ -uint8_t loadState(uint8_t pos); +uint8_t loadState(const uint8_t pos); /** * Wait for a specified amount of time to pass. Keeps process()ing. * This does not power-down the radio nor the Arduino. * Because this calls process() in a loop, it is a good way to wait * in your loop() on a repeater node or sensor that listens to messages. - * @param ms Number of milliseconds to sleep. + * @param waitingMS Number of milliseconds to wait. */ -void wait(unsigned long ms); +void wait(const uint32_t waitingMS); /** * Wait for a specified amount of time to pass or until specified message received. Keeps process()ing. * This does not power-down the radio nor the Arduino. * Because this calls process() in a loop, it is a good way to wait * in your loop() on a repeater node or sensor that listens to messages. - * @param ms Number of milliseconds to sleep. + * @param waitingMS Number of milliseconds to wait. * @param cmd Command of incoming message. * @param msgtype Message type. * @return True if specified message received */ -bool wait(unsigned long ms, uint8_t cmd, uint8_t msgtype); +bool wait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgtype); + +/** + * Function to allow scheduler to do some work. + * @remark Internally it will call yield, kick the watchdog and update led states. + */ +void doYield(void); + /** * Sleep (PowerDownMode) the MCU and radio. Wake up on timer. - * @param ms Number of milliseconds to sleep. - * @return -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update) + * @param sleepingMS Number of milliseconds to sleep. + * @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep. + * @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update) */ -int8_t sleep(unsigned long ms); -int8_t smartSleep(unsigned long ms); +int8_t sleep(const uint32_t sleepingMS, const bool smartSleep = false); /** * Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change. @@ -200,11 +268,12 @@ int8_t smartSleep(unsigned long ms); * is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3 * @param interrupt Interrupt that should trigger the wakeup * @param mode RISING, FALLING, CHANGE - * @param ms Number of milliseconds to sleep or 0 to sleep forever - * @return Interrupt number wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update) + * @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever + * @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep + * @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update) */ -int8_t sleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0); -int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0); +int8_t sleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS = 0, + const bool smartSleep = false); /** * Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts. @@ -214,71 +283,161 @@ int8_t smartSleep(uint8_t interrupt, uint8_t mode, unsigned long ms=0); * @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE) * @param interrupt2 Second interrupt that should trigger the wakeup * @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE) - * @param ms Number of milliseconds to sleep or 0 to sleep forever - * @return Interrupt number wake up was triggered by pin change, -1 if timer woke it up, -2 if not possible (e.g. ongoing FW update) + * @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever + * @param smartSleep Set True if sending heartbeat and process incoming messages before going to sleep. + * @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update) */ -int8_t sleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0); -int8_t smartSleep(uint8_t interrupt1, uint8_t mode1, uint8_t interrupt2, uint8_t mode2, unsigned long ms=0); +int8_t sleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, + const uint8_t mode2, const uint32_t sleepingMS = 0, const bool smartSleep = false); + +/** +* \deprecated Use sleep(ms, true) instead +* Same as sleep(), send heartbeat and process incoming messages before going to sleep. +* Specify the time to wait for incoming messages by defining MY_SMART_SLEEP_WAIT_DURATION to a time (ms). +* @param sleepingMS Number of milliseconds to sleep. +* @return @ref MY_WAKE_UP_BY_TIMER if timer woke it up, @ref MY_SLEEP_NOT_POSSIBLE if not possible (e.g. ongoing FW update) +*/ +int8_t smartSleep(const uint32_t sleepingMS); + +/** +* \deprecated Use sleep(interrupt, mode, ms, true) instead +* Same as sleep(), send heartbeat and process incoming messages before going to sleep. +* Specify the time to wait for incoming messages by defining MY_SMART_SLEEP_WAIT_DURATION to a time (ms). +* @param interrupt Interrupt that should trigger the wakeup +* @param mode RISING, FALLING, CHANGE +* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever +* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update) +*/ +int8_t smartSleep(const uint8_t interrupt, const uint8_t mode, const uint32_t sleepingMS = 0); + +/** +* \deprecated Use sleep(interrupt1, mode1, interrupt2, mode2, ms, true) instead +* Same as sleep(), send heartbeat and process incoming messages before going to sleep. +* Specify the time to wait for incoming messages by defining MY_SMART_SLEEP_WAIT_DURATION to a time (ms). +* @param interrupt1 First interrupt that should trigger the wakeup +* @param mode1 Mode for first interrupt (RISING, FALLING, CHANGE) +* @param interrupt2 Second interrupt that should trigger the wakeup +* @param mode2 Mode for second interrupt (RISING, FALLING, CHANGE) +* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever +* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update) +*/ +int8_t smartSleep(const uint8_t interrupt1, const uint8_t mode1, const uint8_t interrupt2, + const uint8_t mode2, const uint32_t sleepingMS = 0); + +/** +* Sleep (PowerDownMode) the MCU and radio. Wake up on timer or pin change for two separate interrupts. +* See: http://arduino.cc/en/Reference/attachInterrupt for details on modes and which pin +* is assigned to what interrupt. On Nano/Pro Mini: 0=Pin2, 1=Pin3 +* @param sleepingMS Number of milliseconds to sleep or 0 to sleep forever +* @param interrupt1 (optional) First interrupt that should trigger the wakeup +* @param mode1 (optional) Mode for first interrupt (RISING, FALLING, CHANGE) +* @param interrupt2 (optional) Second interrupt that should trigger the wakeup +* @param mode2 (optional) Mode for second interrupt (RISING, FALLING, CHANGE) +* @param smartSleep (optional) Set True if sending heartbeat and process incoming messages before going to sleep. +* @return Interrupt number if wake up was triggered by pin change, @ref MY_WAKE_UP_BY_TIMER if wake up was triggered by timer, @ref MY_SLEEP_NOT_POSSIBLE if sleep was not possible (e.g. ongoing FW update) +*/ +int8_t _sleep(const uint32_t sleepingMS, const bool smartSleep = false, + const uint8_t interrupt1 = INTERRUPT_NOT_DEFINED, const uint8_t mode1 = MODE_NOT_DEFINED, + const uint8_t interrupt2 = INTERRUPT_NOT_DEFINED, const uint8_t mode2 = MODE_NOT_DEFINED); + -#ifdef MY_NODE_LOCK_FEATURE /** * @ingroup MyLockgrp - * @ingroup internals * @brief Lock a node and transmit provided message with 30m intervals * * This function is called if suspicious activity has exceeded the threshold (see - * @ref ATTACK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM - * while unlocking with a custom bootloader require holding @ref UNLOCK_PIN low during power on/reset. + * @ref MY_NODE_LOCK_COUNTER_MAX). Unlocking with a normal Arduino bootloader require erasing the EEPROM + * while unlocking with a custom bootloader require holding @ref MY_NODE_UNLOCK_PIN low during power on/reset. * * @param str The string to transmit. */ -void nodeLock(const char* str); -#endif +void _nodeLock(const char* str); -/****** PRIVATE ********/ +/** + * @brief Check node lock status and prevent node execution if locked. + */ +void _checkNodeLock(void); -void _begin(); +// **** private functions ******** +/** +* @brief Node initialisation +*/ +void _begin(void); +/** +* @brief Main framework process +*/ void _process(void); - -bool _processInternalMessages(); - -void _infiniteLoop(); - -void _registerNode(); - +/** +* @brief Processes internal messages +* @return True if received message requires further processing +*/ +bool _processInternalMessages(void); +/** +* @brief Puts node to a infinite loop if unrecoverable situation detected +*/ +void _infiniteLoop(void); +/** +* @brief Handles registration request +*/ +void _registerNode(void); +/** +* @brief Sends message according to routing table +* @param message +* @return true Returns true if message reached the first stop on its way to destination. +*/ bool _sendRoute(MyMessage &message); -extern NodeConfig _nc; -extern MyMessage _msg; // Buffer for incoming messages. -extern MyMessage _msgTmp; // Buffer for temporary messages (acks and nonces among others). -#ifdef MY_DEBUG - extern char _convBuf[MAX_PAYLOAD*2+1]; -#endif +/** +* @brief Callback for incoming messages +* @param message +*/ void receive(const MyMessage &message) __attribute__((weak)); +/** +* @brief Callback for incoming time messages +*/ void receiveTime(unsigned long) __attribute__((weak)); -void presentation() __attribute__((weak)); -void before() __attribute__((weak)); -void setup() __attribute__((weak)); -void loop() __attribute__((weak)); +/** +* @brief Node presenation +*/ +void presentation(void) __attribute__((weak)); +/** +* @brief Called before node initialises +*/ +void before(void) __attribute__((weak)); +/** +* @brief Called before any hwInitialization is done +*/ +void preHwInit(void) __attribute__((weak)); +/** +* @brief Called after node initialises but before main loop +*/ +void setup(void) __attribute__((weak)); +/** +* @brief Main loop +*/ +void loop(void) __attribute__((weak)); // Inline function and macros -static inline MyMessage& build(MyMessage &msg, uint8_t sender, uint8_t destination, uint8_t sensor, uint8_t command, uint8_t type, bool enableAck) { - msg.sender = sender; +static inline MyMessage& build(MyMessage &msg, const uint8_t destination, const uint8_t sensor, + const uint8_t command, const uint8_t type, const bool ack = false) +{ + msg.sender = getNodeId(); msg.destination = destination; msg.sensor = sensor; msg.type = type; mSetCommand(msg,command); - mSetRequestAck(msg,enableAck); + mSetRequestAck(msg,ack); mSetAck(msg,false); return msg; } -static inline MyMessage& buildGw(MyMessage &msg, uint8_t type) { +static inline MyMessage& buildGw(MyMessage &msg, const uint8_t type) +{ msg.sender = GATEWAY_ADDRESS; msg.destination = GATEWAY_ADDRESS; - msg.sensor = 255; + msg.sensor = NODE_SENSOR_ID; msg.type = type; mSetCommand(msg, C_INTERNAL); mSetRequestAck(msg, false); @@ -288,3 +447,5 @@ static inline MyMessage& buildGw(MyMessage &msg, uint8_t type) { #endif + +/** @}*/ diff --git a/core/MySigning.cpp b/core/MySigning.cpp index a1081a435..35c2ff550 100644 --- a/core/MySigning.cpp +++ b/core/MySigning.cpp @@ -33,6 +33,12 @@ #if defined(MY_SIGNING_REQUEST_SIGNATURES) && (!defined(MY_SIGNING_ATSHA204) && !defined(MY_SIGNING_SOFT)) #error You have to pick either MY_SIGNING_ATSHA204 or MY_SIGNING_SOFT in order to require signatures! #endif +#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL) && !defined(MY_SIGNING_REQUEST_SIGNATURES) +#error You have to require signatures if you want to require signatures from all (also enable MY_SIGNING_REQUEST_SIGNATURES in your gateway) +#endif +#if defined(MY_SIGNING_SOFT) && defined(MY_SIGNING_ATSHA204) +#error You have to pick one and only one signing backend +#endif #ifdef MY_SIGNING_FEATURE uint8_t _doSign[32]; // Bitfield indicating which sensors require signed communication uint8_t _doWhitelist[32]; // Bitfield indicating which sensors require serial salted signatures @@ -44,23 +50,17 @@ static uint8_t nof_nonce_requests = 0; static uint8_t nof_failed_verifications = 0; #endif -// Status when waiting for signing nonce in signerProcessInternal +// Status when waiting for signing nonce in signerSignMsg enum { SIGN_WAITING_FOR_NONCE = 0, SIGN_OK = 1 }; -// Macros for manipulating signing requirement table +// Macros for manipulating signing requirement tables #define DO_SIGN(node) (~_doSign[node>>3]&(1<>3]&=~(1<>3]|=(1<>3]&(1<>3]&=~(1<>3]|=(1<= MY_NODE_LOCK_COUNTER_MAX) { - nodeLock("TMNR"); //Too many nonces requested - } -#endif -#if defined(MY_SIGNING_SOFT) - if (signerAtsha204SoftGetNonce(msg)) { -#endif -#if defined(MY_SIGNING_ATSHA204) - if (signerAtsha204GetNonce(msg)) { -#endif - if (!_sendRoute(build(msg, _nc.nodeId, msg.sender, NODE_SENSOR_ID, - C_INTERNAL, I_NONCE_RESPONSE, false))) { - SIGN_DEBUG(PSTR("Failed to transmit nonce!\n")); - } else { - SIGN_DEBUG(PSTR("Transmitted nonce\n")); - } - } else { - SIGN_DEBUG(PSTR("Failed to generate nonce!\n")); - } - return true; // No need to further process I_NONCE_REQUEST - } else if (msg.type == I_SIGNING_PRESENTATION) { - if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) { - SIGN_DEBUG(PSTR("Unsupported signing presentation version (%d)!\n"), msg.data[0]); - return true; // Just drop this presentation message - } - - // We only handle version 1 here... - if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) { - // We received an indicator that the sender require us to sign all messages we send to it - SIGN_DEBUG(PSTR("Mark node %d as one that require signed messages\n"), msg.sender); - SET_SIGN(msg.sender); - } else { - // We received an indicator that the sender does not require us to sign all messages we send to it - SIGN_DEBUG(PSTR("Mark node %d as one that do not require signed messages\n"), msg.sender); - CLEAR_SIGN(msg.sender); - } - - if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) { - // We received an indicator that the sender require us to salt signatures with serial - SIGN_DEBUG(PSTR("Mark node %d as one that require whitelisting\n"), msg.sender); - SET_WHITELIST(msg.sender); - } else { - // We received an indicator that the sender does not require us to sign all messages we send to it - SIGN_DEBUG(PSTR("Mark node %d as one that do not require whitelisting\n"), msg.sender); - CLEAR_WHITELIST(msg.sender); - } - - // Save updated tables - hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, - sizeof(_doSign)); - hwWriteConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS, - sizeof(_doWhitelist)); - - // Inform sender about our preference if we are a gateway, but only require signing if the sender - // required signing - // We do not want a gateway to require signing from all nodes in a network just because it wants one node - // to sign it's messages -#if defined(MY_GATEWAY_FEATURE) - prepareSigningPresentation(msg, sender); -#if defined(MY_SIGNING_REQUEST_SIGNATURES) - if (DO_SIGN(sender)) { - msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES; - } -#endif -#if defined(MY_SIGNING_NODE_WHITELISTING) - if (DO_WHITELIST(sender)) { - msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING; - } -#endif - if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) { - SIGN_DEBUG(PSTR("Informing node %d that we require signatures\n"), sender); - } else { - SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), sender); - } - if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) { - SIGN_DEBUG(PSTR("Informing node %d that we require whitelisting\n"), sender); - } else { - SIGN_DEBUG(PSTR("Informing node %d that we do not require whitelisting\n"), sender); - } - if (!_sendRoute(msg)) { - SIGN_DEBUG(PSTR("Failed to transmit signing presentation!")); - } -#endif // MY_GATEWAY_FEATURE - return true; // No need to further process I_SIGNING_PRESENTATION - } else if (msg.type == I_NONCE_RESPONSE) { - // Proceed with signing if nonce has been received - SIGN_DEBUG(PSTR("Nonce received from %d. Proceeding with signing...\n"), sender); - if (sender != _msgSign.destination) { - SIGN_DEBUG(PSTR("Nonce did not come from the destination (%d) of the message to be signed! " - "It came from %d.\n"), _msgSign.destination, sender); - SIGN_DEBUG(PSTR("Silently discarding this nonce\n")); - return true; // No need to further process I_NONCE_RESPONSE - } -#if defined(MY_SIGNING_SOFT) - signerAtsha204SoftPutNonce(msg); -#endif -#if defined(MY_SIGNING_ATSHA204) - signerAtsha204PutNonce(msg); -#endif -#if defined(MY_SIGNING_SOFT) - if (!signerAtsha204SoftSignMsg(_msgSign)) { -#endif -#if defined(MY_SIGNING_ATSHA204) - if (!signerAtsha204SignMsg(_msgSign)) { -#endif - SIGN_DEBUG(PSTR("Failed to sign message!\n")); - } else { - SIGN_DEBUG(PSTR("Message signed\n")); - _signingNonceStatus = SIGN_OK; // _msgSign now contains the signed message pending transmission - } - return true; // No need to further process I_NONCE_RESPONSE - } -#endif // MY_SIGNING_FEATURE +bool signerProcessInternal(MyMessage &msg) +{ + bool ret; + switch (msg.type) { + case I_SIGNING_PRESENTATION: + ret = signerInternalProcessPresentation(msg); + break; + case I_NONCE_REQUEST: + ret = signerInternalProcessNonceRequest(msg); + break; + case I_NONCE_RESPONSE: + ret = signerInternalProcessNonceResponse(msg); + break; + default: + ret = false; // Let the transport process this message further as it is not related to signing + break; } - return false; + return ret; } -bool signerCheckTimer(void) { -#if defined(MY_SIGNING_SOFT) - return signerAtsha204SoftCheckTimer(); -#elif defined(MY_SIGNING_ATSHA204) - return signerAtsha204CheckTimer(); -#else - return true; // Without a configured backend, we always give "positive" results -#endif +bool signerCheckTimer(void) +{ + return signerBackendCheckTimer(); } -bool signerSignMsg(MyMessage &msg) { +bool signerSignMsg(MyMessage &msg) +{ + bool ret; #if defined(MY_SIGNING_FEATURE) // If destination is known to require signed messages and we are the sender, - // sign this message unless it is a handshake message - if (DO_SIGN(msg.destination) && msg.sender == _nc.nodeId) { + // sign this message unless it is identified as an exception + if (DO_SIGN(msg.destination) && msg.sender == getNodeId()) { if (skipSign(msg)) { - return true; + ret = true; } else { // Send nonce-request _signingNonceStatus=SIGN_WAITING_FOR_NONCE; - if (!_sendRoute(build(_msgSign, _nc.nodeId, msg.destination, msg.sensor, - C_INTERNAL, I_NONCE_REQUEST, false).set(""))) { + if (!_sendRoute(build(_msgSign, msg.destination, msg.sensor, C_INTERNAL, + I_NONCE_REQUEST).set(""))) { SIGN_DEBUG(PSTR("Failed to transmit nonce request!\n")); - return false; - } - SIGN_DEBUG(PSTR("Nonce requested from %d. Waiting...\n"), msg.destination); - // We have to wait for the nonce to arrive before we can sign our original message - // Other messages could come in-between. We trust _process() takes care of them - unsigned long enter = hwMillis(); - _msgSign = msg; // Copy the message to sign since message buffer might be touched in _process() - while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS && _signingNonceStatus==SIGN_WAITING_FOR_NONCE) { - _process(); - } - if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) { - SIGN_DEBUG(PSTR("Timeout waiting for nonce!\n")); - return false; - } - if (_signingNonceStatus == SIGN_OK) { - // process() received a nonce and signerProcessInternal successfully signed the message - msg = _msgSign; // Write the signed message back - SIGN_DEBUG(PSTR("Message to send has been signed\n")); + ret = false; } else { - SIGN_DEBUG(PSTR("Message to send could not be signed!\n")); - return false; + SIGN_DEBUG(PSTR("Nonce requested from %d. Waiting...\n"), msg.destination); + // We have to wait for the nonce to arrive before we can sign our original message + // Other messages could come in-between. We trust _process() takes care of them + unsigned long enter = hwMillis(); + _msgSign = msg; // Copy the message to sign since message buffer might be touched in _process() + while (hwMillis() - enter < MY_VERIFICATION_TIMEOUT_MS && + _signingNonceStatus==SIGN_WAITING_FOR_NONCE) { + _process(); + } + if (hwMillis() - enter > MY_VERIFICATION_TIMEOUT_MS) { + SIGN_DEBUG(PSTR("Timeout waiting for nonce!\n")); + ret = false; + } else { + if (_signingNonceStatus == SIGN_OK) { + // process() received a nonce and signerProcessInternal successfully signed the message + msg = _msgSign; // Write the signed message back + SIGN_DEBUG(PSTR("Message to send has been signed\n")); + ret = true; + // After this point, only the 'last' member of the message structure is allowed to be altered if the + // message has been signed, or signature will become invalid and the message rejected by the receiver + } else { + SIGN_DEBUG(PSTR("Message to send could not be signed!\n")); + ret = false; + } + } } - // After this point, only the 'last' member of the message structure is allowed to be altered if the - // message has been signed, or signature will become invalid and the message rejected by the receiver } - } else if (_nc.nodeId == msg.sender) { + } else if (getNodeId() == msg.sender) { mSetSigned(msg, 0); // Message is not supposed to be signed, make sure it is marked unsigned + SIGN_DEBUG(PSTR("Will not sign message for destination %d as it does not require it\n"), + msg.destination); + ret = true; + } else { + SIGN_DEBUG(PSTR("Will not sign message since it was from %d and we are %d\n"), msg.sender, + getNodeId()); + ret = true; } #else (void)msg; + ret = true; #endif // MY_SIGNING_FEATURE - return true; + return ret; } -bool signerVerifyMsg(MyMessage &msg) { +bool signerVerifyMsg(MyMessage &msg) +{ bool verificationResult = true; // Before processing message, reject unsigned messages if signing is required and check signature // (if it is signed and addressed to us) // Note that we do not care at all about any signature found if we do not require signing #if defined(MY_SIGNING_FEATURE) && defined(MY_SIGNING_REQUEST_SIGNATURES) - // If we are a node, or we are a gateway and the sender require signatures + // If we are a node, or we are a gateway and the sender require signatures (or just a strict gw) // and we are the destination... - if ((!MY_IS_GATEWAY || DO_SIGN(msg.sender)) && msg.destination == _nc.nodeId) { +#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL) + if (msg.destination == getNodeId()) { +#else + if ((!MY_IS_GATEWAY || DO_SIGN(msg.sender)) && msg.destination == getNodeId()) { +#endif // Internal messages of certain types are not verified if (skipSign(msg)) { verificationResult = true; - } - else if (!mGetSigned(msg)) { + } else if (!mGetSigned(msg)) { // Got unsigned message that should have been signed SIGN_DEBUG(PSTR("Message is not signed, but it should have been!\n")); verificationResult = false; } else { -#if defined(MY_SIGNING_SOFT) - verificationResult = signerAtsha204SoftVerifyMsg(msg); -#endif -#if defined(MY_SIGNING_ATSHA204) - verificationResult = signerAtsha204VerifyMsg(msg); -#endif - if (!verificationResult) { + if (!signerBackendVerifyMsg(msg)) { SIGN_DEBUG(PSTR("Signature verification failed!\n")); + verificationResult = false; } #if defined(MY_NODE_LOCK_FEATURE) if (verificationResult) { @@ -386,9 +254,10 @@ bool signerVerifyMsg(MyMessage &msg) { nof_failed_verifications = 0; } else { nof_failed_verifications++; - SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"), MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications); + SIGN_DEBUG(PSTR("Failed verification attempts left until lockdown: %d\n"), + MY_NODE_LOCK_COUNTER_MAX-nof_failed_verifications); if (nof_failed_verifications >= MY_NODE_LOCK_COUNTER_MAX) { - nodeLock("TMFV"); //Too many failed verifications + _nodeLock("TMFV"); //Too many failed verifications } } #endif @@ -404,23 +273,27 @@ bool signerVerifyMsg(MyMessage &msg) { static uint8_t sha256_hash[32]; Sha256Class _soft_sha256; -void signerSha256Init(void) { +void signerSha256Init(void) +{ memset(sha256_hash, 0, 32); _soft_sha256.init(); } -void signerSha256Update(const uint8_t* data, size_t sz) { +void signerSha256Update(const uint8_t* data, size_t sz) +{ for (size_t i = 0; i < sz; i++) { _soft_sha256.write(data[i]); } } -uint8_t* signerSha256Final(void) { +uint8_t* signerSha256Final(void) +{ memcpy(sha256_hash, _soft_sha256.result(), 32); return sha256_hash; } -int signerMemcmp(const void* a, const void* b, size_t sz) { +int signerMemcmp(const void* a, const void* b, size_t sz) +{ int retVal; size_t i; int done = 0; @@ -448,3 +321,190 @@ int signerMemcmp(const void* a, const void* b, size_t sz) { } return retVal; } + +#if defined(MY_SIGNING_FEATURE) +// Helper function to centralize signing/verification exceptions +static bool skipSign(MyMessage &msg) +{ + bool ret; + if (mGetAck(msg)) { + SIGN_DEBUG(PSTR("Skipping security for ACK on command %d type %d\n"), mGetCommand(msg), msg.type); + ret = true; + } else if (mGetCommand(msg) == C_INTERNAL && + (msg.type == I_NONCE_REQUEST || msg.type == I_NONCE_RESPONSE || + msg.type == I_SIGNING_PRESENTATION || + msg.type == I_ID_REQUEST || msg.type == I_ID_RESPONSE || + msg.type == I_FIND_PARENT_REQUEST || msg.type == I_FIND_PARENT_RESPONSE || + msg.type == I_HEARTBEAT_REQUEST || msg.type == I_HEARTBEAT_RESPONSE || + msg.type == I_PING || msg.type == I_PONG || + msg.type == I_REGISTRATION_REQUEST )) { + SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type); + ret = true; + } else if (mGetCommand(msg) == C_STREAM && + (msg.type == ST_FIRMWARE_REQUEST || msg.type == ST_FIRMWARE_RESPONSE || + msg.type == ST_SOUND || msg.type == ST_IMAGE)) { + SIGN_DEBUG(PSTR("Skipping security for command %d type %d\n"), mGetCommand(msg), msg.type); + ret = true; + } else { + ret = false; + } + return ret; +} +#endif + +// Helper to prepare a signing presentation message +static void prepareSigningPresentation(MyMessage &msg, uint8_t destination) +{ + // Only supports version 1 for now + (void)build(msg, destination, NODE_SENSOR_ID, C_INTERNAL, I_SIGNING_PRESENTATION).set(""); + mSetLength(msg, 2); + mSetPayloadType(msg, P_CUSTOM); // displayed as hex + msg.data[0] = SIGNING_PRESENTATION_VERSION_1; + msg.data[1] = 0; +} + +// Helper to process presentation mesages +static bool signerInternalProcessPresentation(MyMessage &msg) +{ +#if defined(MY_SIGNING_FEATURE) + if (msg.data[0] != SIGNING_PRESENTATION_VERSION_1) { + SIGN_DEBUG(PSTR("Unsupported signing presentation version (%d)!\n"), msg.data[0]); + return true; // Just drop this presentation message + } + // We only handle version 1 here... + if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) { + // We received an indicator that the sender require us to sign all messages we send to it + SIGN_DEBUG(PSTR("Mark node %d as one that require signed messages\n"), msg.sender); + SET_SIGN(msg.sender); + } else { + // We received an indicator that the sender does not require us to sign all messages we send to it + SIGN_DEBUG(PSTR("Mark node %d as one that do not require signed messages\n"), msg.sender); + CLEAR_SIGN(msg.sender); + } + if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) { + // We received an indicator that the sender require us to salt signatures with serial + SIGN_DEBUG(PSTR("Mark node %d as one that require whitelisting\n"), msg.sender); + SET_WHITELIST(msg.sender); + } else { + // We received an indicator that the sender does not require us to sign all messages we send to it + SIGN_DEBUG(PSTR("Mark node %d as one that do not require whitelisting\n"), msg.sender); + CLEAR_WHITELIST(msg.sender); + } + + // Save updated tables + hwWriteConfigBlock((void*)_doSign, (void*)EEPROM_SIGNING_REQUIREMENT_TABLE_ADDRESS, + sizeof(_doSign)); + hwWriteConfigBlock((void*)_doWhitelist, (void*)EEPROM_WHITELIST_REQUIREMENT_TABLE_ADDRESS, + sizeof(_doWhitelist)); + + // Inform sender about our preference if we are a gateway, but only require signing if the sender + // required signing unless we explicitly configure it to +#if defined(MY_GATEWAY_FEATURE) + prepareSigningPresentation(msg, msg.sender); +#if defined(MY_SIGNING_REQUEST_SIGNATURES) +#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL) + msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES; +#else + if (DO_SIGN(msg.sender)) { + msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_SIGNATURES; + } +#endif +#endif // MY_SIGNING_REQUEST_SIGNATURES +#if defined(MY_SIGNING_NODE_WHITELISTING) +#if defined(MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL) + msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING; +#else + if (DO_WHITELIST(msg.sender)) { + msg.data[1] |= SIGNING_PRESENTATION_REQUIRE_WHITELISTING; + } +#endif +#endif // MY_SIGNING_NODE_WHITELISTING + if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_SIGNATURES) { + SIGN_DEBUG(PSTR("Informing node %d that we require signatures\n"), msg.sender); + } else { + SIGN_DEBUG(PSTR("Informing node %d that we do not require signatures\n"), msg.sender); + } + if (msg.data[1] & SIGNING_PRESENTATION_REQUIRE_WHITELISTING) { + SIGN_DEBUG(PSTR("Informing node %d that we require whitelisting\n"), msg.sender); + } else { + SIGN_DEBUG(PSTR("Informing node %d that we do not require whitelisting\n"), msg.sender); + } + if (!_sendRoute(msg)) { + SIGN_DEBUG(PSTR("Failed to transmit signing presentation!\n")); + } +#endif // MY_GATEWAY_FEATURE +#else // not MY_SIGNING_FEATURE +#if defined(MY_GATEWAY_FEATURE) + // If we act as gateway and do not have the signing feature and receive a signing request we still + // need to do make sure the requester does not believe the gateway still require signatures + prepareSigningPresentation(msg, msg.sender); + SIGN_DEBUG( + PSTR("Informing node %d that we do not require signatures because we do not support it\n"), + msg.sender); + if (!_sendRoute(msg)) { + SIGN_DEBUG(PSTR("Failed to transmit signing presentation!\n")); + } +#else // not MY_GATEWAY_FEATURE + // If we act as a node and do not have the signing feature then we just silently drop any signing + // presentation messages received + (void)msg; + SIGN_DEBUG(PSTR("Received signing presentation, but signing is not supported (message ignored)\n")); +#endif // not MY_GATEWAY_FEATURE +#endif // not MY_SIGNING_FEATURE + return true; // No need to further process I_SIGNING_PRESENTATION +} + +// Helper to process nonce request mesages +static bool signerInternalProcessNonceRequest(MyMessage &msg) +{ +#if defined(MY_SIGNING_FEATURE) +#if defined(MY_NODE_LOCK_FEATURE) + nof_nonce_requests++; + SIGN_DEBUG(PSTR("Nonce requests left until lockdown: %d\n"), + MY_NODE_LOCK_COUNTER_MAX-nof_nonce_requests); + if (nof_nonce_requests >= MY_NODE_LOCK_COUNTER_MAX) { + _nodeLock("TMNR"); //Too many nonces requested + } +#endif // MY_NODE_LOCK_FEATURE + if (signerBackendGetNonce(msg)) { + if (!_sendRoute(build(msg, msg.sender, NODE_SENSOR_ID, C_INTERNAL, I_NONCE_RESPONSE))) { + SIGN_DEBUG(PSTR("Failed to transmit nonce!\n")); + } else { + SIGN_DEBUG(PSTR("Transmitted nonce\n")); + } + } else { + SIGN_DEBUG(PSTR("Failed to generate nonce!\n")); + } +#else // not MY_SIGNING_FEATURE + (void)msg; + SIGN_DEBUG(PSTR("Received nonce request, but signing is not supported (message ignored)\n")); +#endif // MY_SIGNING_FEATURE + return true; // No need to further process I_NONCE_REQUEST +} + +// Helper to process nonce response mesages +static bool signerInternalProcessNonceResponse(MyMessage &msg) +{ +#if defined(MY_SIGNING_FEATURE) + // Proceed with signing if nonce has been received + SIGN_DEBUG(PSTR("Nonce received from %d.\n"), msg.sender); + if (msg.sender != _msgSign.destination) { + SIGN_DEBUG(PSTR("Nonce did not come from the destination (%d) of the message to be signed! " + "It came from %d.\n"), _msgSign.destination, msg.sender); + SIGN_DEBUG(PSTR("Silently discarding this nonce\n")); + } else { + SIGN_DEBUG(PSTR("Proceeding with signing...\n")); + signerBackendPutNonce(msg); + if (!signerBackendSignMsg(_msgSign)) { + SIGN_DEBUG(PSTR("Failed to sign message!\n")); + } else { + SIGN_DEBUG(PSTR("Message signed\n")); + _signingNonceStatus = SIGN_OK; // _msgSign now contains the signed message pending transmission + } + } +#else + (void)msg; + SIGN_DEBUG(PSTR("Received nonce response, but signing is not supported (message ignored)\n")); +#endif + return true; // No need to further process I_NONCE_RESPONSE +} diff --git a/core/MySigning.h b/core/MySigning.h index 135335322..386d60386 100644 --- a/core/MySigning.h +++ b/core/MySigning.h @@ -99,7 +99,7 @@ * scramble into “garbage” when transmitted over the air and then reassembled by a receiving node before being fed in “the clear” up the stack * at the receiving end. * - * There are methods and possibilities to provide encryption also in software, but if this is done, it is my recommendation that this is done + * There are methods and possibilities to provide encryption also in software, but if this is done, it is my recommendation that this is done * after integrity- and authentication information has been provided to the message (if this is desired). Integrity and authentication is of * course not mandatory and some might be happy with only having encryption to cover their need for security. I, however, have only focused on * integrity and authenticity while at the same time keeping the current message routing mechanisms intact and therefore leave @@ -156,7 +156,8 @@ * This has to be set by at least one of the node in a "pair" or nobody will actually start calculating a signature for a message. * Just set the flag @ref MY_SIGNING_REQUEST_SIGNATURES and the node will inform the gateway that it expects the gateway to sign all * messages sent to the node. If this is set in a gateway, it will @b NOT force all nodes to sign messages to it. It will only require - * signatures from nodes that in turn require signatures.
+ * signatures from nodes that in turn require signatures. If it is desired that the gateway should require signatures from all nodes, + * @ref MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL can be set in the gateway sketch.
* If you want to have two nodes communicate securely directly with each other, the nodes that require signatures must send a presentation * message to all nodes it expect signed messages from (only the gateway is informed automatically). See @ref signerPresentation().
* A node can have three "states" with respect to signing: @@ -267,14 +268,15 @@ * * If a node does require signing, any unsigned message sent to the node will be rejected.
* This also applies to the gateway. However, the difference is that the gateway will only require signed messages from nodes it knows in turn - * require signed messages.
+ * require signed messages (unless @ref MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL is set).
* A node can also inform a different node that it expects to receive signed messages from it. This is done by transmitting an internal message * of type @ref I_SIGNING_PRESENTATION and provide flags as payload that inform the receiver of the signing preferences of the sender.
* All nodes and gateways in a network maintain a table where the signing preferences of all nodes are stored. This is also stored in EEPROM so * if the gateway reboots, the nodes does not have to retransmit a signing presentation to the gateway for the gateway to realize that the node * expect signed messages.
* Also, the nodes that do not require signed messages will also inform gateway of this, so if you reprogram a node to stop require signing, - * the gateway will adhere to this as soon as the new node has presented itself to the gateway. + * the gateway will adhere to this as soon as the new node has presented itself to the gateway. Note however, that if the gateway sets + * @ref MY_SIGNING_GW_REQUEST_SIGNATURES_FROM_ALL a node that does not support signing will be unable to send any data to the gateway. * * The following sequence diagram illustrate how messages are passed in a MySensors network with respect to signing: * @image html MySigning/signingsequence.png @@ -311,7 +313,7 @@ * The whitelist is stored on the node that require signatures. When a received message is verified, the serial of the sender is looked up in a * list stored on the receiving node, and the corresponding serial stored in the list for that sender is then included in the signature verification * process. The list is stored as the value of the flag that enables whitelisting, @ref MY_SIGNING_NODE_WHITELISTING.
- * + * * Whitelisting is achieved by 'salting' the signature with some node-unique information known to the receiver. In the case of ATSHA204A this is the * unique serial number programmed into the circuit. This unique number is never transmitted over the air in clear text, so Eve will not be able to * figure out a "trusted" serial by snooping on the traffic.
@@ -429,7 +431,7 @@ * ... * @endcode * - * The gateway needs to configured with a whitelist (and it have to have an entry for all nodes that send and/or require signed messages):
+ * The gateway needs to be configured with a whitelist (and it has to have an entry for all nodes that send and/or require signed messages):
* @code{.cpp} * #define MY_SIGNING_SOFT * #define MY_SIGNING_SOFT_RANDOMSEED_PIN 7 @@ -488,14 +490,6 @@ typedef struct { /** @brief Helper macro to determine the number of elements in a array */ #define NUM_OF(x) (sizeof(x)/sizeof(x[0])) -/** @brief Helper macro to determine if node require serial salted signatures */ -#define DO_WHITELIST(node) (~_doWhitelist[node>>3]&(1<>3]&=~(1<>3]|=(1<> 4); printBuffer[(i * 2) + 1] = i2h(buf[i]); } @@ -76,8 +80,7 @@ static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, printBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1-strlen_P((const char*)str)] = '\0'; #endif MY_SERIALDEVICE.print(str); - if (sz > 0) - { + if (sz > 0) { MY_SERIALDEVICE.print(printBuffer); } MY_SERIALDEVICE.println(""); @@ -86,32 +89,36 @@ static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, #define DEBUG_SIGNING_PRINTBUF(str, buf, sz) #endif -void signerAtsha204Init(void) { +void signerAtsha204Init(void) +{ atsha204_init(MY_SIGNING_ATSHA204_PIN); } -bool signerAtsha204CheckTimer(void) { +bool signerAtsha204CheckTimer(void) +{ if (_signing_verification_ongoing) { - if (hwMillis() < _signing_timestamp || hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) { + if (hwMillis() < _signing_timestamp || + hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) { DEBUG_SIGNING_PRINTBUF(F("Verification timeout"), NULL, 0); // Purge nonce memset(_signing_signing_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH); memset(_signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH); _signing_verification_ongoing = false; - return false; + return false; } } return true; } -bool signerAtsha204GetNonce(MyMessage &msg) { +bool signerAtsha204GetNonce(MyMessage &msg) +{ DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204"), NULL, 0); // Generate random number for use as nonce // We used a basic whitening technique that XORs each byte in a 32byte random value with current hwMillis() counter // This 32-byte random value is then hashed (SHA256) to produce the resulting nonce (void)atsha204_wakeup(_signing_temp_message); if (atsha204_execute(SHA204_RANDOM, RANDOM_SEED_UPDATE, 0, 0, NULL, - RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) != SHA204_SUCCESS) { + RANDOM_COUNT, _signing_tx_buffer, RANDOM_RSP_SIZE, _signing_rx_buffer) != SHA204_SUCCESS) { DEBUG_SIGNING_PRINTBUF(F("Failed to generate nonce"), NULL, 0); return false; } @@ -132,11 +139,14 @@ bool signerAtsha204GetNonce(MyMessage &msg) { // Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover) // Note that if message is "too" quick, and arrives before turnover, it will be rejected // but this is consider such a rare case that it is accepted and rejects are 'safe' - if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) _signing_timestamp = 0; + if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) { + _signing_timestamp = 0; + } return true; } -void signerAtsha204PutNonce(MyMessage &msg) { +void signerAtsha204PutNonce(MyMessage &msg) +{ DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204"), NULL, 0); memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD); @@ -144,11 +154,12 @@ void signerAtsha204PutNonce(MyMessage &msg) { memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD); } -bool signerAtsha204SignMsg(MyMessage &msg) { +bool signerAtsha204SignMsg(MyMessage &msg) +{ // If we cannot fit any signature in the message, refuse to sign it if (mGetLength(msg) > MAX_PAYLOAD-2) { DEBUG_SIGNING_PRINTBUF(F("Message too large"), NULL, 0); - return false; + return false; } // Calculate signature of message @@ -157,10 +168,12 @@ bool signerAtsha204SignMsg(MyMessage &msg) { if (DO_WHITELIST(msg.destination)) { // Salt the signature with the senders nodeId and the unique serial of the ATSHA device - memcpy(_signing_signing_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); // We can reuse the nonce buffer now since it is no longer needed + memcpy(_signing_signing_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], + 32); // We can reuse the nonce buffer now since it is no longer needed _signing_signing_nonce[32] = msg.sender; atsha204_getSerialNumber(&_signing_signing_nonce[33]); - (void)signerSha256(_signing_signing_nonce, 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place + (void)signerSha256(_signing_signing_nonce, + 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place DEBUG_SIGNING_PRINTBUF(F("Signature salted with serial"), NULL, 0); } @@ -171,30 +184,34 @@ bool signerAtsha204SignMsg(MyMessage &msg) { _signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER; // Transfer as much signature data as the remaining space in the message permits - memcpy(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg)); - DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg)); + memcpy(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], + MAX_PAYLOAD-mGetLength(msg)); + DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], + MAX_PAYLOAD-mGetLength(msg)); return true; } -bool signerAtsha204VerifyMsg(MyMessage &msg) { +bool signerAtsha204VerifyMsg(MyMessage &msg) +{ if (!_signing_verification_ongoing) { DEBUG_SIGNING_PRINTBUF(F("No active verification session"), NULL, 0); - return false; + return false; } else { // Make sure we have not expired if (!signerCheckTimer()) { - return false; + return false; } _signing_verification_ongoing = false; if (msg.data[mGetLength(msg)] != SIGNING_IDENTIFIER) { DEBUG_SIGNING_PRINTBUF(F("Incorrect signing identifier"), NULL, 0); - return false; + return false; } - DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], MAX_PAYLOAD-mGetLength(msg)); + DEBUG_SIGNING_PRINTBUF(F("Signature in message: "), (uint8_t*)&msg.data[mGetLength(msg)], + MAX_PAYLOAD-mGetLength(msg)); signerCalculateSignature(msg, false); // Get signature of message #ifdef MY_SIGNING_NODE_WHITELISTING @@ -203,10 +220,12 @@ bool signerAtsha204VerifyMsg(MyMessage &msg) { for (j=0; j < NUM_OF(_signing_whitelist); j++) { if (_signing_whitelist[j].nodeId == msg.sender) { DEBUG_SIGNING_PRINTBUF(F("Sender found in whitelist"), NULL, 0); - memcpy(_signing_verifying_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); // We can reuse the nonce buffer now since it is no longer needed + memcpy(_signing_verifying_nonce, &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], + 32); // We can reuse the nonce buffer now since it is no longer needed _signing_verifying_nonce[32] = msg.sender; memcpy(&_signing_verifying_nonce[33], _signing_whitelist[j].serial, SHA204_SERIAL_SZ); - (void)signerSha256(_signing_verifying_nonce, 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place + (void)signerSha256(_signing_verifying_nonce, + 32+1+SHA204_SERIAL_SZ); // we can 'void' sha256 because the hash is already put in the correct place break; } } @@ -225,12 +244,14 @@ bool signerAtsha204VerifyMsg(MyMessage &msg) { _signing_rx_buffer[SHA204_BUFFER_POS_DATA] = SIGNING_IDENTIFIER; // Compare the caluclated signature with the provided signature - if (signerMemcmp(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg))) { - DEBUG_SIGNING_PRINTBUF(F("Signature bad: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], MAX_PAYLOAD-mGetLength(msg)); + if (signerMemcmp(&msg.data[mGetLength(msg)], &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], + MAX_PAYLOAD-mGetLength(msg))) { + DEBUG_SIGNING_PRINTBUF(F("Signature bad: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], + MAX_PAYLOAD-mGetLength(msg)); #ifdef MY_SIGNING_NODE_WHITELISTING DEBUG_SIGNING_PRINTBUF(F("Is the sender whitelisted and serial correct?"), NULL, 0); #endif - return false; + return false; } else { DEBUG_SIGNING_PRINTBUF(F("Signature OK"), NULL, 0); return true; @@ -239,42 +260,49 @@ bool signerAtsha204VerifyMsg(MyMessage &msg) { } // Helper to calculate signature of msg (returned in _signing_rx_buffer[SHA204_BUFFER_POS_DATA]) -static void signerCalculateSignature(MyMessage &msg, bool signing) { +static void signerCalculateSignature(MyMessage &msg, bool signing) +{ (void)atsha204_wakeup(_signing_temp_message); memset(_signing_temp_message, 0, 32); - memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + memcpy(_signing_temp_message, (uint8_t*)&msg.data[1-HEADER_SIZE], + MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); // Program the data to sign into the ATSHA204 - DEBUG_SIGNING_PRINTBUF(F("Message to process: "), (uint8_t*)&msg.data[1-HEADER_SIZE], MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); - DEBUG_SIGNING_PRINTBUF(F("Current nonce: "), signing ? _signing_signing_nonce : _signing_verifying_nonce, 32); - (void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32, _signing_temp_message, - WRITE_COUNT_LONG, _signing_tx_buffer, WRITE_RSP_SIZE, _signing_rx_buffer); + DEBUG_SIGNING_PRINTBUF(F("Message to process: "), (uint8_t*)&msg.data[1-HEADER_SIZE], + MAX_MESSAGE_LENGTH-1-(MAX_PAYLOAD-mGetLength(msg))); + DEBUG_SIGNING_PRINTBUF(F("Current nonce: "), + signing ? _signing_signing_nonce : _signing_verifying_nonce, 32); + (void)atsha204_execute(SHA204_WRITE, SHA204_ZONE_DATA | SHA204_ZONE_COUNT_FLAG, 8 << 3, 32, + _signing_temp_message, + WRITE_COUNT_LONG, _signing_tx_buffer, WRITE_RSP_SIZE, _signing_rx_buffer); // Program the nonce to use for the signature (has to be done just before GENDIG due to chip limitations) (void)atsha204_execute(SHA204_NONCE, NONCE_MODE_PASSTHROUGH, 0, NONCE_NUMIN_SIZE_PASSTHROUGH, - signing ? _signing_signing_nonce : _signing_verifying_nonce, - NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT, _signing_rx_buffer); + signing ? _signing_signing_nonce : _signing_verifying_nonce, + NONCE_COUNT_LONG, _signing_tx_buffer, NONCE_RSP_SIZE_SHORT, _signing_rx_buffer); // Purge nonce when used - memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0x00, NONCE_NUMIN_SIZE_PASSTHROUGH); + memset(signing ? _signing_signing_nonce : _signing_verifying_nonce, 0x00, + NONCE_NUMIN_SIZE_PASSTHROUGH); // Generate digest of data and nonce (void)atsha204_execute(SHA204_GENDIG, GENDIG_ZONE_DATA, 8, 0, NULL, - GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE, _signing_rx_buffer); + GENDIG_COUNT_DATA, _signing_tx_buffer, GENDIG_RSP_SIZE, _signing_rx_buffer); // Calculate HMAC of message+nonce digest and secret key (void)atsha204_execute(SHA204_HMAC, HMAC_MODE_SOURCE_FLAG_MATCH, 0, 0, NULL, - HMAC_COUNT, _signing_tx_buffer, HMAC_RSP_SIZE, _signing_rx_buffer); + HMAC_COUNT, _signing_tx_buffer, HMAC_RSP_SIZE, _signing_rx_buffer); DEBUG_SIGNING_PRINTBUF(F("HMAC: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); } // Helper to calculate a generic SHA256 digest of provided buffer (only supports one block) // The pointer to the hash is returned, but the hash is also stored in _signing_rx_buffer[SHA204_BUFFER_POS_DATA]) -static uint8_t* signerSha256(const uint8_t* data, size_t sz) { +static uint8_t* signerSha256(const uint8_t* data, size_t sz) +{ // Initiate SHA256 calculator (void)atsha204_execute(SHA204_SHA, SHA_INIT, 0, 0, NULL, - SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT, _signing_rx_buffer); + SHA_COUNT_SHORT, _signing_tx_buffer, SHA_RSP_SIZE_SHORT, _signing_rx_buffer); // Calculate a hash memset(_signing_temp_message, 0x00, SHA_MSG_SIZE); @@ -284,7 +312,7 @@ static uint8_t* signerSha256(const uint8_t* data, size_t sz) { _signing_temp_message[SHA_MSG_SIZE-2] = (sz >> 5); _signing_temp_message[SHA_MSG_SIZE-1] = (sz << 3); (void)atsha204_execute(SHA204_SHA, SHA_CALC, 0, SHA_MSG_SIZE, _signing_temp_message, - SHA_COUNT_LONG, _signing_tx_buffer, SHA_RSP_SIZE_LONG, _signing_rx_buffer); + SHA_COUNT_LONG, _signing_tx_buffer, SHA_RSP_SIZE_LONG, _signing_rx_buffer); DEBUG_SIGNING_PRINTBUF(F("SHA256: "), &_signing_rx_buffer[SHA204_BUFFER_POS_DATA], 32); return &_signing_rx_buffer[SHA204_BUFFER_POS_DATA]; diff --git a/core/MySigningAtsha204Soft.cpp b/core/MySigningAtsha204Soft.cpp index 400895f2f..62490f0ca 100644 --- a/core/MySigningAtsha204Soft.cpp +++ b/core/MySigningAtsha204Soft.cpp @@ -47,75 +47,89 @@ extern uint8_t _doWhitelist[32]; static uint8_t _signing_node_serial_info[9]; #ifdef MY_SIGNING_NODE_WHITELISTING - const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING; +const whitelist_entry_t _signing_whitelist[] = MY_SIGNING_NODE_WHITELISTING; #endif static void signerCalculateSignature(MyMessage &msg, bool signing); #ifdef MY_DEBUG_VERBOSE_SIGNING static char i2h(uint8_t i) - { +{ uint8_t k = i & 0x0F; - if (k <= 9) + if (k <= 9) { return '0' + k; - else + } else { return 'A' + k - 10; + } } -static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, uint8_t sz) { +#ifdef __linux__ +#define __FlashStringHelper char +#define MY_SERIALDEVICE.print debug +#endif + +static void DEBUG_SIGNING_PRINTBUF(const __FlashStringHelper* str, uint8_t* buf, uint8_t sz) +{ static char printBuffer[300]; -#ifdef MY_GATEWAY_FEATURE + if (NULL == buf) { + return; + } +#if defined(MY_GATEWAY_FEATURE) && !defined(__linux__) // prepend debug message to be handled correctly by controller (C_INTERNAL, I_LOG_MESSAGE) snprintf_P(printBuffer, 299, PSTR("0;255;%d;0;%d;"), C_INTERNAL, I_LOG_MESSAGE); MY_SERIALDEVICE.print(printBuffer); #endif - for (int i=0; i> 4); printBuffer[(i * 2) + 1] = i2h(buf[i]); } printBuffer[sz * 2] = '\0'; -#ifdef MY_GATEWAY_FEATURE +#if defined(MY_GATEWAY_FEATURE) && !defined(__linux__) // Truncate message if this is gateway node printBuffer[MY_GATEWAY_MAX_SEND_LENGTH-1-strlen_P((const char*)str)] = '\0'; #endif MY_SERIALDEVICE.print(str); - if (sz > 0) - { + if (sz > 0) { MY_SERIALDEVICE.print(printBuffer); } +#if !defined(__linux__) MY_SERIALDEVICE.println(""); +#endif } #else #define DEBUG_SIGNING_PRINTBUF(str, buf, sz) #endif -void signerAtsha204SoftInit(void) { +void signerAtsha204SoftInit(void) +{ // initialize pseudo-RNG - randomSeed(analogRead(MY_SIGNING_SOFT_RANDOMSEED_PIN)); + hwRandomNumberInit(); // Set secrets hwReadConfigBlock((void*)_signing_hmac_key, (void*)EEPROM_SIGNING_SOFT_HMAC_KEY_ADDRESS, 32); hwReadConfigBlock((void*)_signing_node_serial_info, (void*)EEPROM_SIGNING_SOFT_SERIAL_ADDRESS, 9); } -bool signerAtsha204SoftCheckTimer(void) { +bool signerAtsha204SoftCheckTimer(void) +{ if (_signing_verification_ongoing) { - if (hwMillis() < _signing_timestamp || hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) { + if (hwMillis() < _signing_timestamp || + hwMillis() > _signing_timestamp + MY_VERIFICATION_TIMEOUT_MS) { DEBUG_SIGNING_PRINTBUF(F("Verification timeout"), NULL, 0); // Purge nonce memset(_signing_signing_nonce, 0xAA, 32); memset(_signing_verifying_nonce, 0xAA, 32); _signing_verification_ongoing = false; - return false; + return false; } } return true; } -bool signerAtsha204SoftGetNonce(MyMessage &msg) { +bool signerAtsha204SoftGetNonce(MyMessage &msg) +{ DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204Soft"), NULL, 0); - // We used a basic whitening technique that XORs a random byte with the current hwMillis() counter and then the byte is + // We used a basic whitening technique that XORs a random byte with the current hwMillis() counter and then the byte is // hashed (SHA256) to produce the resulting nonce _signing_sha256.init(); for (int i = 0; i < 32; i++) { @@ -134,11 +148,14 @@ bool signerAtsha204SoftGetNonce(MyMessage &msg) { // Be a little fancy to handle turnover (prolong the time allowed to timeout after turnover) // Note that if message is "too" quick, and arrives before turnover, it will be rejected // but this is consider such a rare case that it is accepted and rejects are 'safe' - if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) _signing_timestamp = 0; + if (_signing_timestamp + MY_VERIFICATION_TIMEOUT_MS < hwMillis()) { + _signing_timestamp = 0; + } return true; } -void signerAtsha204SoftPutNonce(MyMessage &msg) { +void signerAtsha204SoftPutNonce(MyMessage &msg) +{ DEBUG_SIGNING_PRINTBUF(F("Signing backend: ATSHA204Soft"), NULL, 0); memcpy(_signing_signing_nonce, (uint8_t*)msg.getCustom(), MAX_PAYLOAD); @@ -146,11 +163,12 @@ void signerAtsha204SoftPutNonce(MyMessage &msg) { memset(&_signing_signing_nonce[MAX_PAYLOAD], 0xAA, sizeof(_signing_signing_nonce)-MAX_PAYLOAD); } -bool signerAtsha204SoftSignMsg(MyMessage &msg) { +bool signerAtsha204SoftSignMsg(MyMessage &msg) +{ // If we cannot fit any signature in the message, refuse to sign it if (mGetLength(msg) > MAX_PAYLOAD-2) { DEBUG_SIGNING_PRINTBUF(F("Message too large"), NULL, 0); - return false; + return false; } // Calculate signature of message @@ -160,9 +178,13 @@ bool signerAtsha204SoftSignMsg(MyMessage &msg) { if (DO_WHITELIST(msg.destination)) { // Salt the signature with the senders nodeId and the (hopefully) unique serial The Creator has provided _signing_sha256.init(); - for (int i=0; i<32; i++) _signing_sha256.write(_signing_hmac[i]); + for (int i=0; i<32; i++) { + _signing_sha256.write(_signing_hmac[i]); + } _signing_sha256.write(msg.sender); - for (int i=0; i - * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2013-2016 Sensnology AB * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -17,452 +17,648 @@ * version 2 as published by the Free Software Foundation. */ - #include "MyTransport.h" -static uint32_t _transport_lastUplinkCheck; //!< last uplink check, to prevent GW flooding +// SM: transitions and update states +static transportState_t stInit = { stInitTransition, stInitUpdate }; +static transportState_t stParent = { stParentTransition, stParentUpdate }; +static transportState_t stID = { stIDTransition, stIDUpdate }; +static transportState_t stUplink = { stUplinkTransition, stUplinkUpdate }; +static transportState_t stReady = { stReadyTransition, stReadyUpdate }; +static transportState_t stFailure = { stFailureTransition, stFailureUpdate }; + +// transport SM variables +static transportSM_t _transportSM; + +// transport configuration +static transportConfig_t _transportConfig; + +// callback transportOk +transportCallback_t transportReady_cb = NULL; + +// global variables +extern MyMessage _msg; // incoming message +extern MyMessage _msgTmp; // outgoing message -// repeaters (this includes GW) should perform regular sanity checks for network reliability -#if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE) - uint32_t _transport_lastSanityCheck; //!< last sanity check +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) +static routingTable_t _transportRoutingTable; //!< routing table +static uint32_t _lastRoutingTableSave; //!< last routing table dump #endif -// SM: transitions and update states -static State stInit = { stInitTransition, NULL }; -static State stParent = { stParentTransition, stParentUpdate }; -static State stID = { stIDTransition, stIDUpdate }; -static State stUplink = { stUplinkTransition, NULL }; -static State stOK = { stOKTransition, stOKUpdate }; -static State stFailure = { stFailureTransition, stFailureUpdate }; - -static transportSM _transportSM; - -// stInit: initialize transport HW -void stInitTransition() { - debug(PSTR("TSM:INIT\n")); - // initialize status variables - _transportSM.failedUplinkTransmissions = 0; +// regular sanity check, activated by default on GW and repeater nodes +#if defined(MY_TRANSPORT_SANITY_CHECK) +static uint32_t _lastSanityCheck; //!< last sanity check +#endif + +// regular network discovery, sends I_DISCOVER_REQUESTS to update routing table +// sufficient to have GW triggering requests to also update repeater nodes +#if defined(MY_GATEWAY_FEATURE) +static uint32_t _lastNetworkDiscovery; //! last network discovery +#endif + +// stInit: initialise transport HW +void stInitTransition(void) +{ + TRANSPORT_DEBUG(PSTR("TSM:INIT\n")); + // initialise status variables _transportSM.pingActive = false; _transportSM.transportActive = false; - #if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE) - _transport_lastSanityCheck = hwMillis(); - #endif - _transport_lastUplinkCheck = 0; - // Read node settings (ID, parentId, GW distance) from EEPROM - hwReadConfigBlock((void*)&_nc, (void*)EEPROM_NODE_ID_ADDRESS, sizeof(NodeConfig)); - - // initialize radio + _transportSM.lastUplinkCheck = 0ul; + +#if defined(MY_TRANSPORT_SANITY_CHECK) + _lastSanityCheck = hwMillis(); +#endif +#if defined(MY_GATEWAY_FEATURE) + _lastNetworkDiscovery = 0ul; +#endif +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) + _lastRoutingTableSave = hwMillis(); +#endif + + // Read node settings (ID, parent ID, GW distance) from EEPROM + hwReadConfigBlock((void*)&_transportConfig, (void*)EEPROM_NODE_ID_ADDRESS, + sizeof(transportConfig_t)); +} + +void stInitUpdate(void) +{ + // initialise radio if (!transportInit()) { - debug(PSTR("!TSM:RADIO:FAIL\n")); + TRANSPORT_DEBUG(PSTR("!TSM:INIT:TSP FAIL\n")); setIndication(INDICATION_ERR_INIT_TRANSPORT); transportSwitchSM(stFailure); - } - else { - debug(PSTR("TSM:RADIO:OK\n")); + } else { + TRANSPORT_DEBUG(PSTR("TSM:INIT:TSP OK\n")); _transportSM.transportActive = true; - #if defined(MY_GATEWAY_FEATURE) - // Set configuration for gateway - debug(PSTR("TSM:GW MODE\n")); - _nc.parentNodeId = GATEWAY_ADDRESS; - _nc.distance = 0; - _nc.nodeId = GATEWAY_ADDRESS; - transportSetAddress(GATEWAY_ADDRESS); - transportSwitchSM(stOK); - #else - if (MY_NODE_ID != AUTO) { - // Set static id - _nc.nodeId = MY_NODE_ID; - // Save static id in eeprom - hwWriteConfig(EEPROM_NODE_ID_ADDRESS, MY_NODE_ID); - } - // set ID if static or set in EEPROM - if(_nc.nodeId!=AUTO) transportAssignNodeID(_nc.nodeId); +#if defined(MY_GATEWAY_FEATURE) + // Set configuration for gateway + TRANSPORT_DEBUG(PSTR("TSM:INIT:GW MODE\n")); + _transportConfig.parentNodeId = GATEWAY_ADDRESS; + _transportConfig.distanceGW = 0u; + _transportConfig.nodeId = GATEWAY_ADDRESS; + transportSetAddress(GATEWAY_ADDRESS); + // GW mode: skip FPAR,ID,UPL states + transportSwitchSM(stReady); +#else + if (MY_NODE_ID != AUTO) { + TRANSPORT_DEBUG(PSTR("TSM:INIT:STATID=%d\n"),(uint8_t)MY_NODE_ID); + // Set static ID + _transportConfig.nodeId = (uint8_t)MY_NODE_ID; + // Save static ID to eeprom (for bootloader) + hwWriteConfig(EEPROM_NODE_ID_ADDRESS, (uint8_t)MY_NODE_ID); + } + // assign ID if set + if (_transportConfig.nodeId == AUTO || transportAssignNodeID(_transportConfig.nodeId)) { + // if node ID valid (>0 and <255), proceed to next state transportSwitchSM(stParent); - #endif + } else { + // ID invalid (0 or 255) + transportSwitchSM(stFailure); + } +#endif } } // stParent: find parent -void stParentTransition() { - debug(PSTR("TSM:FPAR\n")); // find parent +void stParentTransition(void) +{ + TRANSPORT_DEBUG(PSTR("TSM:FPAR\n")); // find parent + setIndication(INDICATION_FIND_PARENT); + _transportSM.uplinkOk = false; _transportSM.preferredParentFound = false; +#if defined(MY_PARENT_NODE_IS_STATIC) + TRANSPORT_DEBUG(PSTR("TSM:FPAR:STATP=%d\n"), (uint8_t)MY_PARENT_NODE_ID); // static parent + _transportSM.findingParentNode = false; + _transportConfig.distanceGW = 1u; // assumption, CHKUPL:GWDC will update this variable + _transportConfig.parentNodeId = (uint8_t)MY_PARENT_NODE_ID; + // save parent ID to eeprom (for bootloader) + hwWriteConfig(EEPROM_PARENT_NODE_ID_ADDRESS, (uint8_t)MY_PARENT_NODE_ID); +#else _transportSM.findingParentNode = true; - _transportSM.failedUplinkTransmissions = 0; - _transportSM.uplinkOk = false; - // Set distance to max and invalidate parent node id - _nc.distance = DISTANCE_INVALID; - _nc.parentNodeId = AUTO; + _transportConfig.distanceGW = DISTANCE_INVALID; // Set distance to max and invalidate parent node ID + _transportConfig.parentNodeId = AUTO; // Broadcast find parent request - setIndication(INDICATION_FIND_PARENT); - transportRouteMessage(build(_msgTmp, _nc.nodeId, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT, false).set("")); + (void)transportRouteMessage(build(_msgTmp, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, + I_FIND_PARENT_REQUEST).set("")); +#endif } // stParentUpdate -void stParentUpdate() { - if (transportTimeInState() > STATE_TIMEOUT || _transportSM.preferredParentFound) { +void stParentUpdate(void) +{ +#if defined(MY_PARENT_NODE_IS_STATIC) + // skipping find parent + setIndication(INDICATION_GOT_PARENT); + transportSwitchSM(stID); +#else + if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS || _transportSM.preferredParentFound) { // timeout or preferred parent found - if (_nc.parentNodeId != AUTO) { - debug(PSTR("TSM:FPAR:OK\n")); // find parent ok + if (_transportConfig.parentNodeId != AUTO) { + // parent assigned + TRANSPORT_DEBUG(PSTR("TSM:FPAR:OK\n")); // find parent ok _transportSM.findingParentNode = false; setIndication(INDICATION_GOT_PARENT); + // go to next state transportSwitchSM(stID); - } - else if (transportTimeInState() > STATE_TIMEOUT) { - if (_transportSM.retries < STATE_RETRIES) { - // reenter if timeout and retries left + } else { + // timeout w/o reply or valid parent + if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) { + // retries left + TRANSPORT_DEBUG(PSTR("!TSM:FPAR:NO REPLY\n")); // find parent, no reply + // reenter state transportSwitchSM(stParent); - } - else { - debug(PSTR("!TSM:FPAR:FAIL\n")); // find parent fail + } else { + // no retries left, finding parent failed + TRANSPORT_DEBUG(PSTR("!TSM:FPAR:FAIL\n")); setIndication(INDICATION_ERR_FIND_PARENT); transportSwitchSM(stFailure); } } } +#endif } // stID: verify and request ID if necessary -void stIDTransition() { - debug(PSTR("TSM:ID\n")); - if (_nc.nodeId == AUTO) { +void stIDTransition(void) +{ + TRANSPORT_DEBUG(PSTR("TSM:ID\n")); // verify/request node ID + if (_transportConfig.nodeId == AUTO) { // send ID request setIndication(INDICATION_REQ_NODEID); - transportRouteMessage(build(_msgTmp, _nc.nodeId, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, I_ID_REQUEST, false).set("")); + TRANSPORT_DEBUG(PSTR("TSM:ID:REQ\n")); // request node ID + (void)transportRouteMessage(build(_msgTmp, GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, + I_ID_REQUEST).set("")); } } -void stIDUpdate() { - if (_nc.nodeId != AUTO) { - // current node ID is valid, proceed to uplink check - debug(PSTR("TSM:CHKID:OK (ID=%d)\n"), _nc.nodeId); +void stIDUpdate(void) +{ + if (_transportConfig.nodeId != AUTO) { + // current node ID is valid + TRANSPORT_DEBUG(PSTR("TSM:ID:OK\n")); setIndication(INDICATION_GOT_NODEID); - // check uplink - transportSwitchSM(stUplink); - } - else if (transportTimeInState() > STATE_TIMEOUT) { - if (_transportSM.retries < STATE_RETRIES) { - // re-enter if retries left + // proceed to next state + transportSwitchSM(stUplink); + } else if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS) { + // timeout + if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) { + // retries left: reenter state transportSwitchSM(stID); - } - else { - // fail - debug(PSTR("!TSM:CHKID:FAIL (ID=%d)\n"), _nc.nodeId); + } else { + // no retries left + TRANSPORT_DEBUG(PSTR("!TSM:ID:FAIL\n")); setIndication(INDICATION_ERR_GET_NODEID); transportSwitchSM(stFailure); } } } -void stUplinkTransition() { - debug(PSTR("TSM:UPL\n")); - if(transportCheckUplink(true)) { - debug(PSTR("TSM:UPL:OK\n")); - transportSwitchSM(stOK); - } - else { - debug(PSTR("!TSM:UPL:FAIL\n")); - transportSwitchSM(stParent); +void stUplinkTransition(void) +{ +#if !defined(MY_TRANSPORT_UPLINK_CHECK_DISABLED) + TRANSPORT_DEBUG(PSTR("TSM:UPL\n")); + setIndication(INDICATION_CHECK_UPLINK); + _transportSM.pingResponse = INVALID_HOPS; + _transportSM.pingActive = true; + (void)transportRouteMessage(build(_msgTmp,GATEWAY_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, + I_PING).set((uint8_t)0x01)); +#endif +} + +void stUplinkUpdate(void) +{ +#if !defined(MY_TRANSPORT_UPLINK_CHECK_DISABLED) + if (_transportSM.pingResponse != INVALID_HOPS) { + _transportSM.lastUplinkCheck = hwMillis(); + // uplink ok, i.e. GW replied + TRANSPORT_DEBUG(PSTR("TSM:UPL:OK\n")); // uplink ok + if (_transportSM.pingResponse != _transportConfig.distanceGW) { + TRANSPORT_DEBUG(PSTR("TSM:UPL:DGWC,O=%d,N=%d\n"), _transportConfig.distanceGW, + _transportSM.pingResponse); // distance to GW changed + _transportConfig.distanceGW = _transportSM.pingResponse; + } + transportSwitchSM(stReady); // proceed to next state + } else if (transportTimeInState() > MY_TRANSPORT_STATE_TIMEOUT_MS) { + // timeout + if (_transportSM.stateRetries < MY_TRANSPORT_STATE_RETRIES) { + // retries left: reenter state + transportSwitchSM(stUplink); + } else { + // no retries left + TRANSPORT_DEBUG(PSTR("!TSM:UPL:FAIL\n")); // uplink check failed + _transportSM.pingActive = false; + setIndication(INDICATION_ERR_CHECK_UPLINK); + transportSwitchSM(stParent); // go back to stParent + } } +#else + TRANSPORT_DEBUG(PSTR("TSM:UPL:DISABLED\n")); // uplink check disabled + transportSwitchSM(stReady); +#endif } -void stOKTransition() { - debug(PSTR("TSM:READY\n")); // transport is ready +void stReadyTransition(void) +{ + // transport is ready and fully operational + TRANSPORT_DEBUG(PSTR("TSM:READY:ID=%d,PAR=%d,DIS=%d\n"), _transportConfig.nodeId, + _transportConfig.parentNodeId, _transportConfig.distanceGW); _transportSM.uplinkOk = true; + _transportSM.failureCounter = 0u; // reset failure counter + _transportSM.failedUplinkTransmissions = 0u; // reset failed uplink TX counter + // callback + if (transportReady_cb) { + transportReady_cb(); + } } -// stOK update: monitors uplink failures -void stOKUpdate() { - #if !defined(MY_GATEWAY_FEATURE) - if (_transportSM.failedUplinkTransmissions > TRANSMISSION_FAILURES) { - // too many uplink transmissions failed, find new parent - #if !defined(MY_PARENT_NODE_IS_STATIC) - debug(PSTR("!TSM:UPL FAIL, SNP\n")); - transportSwitchSM(stParent); - #else - debug(PSTR("!TSM:UPL FAIL, STATP\n")); - _transportSM.failedUplinkTransmissions = 0; - #endif - } - #endif +// stReadyUpdate: monitors link +void stReadyUpdate(void) +{ +#if defined(MY_GATEWAY_FEATURE) + if (hwMillis() - _lastNetworkDiscovery > MY_TRANSPORT_DISCOVERY_INTERVAL_MS) { + _lastNetworkDiscovery = hwMillis(); + TRANSPORT_DEBUG(PSTR("TSM:READY:NWD REQ\n")); // send transport network discovery + (void)transportRouteMessage(build(_msgTmp, BROADCAST_ADDRESS, NODE_SENSOR_ID, C_INTERNAL, + I_DISCOVER_REQUEST).set("")); + } +#else + if (_transportSM.failedUplinkTransmissions > MY_TRANSPORT_MAX_TX_FAILURES) { + // too many uplink transmissions failed, find new parent (if non-static) +#if !defined(MY_PARENT_NODE_IS_STATIC) + TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,SNP\n")); // uplink failed, search new parent + transportSwitchSM(stParent); +#else + TRANSPORT_DEBUG(PSTR("!TSM:READY:UPL FAIL,STATP\n")); // uplink failed, static parent + // reset counter + _transportSM.failedUplinkTransmissions = 0u; +#endif + } +#endif + +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) + if (hwMillis() - _lastRoutingTableSave > MY_ROUTING_TABLE_SAVE_INTERVAL_MS) { + _lastRoutingTableSave = hwMillis(); + transportSaveRoutingTable(); + } +#endif } // stFailure: entered upon HW init failure or max retries exceeded -void stFailureTransition() { - debug(PSTR("!TSM:FAILURE\n")); - _transportSM.uplinkOk = false; - _transportSM.transportActive = false; +void stFailureTransition(void) +{ + if (_transportSM.failureCounter < MY_TRANSPORT_MAX_TSM_FAILURES) { + _transportSM.failureCounter++; // increment consecutive TSM failure counter + } + TRANSPORT_DEBUG(PSTR("TSM:FAIL:CNT=%d\n"),_transportSM.failureCounter); + _transportSM.uplinkOk = false; // uplink nok + _transportSM.transportActive = false; // transport inactive setIndication(INDICATION_ERR_INIT_TRANSPORT); - // power down transport, no need until re-init - debug(PSTR("TSM:PDT\n")); +#if defined(MY_SENSOR_NETWORK) + TRANSPORT_DEBUG(PSTR("TSM:FAIL:PDT\n")); // power down transport, no need until re-init transportPowerDown(); +#endif } -void stFailureUpdate() { - if (transportTimeInState()> TIMEOUT_FAILURE_STATE) { +void stFailureUpdate(void) +{ + if (transportTimeInState() > ( isTransportExtendedFailure()? MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE: + MY_TRANSPORT_TIMEOUT_FAILURE_STATE) ) { + TRANSPORT_DEBUG(PSTR("TSM:FAIL:RE-INIT\n")); // attempt to re-initialise transport transportSwitchSM(stInit); } } -void transportSwitchSM(State& newState) { +void transportSwitchSM(transportState_t& newState) +{ if (_transportSM.currentState != &newState) { - // state change, reset retry counter - _transportSM.retries = 0; - // change state - _transportSM.currentState = &newState; + _transportSM.stateRetries = 0u; // state change, reset retry counter + _transportSM.currentState = &newState; // change state + } else { + _transportSM.stateRetries++; // increment retries } - else { - _transportSM.retries++; // increment retries + if (_transportSM.currentState->Transition) { + _transportSM.currentState->Transition(); // State transition } - // Transition event - if (_transportSM.currentState->Transition) _transportSM.currentState->Transition(); - // save time - _transportSM.stateEnter = hwMillis(); + _transportSM.stateEnter = hwMillis(); // save time } -uint32_t transportTimeInState() { +uint32_t transportTimeInState(void) +{ return hwMillis() - _transportSM.stateEnter; } -void transportUpdateSM(){ - if (_transportSM.currentState->Update) _transportSM.currentState->Update(); +void transportUpdateSM(void) +{ + if (_transportSM.currentState->Update) { + _transportSM.currentState->Update(); + } +} + +bool isTransportReady(void) +{ + return _transportSM.uplinkOk; } -bool isTransportOK() { - return (_transportSM.uplinkOk); +bool isTransportExtendedFailure(void) +{ + return _transportSM.failureCounter == MY_TRANSPORT_MAX_TSM_FAILURES; } +bool isTransportSearchingParent(void) +{ + return _transportSM.findingParentNode; +} -void transportInitialize() { +bool isMessageReceived(void) +{ + return _transportSM.msgReceived; +} + +void resetMessageReceived(void) +{ + _transportSM.msgReceived = false; +} + + +void transportInitialise(void) +{ + _transportSM.failureCounter = 0u; // reset failure counter + transportLoadRoutingTable(); // load routing table to RAM (if feature enabled) // intial state - _transportSM.currentState = &stFailure; + _transportSM.currentState = NULL; transportSwitchSM(stInit); } +bool transportWaitUntilReady(const uint32_t waitingMS) +{ + // check if transport ready + TRANSPORT_DEBUG(PSTR("TSF:WUR:MS=%lu\n"), waitingMS); // timeout + uint32_t enterMS = hwMillis(); + bool result = false; + while (!result && ( hwMillis() - enterMS < waitingMS || !waitingMS)) { + transportProcess(); + result = isTransportReady(); + doYield(); + } + return result; +} + // update TSM and process incoming messages -void transportProcess() { +void transportProcess(void) +{ // update state machine - transportUpdateSM(); + transportUpdateSM(); // process transport FIFO transportProcessFIFO(); } -bool transportCheckUplink(bool force) { - if (!force && (hwMillis() - _transport_lastUplinkCheck) < CHKUPL_INTERVAL) { - debug(PSTR("TSP:CHKUPL:OK (FLDCTRL)\n")); // flood control +bool transportCheckUplink(const bool force) +{ + if (!force && (hwMillis() - _transportSM.lastUplinkCheck) < MY_TRANSPORT_CHKUPL_INTERVAL_MS) { + TRANSPORT_DEBUG(PSTR("TSF:CKU:OK,FCTRL\n")); // flood control return true; } // ping GW - uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS); + const uint8_t hopsCount = transportPingNode(GATEWAY_ADDRESS); // verify hops if (hopsCount != INVALID_HOPS) { // update - _transport_lastUplinkCheck = hwMillis(); - debug(PSTR("TSP:CHKUPL:OK\n")); - // did distance to GW change upstream, i.e. re-routing of uplink nodes? - if (hopsCount != _nc.distance) { - debug(PSTR("TSP:CHKUPL:DGWC (old=%d,new=%d)"), _nc.distance, hopsCount); // distance to GW changed - _nc.distance = hopsCount; + _transportSM.lastUplinkCheck = hwMillis(); + TRANSPORT_DEBUG(PSTR("TSF:CKU:OK\n")); + // did distance to GW change upstream, eg. re-routing of uplink nodes + if (hopsCount != _transportConfig.distanceGW) { + TRANSPORT_DEBUG(PSTR("TSF:CKU:DGWC,O=%d,N=%d\n"), _transportConfig.distanceGW, + hopsCount); // distance to GW changed + _transportConfig.distanceGW = hopsCount; } return true; - } - else { - debug(PSTR("TSP:CHKUPL:FAIL (hops=%d)\n"), hopsCount); + } else { + TRANSPORT_DEBUG(PSTR("TSF:CKU:FAIL\n")); return false; } } -void transportAssignNodeID(uint8_t newNodeId) { +bool transportAssignNodeID(const uint8_t newNodeId) +{ // verify if ID valid if (newNodeId != GATEWAY_ADDRESS && newNodeId != AUTO) { - _nc.nodeId = newNodeId; + _transportConfig.nodeId = newNodeId; transportSetAddress(newNodeId); // Write ID to EEPROM hwWriteConfig(EEPROM_NODE_ID_ADDRESS, newNodeId); - debug(PSTR("TSP:ASSIGNID:OK (ID=%d)\n"),newNodeId); - } - else { - debug(PSTR("!TSP:ASSIGNID:FAIL (ID=%d)\n"),newNodeId); + TRANSPORT_DEBUG(PSTR("TSF:SID:OK,ID=%d\n"),newNodeId); // Node ID assigned + return true; + } else { + TRANSPORT_DEBUG(PSTR("!TSF:SID:FAIL,ID=%d\n"),newNodeId); // ID is invalid, cannot assign ID setIndication(INDICATION_ERR_NET_FULL); - // Nothing else we can do... - transportSwitchSM(stFailure); + _transportConfig.nodeId = AUTO; + return false; } } -bool transportRouteMessage(MyMessage &message) { - uint8_t destination = message.destination; - uint8_t route; - +bool transportRouteMessage(MyMessage &message) +{ + const uint8_t destination = message.destination; + uint8_t route = _transportConfig.parentNodeId; // by default, all traffic is routed via parent node + if (_transportSM.findingParentNode && destination != BROADCAST_ADDRESS) { - debug(PSTR("!TSP:FPAR:ACTIVE (msg not send)\n")); + TRANSPORT_DEBUG(PSTR("!TSF:RTE:FPAR ACTIVE\n")); // find parent active, message not sent // request to send a non-BC message while finding parent active, abort return false; } if (destination == GATEWAY_ADDRESS) { - route = _nc.parentNodeId; // message to GW always routes via parent - } - else if (destination == BROADCAST_ADDRESS) { + route = _transportConfig.parentNodeId; // message to GW always routes via parent + } else if (destination == BROADCAST_ADDRESS) { route = BROADCAST_ADDRESS; // message to BC does not require routing - } - else { - #if defined(MY_REPEATER_FEATURE) - // destination not GW & not BC, get route - route = hwReadConfig(EEPROM_ROUTES_ADDRESS + destination); - if (route == AUTO) { - // route unknown - if (message.last != _nc.parentNodeId) { - // message not from parent, i.e. child node - route it to parent - debug(PSTR("!TSP:ROUTING:DEST UNKNOWN (dest=%d, STP=%d)\n"), destination, _nc.parentNodeId); - route = _nc.parentNodeId; - } - else { - // route unknown and msg received from parent, send it to destination assuming in rx radius - route = destination; - } + } else { +#if defined(MY_REPEATER_FEATURE) + // destination not GW & not BC, get route + route = transportGetRoute(destination); + if (route == AUTO) { + TRANSPORT_DEBUG(PSTR("!TSF:RTE:%d UNKNOWN\n"), destination); // route unknown +#if !defined(MY_GATEWAY_FEATURE) + if (message.last != _transportConfig.parentNodeId) { + // message not from parent, i.e. child node - route it to parent + route = _transportConfig.parentNodeId; + } else { + // route unknown and msg received from parent, send it to destination assuming in rx radius + route = destination; } - #else - route = _nc.parentNodeId; // not a repeater, all traffic routed via parent - #endif +#else + // if GW, all unknown destinations are directly addressed + route = destination; +#endif + } +#else + route = _transportConfig.parentNodeId; // not a repeater, all traffic routed via parent +#endif } // send message - bool ok = transportSendWrite(route, message); - #if !defined(MY_GATEWAY_FEATURE) - // update counter - if (route == _nc.parentNodeId) { - if (!ok) { - setIndication(INDICATION_ERR_TX); - _transportSM.failedUplinkTransmissions++; - } - else _transportSM.failedUplinkTransmissions = 0; + const bool result = transportSendWrite(route, message); +#if !defined(MY_GATEWAY_FEATURE) + // update counter + if (route == _transportConfig.parentNodeId) { + if (!result) { + setIndication(INDICATION_ERR_TX); + _transportSM.failedUplinkTransmissions++; + } else { + _transportSM.failedUplinkTransmissions = 0u; } - #else - if(!ok) setIndication(INDICATION_ERR_TX); - - #endif + } +#else + if(!result) { + setIndication(INDICATION_ERR_TX); + } +#endif - return ok; + return result; } -bool transportSendRoute(MyMessage &message) { - if (isTransportOK()) { - return transportRouteMessage(message); - } - else { +bool transportSendRoute(MyMessage &message) +{ + bool result = false; + if (isTransportReady()) { + result = transportRouteMessage(message); + } else { // TNR: transport not ready - debug(PSTR("!TSP:SEND:TNR\n")); - return false; + TRANSPORT_DEBUG(PSTR("!TSF:SND:TNR\n")); } + return result; } // only be used inside transport -bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype){ - uint32_t enter = hwMillis(); +bool transportWait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgType) +{ + const uint32_t enterMS = hwMillis(); // invalidate msg type - _msg.type = !msgtype; + _msg.type = !msgType; bool expectedResponse = false; - while ((hwMillis() - enter < ms) && !expectedResponse) { + while ((hwMillis() - enterMS < waitingMS) && !expectedResponse) { // process incoming messages transportProcessFIFO(); - #if defined(ARDUINO_ARCH_ESP8266) - yield(); - #endif - expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgtype); + doYield(); + expectedResponse = (mGetCommand(_msg) == cmd && _msg.type == msgType); } return expectedResponse; } -uint8_t transportPingNode(uint8_t targetId) { - if(!_transportSM.pingActive){ - if(targetId == _nc.nodeId) { - // ping to ourself, pingActive remains false - return 0; +uint8_t transportPingNode(const uint8_t targetId) +{ + if(!_transportSM.pingActive) { + TRANSPORT_DEBUG(PSTR("TSF:PNG:SEND,TO=%d\n"), targetId); + if(targetId == _transportConfig.nodeId) { + // pinging self + _transportSM.pingResponse = 0u; + } else { + _transportSM.pingActive = true; + _transportSM.pingResponse = INVALID_HOPS; + (void)transportRouteMessage(build(_msgTmp, targetId, NODE_SENSOR_ID, C_INTERNAL, + I_PING).set((uint8_t)0x01)); + // Wait for ping reply or timeout + (void)transportWait(2000, C_INTERNAL, I_PONG); } - _transportSM.pingActive = true; - _transportSM.pingResponse = INVALID_HOPS; - debug(PSTR("TSP:PING:SEND (dest=%d)\n"), targetId); - transportRouteMessage(build(_msgTmp, _nc.nodeId, targetId, NODE_SENSOR_ID, C_INTERNAL, I_PING, false).set((uint8_t)0x01)); - // Wait for ping reply or timeout - transportWait(2000, C_INTERNAL, I_PONG); // make sure missing I_PONG msg does not block pinging function by leaving pignActive=true _transportSM.pingActive = false; return _transportSM.pingResponse; - } - else { + } else { + TRANSPORT_DEBUG(PSTR("!TSF:PNG:ACTIVE\n")); // ping active, cannot start new ping return INVALID_HOPS; } } -void transportClearRoutingTable() { - for (uint8_t i = 0; i != 255; i++) { - hwWriteConfig(EEPROM_ROUTES_ADDRESS + i, BROADCAST_ADDRESS); - } - debug(PSTR("TSP:CRT:OK\n")); // clear routing table -} - -uint32_t transportGetHeartbeat() { +uint32_t transportGetHeartbeat(void) +{ return transportTimeInState(); } -void transportProcessMessage() { - (void)signerCheckTimer(); // Manage signing timeout - - uint8_t payloadLength = transportReceive((uint8_t *)&_msg); - (void)payloadLength; // currently not used, but good to test for CRC-ok but corrupt msgs - +void transportProcessMessage(void) +{ + // Manage signing timeout + (void)signerCheckTimer(); + // receive message setIndication(INDICATION_RX); + uint8_t payloadLength = transportReceive((uint8_t *)&_msg); + // get message length and limit size + + const uint8_t msgLength = min(mGetLength(_msg), (uint8_t)MAX_PAYLOAD); + // calculate expected length + const uint8_t expectedMessageLength = HEADER_SIZE + (mGetSigned(_msg) ? MAX_PAYLOAD : msgLength); +#if defined(MY_RF24_ENABLE_ENCRYPTION) + // payload length = a multiple of blocksize length for decrypted messages, i.e. cannot be used for payload length check + payloadLength = expectedMessageLength; +#endif + const uint8_t command = mGetCommand(_msg); + const uint8_t type = _msg.type; + const uint8_t sender = _msg.sender; + const uint8_t last = _msg.last; + const uint8_t destination = _msg.destination; + + TRANSPORT_DEBUG(PSTR("TSF:MSG:READ,%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), + sender, last, destination, _msg.sensor, command, type, mGetPayloadType(_msg), msgLength, + mGetSigned(_msg), _msg.getString(_convBuf)); + + // Reject payloads with incorrect length + if (payloadLength != expectedMessageLength) { + setIndication(INDICATION_ERR_LENGTH); + TRANSPORT_DEBUG(PSTR("!TSF:MSG:LEN,%d!=%d\n"), payloadLength, + expectedMessageLength); // invalid payload length + return; + } - uint8_t command = mGetCommand(_msg); - uint8_t type = _msg.type; - uint8_t sender = _msg.sender; - uint8_t last = _msg.last; - uint8_t destination = _msg.destination; - - debug(PSTR("TSP:MSG:READ %d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d:%s\n"), - sender, last, destination, _msg.sensor, mGetCommand(_msg), type, mGetPayloadType(_msg), mGetLength(_msg), mGetSigned(_msg), _msg.getString(_convBuf)); - - // verify protocol version - if(mGetVersion(_msg) != PROTOCOL_VERSION) { + // Reject messages with incorrect protocol version + if (mGetVersion(_msg) != PROTOCOL_VERSION) { setIndication(INDICATION_ERR_VERSION); - debug(PSTR("!TSP:MSG:PVER mismatch\n")); // protocol version + TRANSPORT_DEBUG(PSTR("!TSF:MSG:PVER,%d=%d\n"), mGetVersion(_msg), + PROTOCOL_VERSION); // protocol version mismatch return; } - - // Reject massages that do not pass verification + + // Reject messages that do not pass verification if (!signerVerifyMsg(_msg)) { setIndication(INDICATION_ERR_SIGN); - debug(PSTR("!TSP:MSG:SIGN verify fail\n")); - return; + TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN VERIFY FAIL\n")); + return; } - - if (destination == _nc.nodeId) { - // This message is addressed to this node + // update routing table if msg not from parent +#if defined(MY_REPEATER_FEATURE) +#if !defined(MY_GATEWAY_FEATURE) + if (last != _transportConfig.parentNodeId) { +#else + // GW doesn't have parent + { +#endif + // Message is from one of the child nodes and not sent from this node. Add it to routing table. + if (sender != _transportConfig.nodeId) + { + transportSetRoute(sender, last); + } + } +#endif // MY_REPEATER_FEATURE + + // set message received flag + _transportSM.msgReceived = true; + + // Is message addressed to this node? + if (destination == _transportConfig.nodeId) { // prevent buffer overflow by limiting max. possible message length (5 bits=31 bytes max) to MAX_PAYLOAD (25 bytes) - mSetLength(_msg, min(mGetLength(_msg),MAX_PAYLOAD)); + mSetLength(_msg, min(mGetLength(_msg),(uint8_t)MAX_PAYLOAD)); // null terminate data - _msg.data[mGetLength(_msg)] = 0x00; - - // update routing table if msg not from parent - #if defined(MY_REPEATER_FEATURE) - if (last != _nc.parentNodeId) { - // Message is from one of the child nodes. Add it to routing table. - hwWriteConfig(EEPROM_ROUTES_ADDRESS+sender, last); - } - #endif - + _msg.data[msgLength] = 0u; // Check if sender requests an ack back. if (mGetRequestAck(_msg)) { - _msgTmp = _msg; // Copy message - mSetRequestAck(_msgTmp, false); // Reply without ack flag (otherwise we would end up in an eternal loop) + TRANSPORT_DEBUG(PSTR("TSF:MSG:ACK REQ\n")); // ACK requested + _msgTmp = _msg; // Copy message + mSetRequestAck(_msgTmp, + false); // Reply without ack flag (otherwise we would end up in an eternal loop) mSetAck(_msgTmp, true); // set ACK flag - _msgTmp.sender = _nc.nodeId; + _msgTmp.sender = _transportConfig.nodeId; _msgTmp.destination = sender; - // send ACK - debug(PSTR("TSP:MSG:ACK msg\n")); - // use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply - transportSendRoute(_msgTmp); - } + // send ACK, use transportSendRoute since ACK reply is not internal, i.e. if !transportOK do not reply + (void)transportSendRoute(_msgTmp); + } if(!mGetAck(_msg)) { // only process if not ACK if (command == C_INTERNAL) { @@ -470,211 +666,303 @@ void transportProcessMessage() { if (signerProcessInternal(_msg)) { return; // Signer processing indicated no further action needed } - #if !defined(MY_GATEWAY_FEATURE) - if (type == I_ID_RESPONSE) { - #if (MY_NODE_ID == AUTO) - // only active if node ID dynamic - transportAssignNodeID(_msg.getByte()); - #endif +#if !defined(MY_GATEWAY_FEATURE) + if (type == I_ID_RESPONSE) { +#if (MY_NODE_ID == AUTO) + // only active if node ID dynamic + (void)transportAssignNodeID(_msg.getByte()); +#endif return; // no further processing required - } - if (type == I_FIND_PARENT_RESPONSE) { - #if !defined(MY_GATEWAY_FEATURE) - // Reply to a I_FIND_PARENT message. Check if the distance is shorter than we already have. - uint8_t distance = _msg.getByte(); - debug(PSTR("TSP:MSG:FPAR RES (ID=%d, dist=%d)\n"), sender, distance); - if (isValidDistance(distance)) { - // Distance to gateway is one more for us w.r.t. parent - distance++; - // update settings if distance shorter or preferred parent found - if (((isValidDistance(distance) && distance < _nc.distance) || (!_autoFindParent && sender == MY_PARENT_NODE_ID)) && !_transportSM.preferredParentFound) { - // Found a neighbor closer to GW than previously found - if (!_autoFindParent && sender == MY_PARENT_NODE_ID) { - _transportSM.preferredParentFound = true; - debug(PSTR("TSP:MSG:FPAR (PPAR FOUND)\n")); // preferred parent found - } - _nc.distance = distance; - _nc.parentNodeId = sender; - debug(PSTR("TSP:MSG:PAR OK (ID=%d, dist=%d)\n"), _nc.parentNodeId, _nc.distance); + } + if (type == I_FIND_PARENT_RESPONSE) { +#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_PARENT_NODE_IS_STATIC) + if (_transportSM.findingParentNode) { // only process if find parent active + // Reply to a I_FIND_PARENT_REQUEST message. Check if the distance is shorter than we already have. + uint8_t distance = _msg.getByte(); + if (isValidDistance(distance)) { + distance++; // Distance to gateway is one more for us w.r.t. parent + // update settings if distance shorter or preferred parent found + if (((isValidDistance(distance) && distance < _transportConfig.distanceGW) || (!_autoFindParent && + sender == (uint8_t)MY_PARENT_NODE_ID)) && !_transportSM.preferredParentFound) { + // Found a neighbor closer to GW than previously found + if (!_autoFindParent && sender == (uint8_t)MY_PARENT_NODE_ID) { + _transportSM.preferredParentFound = true; + TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR PREF\n")); // find parent, preferred parent found } + _transportConfig.distanceGW = distance; + _transportConfig.parentNodeId = sender; + TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR OK,ID=%d,D=%d\n"), _transportConfig.parentNodeId, + _transportConfig.distanceGW); } - return; - #endif + } + } else { + TRANSPORT_DEBUG(PSTR("!TSF:MSG:FPAR INACTIVE\n")); // find parent response received, but inactive } - #endif + return; +#endif + } +#endif // general if (type == I_PING) { - debug(PSTR("TSP:MSG:PINGED (ID=%d, hops=%d)\n"), sender, _msg.getByte()); - transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_PONG, false).set((uint8_t)0x01)); + TRANSPORT_DEBUG(PSTR("TSF:MSG:PINGED,ID=%d,HP=%d\n"), sender, _msg.getByte()); // node pinged +#if defined(MY_GATEWAY_FEATURE) && (F_CPU>16000000) + // delay for fast GW and slow nodes + delay(5); +#endif + (void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL, + I_PONG).set((uint8_t)1)); return; // no further processing required } if (type == I_PONG) { if (_transportSM.pingActive) { _transportSM.pingActive = false; _transportSM.pingResponse = _msg.getByte(); - debug(PSTR("TSP:MSG:PONG RECV (hops=%d)\n"), _transportSM.pingResponse); + TRANSPORT_DEBUG(PSTR("TSF:MSG:PONG RECV,HP=%d\n"), _transportSM.pingResponse); // pong received + } else { + TRANSPORT_DEBUG(PSTR("!TSF:MSG:PONG RECV,INACTIVE\n")); // pong received, but !pingActive } return; // no further processing required } - if (_processInternalMessages()) { return; // no further processing required } } else if (command == C_STREAM) { - #if defined(MY_OTA_FIRMWARE_FEATURE) - if(firmwareOTAUpdateProcess()){ - return; // OTA FW update processing indicated no further action needed - } - #endif +#if defined(MY_OTA_FIRMWARE_FEATURE) + if(firmwareOTAUpdateProcess()) { + return; // OTA FW update processing indicated no further action needed + } +#endif } + } else { + TRANSPORT_DEBUG( + PSTR("TSF:MSG:ACK\n")); // received message is ACK, no internal processing, handover to msg callback } - #if defined(MY_GATEWAY_FEATURE) - // Hand over message to controller - gatewayTransportSend(_msg); - #else - // Call incoming message callback if available - if (receive) { - receive(_msg); - } - #endif - return; - } - else if (destination == BROADCAST_ADDRESS) { - // broadcast - debug(PSTR("TSP:MSG:BC\n")); +#if defined(MY_GATEWAY_FEATURE) + // Hand over message to controller + (void)gatewayTransportSend(_msg); +#endif + // Call incoming message callback if available + if (receive) { + receive(_msg); + } + } else if (destination == BROADCAST_ADDRESS) { + TRANSPORT_DEBUG(PSTR("TSF:MSG:BC\n")); // broadcast msg if (command == C_INTERNAL) { - if (isTransportOK()) { + if (isTransportReady()) { // only reply if node is fully operational - if (type == I_FIND_PARENT) { - #if defined(MY_REPEATER_FEATURE) - if (sender != _nc.parentNodeId) { // no circular reference - debug(PSTR("TSP:MSG:FPAR REQ (sender=%d)\n"), sender); // FPR: find parent request - // node is in our range, update routing table - important if node has new repeater as parent - hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, sender); - // check if uplink functional - node can only be parent node if link to GW functional - // this also prevents circular references in case GW ooo - if(transportCheckUplink(false)){ - #if defined(MY_REPEATER_FEATURE) - _transport_lastUplinkCheck = hwMillis(); - #endif - debug(PSTR("TSP:MSG:GWL OK\n")); // GW uplink ok - // delay minimizes collisions - delay(hwMillis() & 0x3ff); - transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_FIND_PARENT_RESPONSE, false).set(_nc.distance)); - } + if (type == I_FIND_PARENT_REQUEST) { +#if defined(MY_REPEATER_FEATURE) + if (sender != _transportConfig.parentNodeId) { // no circular reference + TRANSPORT_DEBUG(PSTR("TSF:MSG:FPAR REQ,ID=%d\n"), sender); // FPAR: find parent request + // check if uplink functional - node can only be parent node if link to GW functional + // this also prevents circular references in case GW ooo + if (transportCheckUplink()) { + _transportSM.lastUplinkCheck = hwMillis(); + TRANSPORT_DEBUG(PSTR("TSF:MSG:GWL OK\n")); // GW uplink ok + // random delay minimizes collisions + delay(hwMillis() & 0x3ff); + (void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL, + I_FIND_PARENT_RESPONSE).set(_transportConfig.distanceGW)); + } else { + TRANSPORT_DEBUG(PSTR("!TSF:MSG:GWL FAIL\n")); // GW uplink fail, do not respond to parent request } - - #endif - return; // no further processing required + } +#endif + return; // no further processing required, do not forward } + } // isTransportReady + if (type == I_FIND_PARENT_RESPONSE) { + return; // no further processing required, do not forward } - if (type == I_DISCOVER) { - if (last == _nc.parentNodeId) { +#if !defined(MY_GATEWAY_FEATURE) + if (type == I_DISCOVER_REQUEST) { + if (last == _transportConfig.parentNodeId) { // random wait to minimize collisions delay(hwMillis() & 0x3ff); - transportRouteMessage(build(_msgTmp, _nc.nodeId, sender, NODE_SENSOR_ID, C_INTERNAL, I_DISCOVER_RESPONSE, false).set(_nc.parentNodeId)); + (void)transportRouteMessage(build(_msgTmp, sender, NODE_SENSOR_ID, C_INTERNAL, + I_DISCOVER_RESPONSE).set(_transportConfig.parentNodeId)); // no return here (for fwd if repeater) } } +#endif } // controlled BC relay - #if defined(MY_REPEATER_FEATURE) - // controlled BC repeating: forward only if message received from parent and sender not self to prevent circular fwds - if(last == _nc.parentNodeId && sender != _nc.nodeId && isTransportOK()){ - debug(PSTR("TSP:MSG:FWD BC MSG\n")); // forward BC msg - transportRouteMessage(_msg); - } - #endif - - // Call incoming message callback if available, but only if message received from parent - if (command != C_INTERNAL && last == _nc.parentNodeId && receive) { - receive(_msg); +#if defined(MY_REPEATER_FEATURE) + // controlled BC repeating: forward only if message received from parent and sender not self to prevent circular fwds + if(last == _transportConfig.parentNodeId && sender != _transportConfig.nodeId && + isTransportReady()) { + TRANSPORT_DEBUG(PSTR("TSF:MSG:FWD BC MSG\n")); // controlled broadcast msg forwarding + (void)transportRouteMessage(_msg); } - - } - else { - // msg not no us and not BC, relay msg - #if defined(MY_REPEATER_FEATURE) - if (isTransportOK()) { - debug(PSTR("TSP:MSG:REL MSG\n")); // relay msg - // update routing table if message not received from parent - if (last != _nc.parentNodeId) { - hwWriteConfig(EEPROM_ROUTES_ADDRESS + sender, last); +#endif + + // Callback for BC, only for non-internal messages + if (command != C_INTERNAL) { +#if !defined(MY_GATEWAY_FEATURE) + // only proceed if message received from parent + if (last != _transportConfig.parentNodeId) { + return; + } +#endif +#if defined(MY_GATEWAY_FEATURE) + // Hand over message to controller + (void)gatewayTransportSend(_msg); +#endif + if (receive) { + receive(_msg); } + } + + } else { + // msg not to us and not BC, relay msg +#if defined(MY_REPEATER_FEATURE) + if (isTransportReady()) { + TRANSPORT_DEBUG(PSTR("TSF:MSG:REL MSG\n")); // relay msg if (command == C_INTERNAL) { if (type == I_PING || type == I_PONG) { uint8_t hopsCnt = _msg.getByte(); if (hopsCnt != MAX_HOPS) { - debug(PSTR("TSP:MSG:REL PxNG (hops=%d)\n"), hopsCnt); - _msg.set((uint8_t)(hopsCnt + 1)); + TRANSPORT_DEBUG(PSTR("TSF:MSG:REL PxNG,HP=%d\n"), hopsCnt); + hopsCnt++; + _msg.set(hopsCnt); } } } // Relay this message to another node - transportRouteMessage(_msg); + (void)transportRouteMessage(_msg); } - #else - debug(PSTR("!TSM:MSG:REL MSG, but not a repeater\n")); - #endif +#else + TRANSPORT_DEBUG(PSTR("!TSF:MSG:REL MSG,NREP\n")); // message relaying request, but not a repeater +#endif } } -inline void transportProcessFIFO() { - if (_transportSM.transportActive) { - #if defined(MY_TRANSPORT_SANITY_CHECK) || defined(MY_REPEATER_FEATURE) - if (hwMillis() > (_transport_lastSanityCheck + MY_TRANSPORT_SANITY_CHECK_INTERVAL)) { - _transport_lastSanityCheck = hwMillis(); - if (!transportSanityCheck()) { - debug(PSTR("!TSP:SANCHK:FAIL\n")); // sanity check fail - transportSwitchSM(stFailure); - return; - } - else { - debug(PSTR("TSP:SANCHK:OK\n")); // sanity check ok - } - } - #endif +void transportInvokeSanityCheck(void) +{ + if (!transportSanityCheck()) { + TRANSPORT_DEBUG(PSTR("!TSF:SAN:FAIL\n")); // sanity check fail + transportSwitchSM(stFailure); + } else { + TRANSPORT_DEBUG(PSTR("TSF:SAN:OK\n")); // sanity check ok } - else { - // transport not active, nothing to be done +} + +void transportProcessFIFO(void) +{ + if (!_transportSM.transportActive) { + // transport not active, no further processing required return; } + +#if defined(MY_TRANSPORT_SANITY_CHECK) + if (hwMillis() - _lastSanityCheck > MY_TRANSPORT_SANITY_CHECK_INTERVAL_MS) { + _lastSanityCheck = hwMillis(); + transportInvokeSanityCheck(); + } +#endif + uint8_t _processedMessages = MAX_SUBSEQ_MSGS; // process all msgs in FIFO or counter exit while (transportAvailable() && _processedMessages--) { transportProcessMessage(); } - #if defined(MY_OTA_FIRMWARE_FEATURE) - if (isTransportOK()) { - // only process if transport ok - firmwareOTAUpdateRequest(); - } - #endif +#if defined(MY_OTA_FIRMWARE_FEATURE) + if (isTransportReady()) { + // only process if transport ok + firmwareOTAUpdateRequest(); + } +#endif } -bool transportSendWrite(uint8_t to, MyMessage &message) { - // set protocol version and update last - mSetVersion(message, PROTOCOL_VERSION); - message.last = _nc.nodeId; - +bool transportSendWrite(const uint8_t to, MyMessage &message) +{ + message.last = _transportConfig.nodeId; // Update last // sign message if required if (!signerSignMsg(message)) { - debug(PSTR("!TSP:MSG:SIGN fail\n")); + TRANSPORT_DEBUG(PSTR("!TSF:MSG:SIGN FAIL\n")); setIndication(INDICATION_ERR_SIGN); return false; } - + // msg length changes if signed - uint8_t length = mGetSigned(message) ? MAX_MESSAGE_LENGTH : mGetLength(message); - + const uint8_t totalMsgLength = HEADER_SIZE + ( mGetSigned(message) ? MAX_PAYLOAD : mGetLength( + message) ); + // send setIndication(INDICATION_TX); - bool ok = transportSend(to, &message, min(MAX_MESSAGE_LENGTH, HEADER_SIZE + length)); - - debug(PSTR("%sTSP:MSG:SEND %d-%d-%d-%d s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,ft=%d,st=%s:%s\n"), - (ok || to == BROADCAST_ADDRESS ? "" : "!"),message.sender,message.last, to, message.destination, message.sensor, mGetCommand(message), message.type, - mGetPayloadType(message), mGetLength(message), mGetSigned(message), _transportSM.failedUplinkTransmissions, to==BROADCAST_ADDRESS ? "bc" : (ok ? "ok":"fail"), message.getString(_convBuf)); - - return (ok || to==BROADCAST_ADDRESS); + bool result = transportSend(to, &message, min((uint8_t)MAX_MESSAGE_LENGTH, totalMsgLength)); + // broadcasting (workaround counterfeits) + result |= (to == BROADCAST_ADDRESS); + + TRANSPORT_DEBUG(PSTR("%sTSF:MSG:SEND,%d-%d-%d-%d,s=%d,c=%d,t=%d,pt=%d,l=%d,sg=%d,ft=%d,st=%s:%s\n"), + (result ? "" : "!"), message.sender, message.last, to, message.destination, message.sensor, + mGetCommand(message), message.type, + mGetPayloadType(message), mGetLength(message), mGetSigned(message), + _transportSM.failedUplinkTransmissions, (result ? "OK" : "NACK"), message.getString(_convBuf)); + + return result; +} + +void transportRegisterReadyCallback(transportCallback_t cb) +{ + transportReady_cb = cb; +} + +uint8_t transportGetNodeId(void) +{ + return _transportConfig.nodeId; +} +uint8_t transportGetParentNodeId(void) +{ + return _transportConfig.parentNodeId; +} +uint8_t transportGetDistanceGW(void) +{ + return _transportConfig.distanceGW; +} + + +void transportClearRoutingTable(void) +{ + for (uint16_t i = 0; i < SIZE_ROUTES; i++) { + transportSetRoute((uint8_t)i, BROADCAST_ADDRESS); + } + transportSaveRoutingTable(); // save cleared routing table to EEPROM (if feature enabled) + TRANSPORT_DEBUG(PSTR("TSF:CRT:OK\n")); // clear routing table } +void transportLoadRoutingTable(void) +{ +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) + hwReadConfigBlock((void*)&_transportRoutingTable.route, (void*)EEPROM_ROUTES_ADDRESS, SIZE_ROUTES); + TRANSPORT_DEBUG(PSTR("TSF:LRT:OK\n")); // load routing table +#endif +} + +void transportSaveRoutingTable(void) +{ +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) + hwWriteConfigBlock((void*)&_transportRoutingTable.route, (void*)EEPROM_ROUTES_ADDRESS, SIZE_ROUTES); + TRANSPORT_DEBUG(PSTR("TSF:SRT:OK\n")); // save routing table +#endif +} + +void transportSetRoute(const uint8_t node, const uint8_t route) +{ +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) + _transportRoutingTable.route[node] = route; +#else + hwWriteConfig(EEPROM_ROUTES_ADDRESS + node, route); +#endif +} + +uint8_t transportGetRoute(const uint8_t node) +{ + uint8_t result; +#if defined(MY_RAM_ROUTING_TABLE_ENABLED) + result = _transportRoutingTable.route[node]; +#else + result = hwReadConfig(EEPROM_ROUTES_ADDRESS + node); +#endif + return result; +} diff --git a/core/MyTransport.h b/core/MyTransport.h index c4800e9c1..02a3f0eca 100644 --- a/core/MyTransport.h +++ b/core/MyTransport.h @@ -6,7 +6,7 @@ * network topology allowing messages to be routed to nodes. * * Created by Henrik Ekblad - * Copyright (C) 2013-2015 Sensnology AB + * Copyright (C) 2013-2016 Sensnology AB * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors * * Documentation: http://www.mysensors.org @@ -17,154 +17,355 @@ * version 2 as published by the Free Software Foundation. */ - /** - * @file MyTransport.h - * - * @brief API declaration for MyTransport - */ +/** +* @file MyTransport.h +* +* @defgroup MyTransportgrp MyTransport +* @ingroup internals +* @{ +* +* Transport-related log messages, format: [!]SYSTEM:[SUB SYSTEM:]MESSAGE +* - [!] Exclamation mark is prepended in case of error +* - SYSTEM: +* - TSM: messages emitted by the transport state machine +* - TSF: messages emitted by transport support functions +* - SUB SYSTEMS: +* - Transport state machine (TSM) +* - TSM:INIT from stInit Initialize transport and radio +* - TSM:FPAR from stParent Find parent +* - TSM:ID from stID Check/request node ID, if dynamic node ID set +* - TSM:UPL from stUplink Verify uplink connection by pinging GW +* - TSM:READY from stReady Transport is ready and fully operational +* - TSM:FAIL from stFailure Failure in transport link or transport HW +* - Transport support function (TSF) +* - TSF:CKU from @ref transportCheckUplink(), checks connection to GW +* - TSF:SID from @ref transportAssignNodeID(), assigns node ID +* - TSF:PNG from @ref transportPingNode(), pings a node +* - TSF:WUR from @ref transportWaitUntilReady(), waits until transport is ready +* - TSF:CRT from @ref transportClearRoutingTable(), clears routing table stored in EEPROM +* - TSF:LRT from @ref transportLoadRoutingTable(), loads RAM routing table from EEPROM (only GW/repeaters) +* - TSF:SRT from @ref transportSaveRoutingTable(), saves RAM routing table to EEPROM (only GW/repeaters) +* - TSF:MSG from @ref transportProcessMessage(), processes incoming message +* - TSF:SAN from @ref transportInvokeSanityCheck(), calls transport-specific sanity check +* - TSF:RTE from @ref transportRouteMessage(), sends message +* - TSF:SND from @ref transportSendRoute(), sends message if transport is ready (exposed) + +* +* Transport debug log messages: +* +* |E| SYS | SUB | Message | Comment +* |-|------|-----------|-----------------------|--------------------------------------------------------------------- +* | | TSM | INIT | | Transition to stInit state +* | | TSM | INIT | STATID=%%d | Node ID is static +* | | TSM | INIT | TSP OK | Transport device configured and fully operational +* | | TSM | INIT | GW MODE | Node is set up as GW, thus omitting ID and findParent states +* |!| TSM | INIT | TSP FAIL | Transport device initialization failed +* | | TSM | FPAR | | Transition to stParent state +* | | TSM | FPAR | STATP=%%d | Static parent set, skip finding parent +* | | TSM | FPAR | OK | Parent node identified +* |!| TSM | FPAR | NO REPLY | No potential parents replied to find parent request +* |!| TSM | FPAR | FAIL | Finding parent failed +* | | TSM | ID | | Transition to stID state +* | | TSM | ID | OK,ID=%%d | Node ID is valid +* | | TSM | ID | REQ | Request node ID from controller +* |!| TSM | ID | FAIL,ID=%%d | ID verification failed, ID invalid +* | | TSM | UPL | | Transition to stUplink state +* | | TSM | UPL | OK | Uplink OK, GW returned ping +* | | TSF | UPL | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N) +* |!| TSM | UPL | FAIL | Uplink check failed, i.e. GW could not be pinged +* | | TSM | READY | SRT | Save routing table +* | | TSM | READY | ID=%%d,PAR=%%d,DIS=%%d| Transition to stReady Transport ready, node ID (ID), parent node ID (PAR), distance to GW (DIS) +* |!| TSM | READY | UPL FAIL,SNP | Too many failed uplink transmissions, search new parent +* |!| TSM | READY | FAIL,STATP | Too many failed uplink transmissions, static parent enforced +* | | TSM | FAIL | CNT=%%d | Transition to stFailure state, consecutive failure counter (CNT) +* | | TSM | FAIL | PDT | Power-down transport +* | | TSM | FAIL | RE-INIT | Attempt to re-initialize transport +* | | TSF | CKU | OK | Uplink OK +* | | TSF | CKU | OK,FCTRL | Uplink OK, flood control prevents pinging GW in too short intervals +* | | TSF | CKU | DGWC,O=%%d,N=%%d | Uplink check revealed changed network topology, old distance (O), new distance (N) +* | | TSF | CKU | FAIL | No reply received when checking uplink +* | | TSF | SID | OK,ID=%%d | Node ID assigned +* |!| TSF | SID | FAIL,ID=%%d | Assigned ID is invalid +* | | TSF | PNG | SEND,TO=%%d | Send ping to destination (TO) +* | | TSF | WUR | MS=%%lu | Wait until transport ready, timeout (MS) +* | | TSF | MSG | ACK REQ | ACK message requested +* | | TSF | MSG | ACK | ACK message, do not proceed but forward to callback +* | | TSF | MSG | FPAR RES,ID=%%d,D=%%d | Response to find parent received from node (ID) with distance (D) to GW +* | | TSF | MSG | FPAR PREF FOUND | Preferred parent found, i.e. parent defined via MY_PARENT_NODE_ID +* | | TSF | MSG | FPAR OK,ID=%%d,D=%%d | Find parent response from node (ID) is valid, distance (D) to GW +* | | TSF | MSG | FPAR INACTIVE | Find parent response received, but no find parent request active, skip response +* | | TSF | MSG | FPAR REQ,ID=%%d | Find parent request from node (ID) +* | | TSF | MSG | PINGED,ID=%%d,HP=%%d | Node pinged by node (ID) with (HP) hops +* | | TSF | MSG | PONG RECV,HP=%%d | Pinged node replied with (HP) hops +* | | TSF | MSG | BC | Broadcast message received +* | | TSF | MSG | GWL OK | Link to GW ok +* | | TSF | MSG | FWD BC MSG | Controlled broadcast message forwarding +* | | TSF | MSG | REL MSG | Relay message +* | | TSF | MSG | REL PxNG,HP=%%d | Relay PING/PONG message, increment hop counter (HP) +* |!| TSF | MSG | LEN,%%d!=%%d | Invalid message length, (actual!=expected) +* |!| TSF | MSG | PVER,%%d!=%%d | Message protocol version mismatch (actual!=expected) +* |!| TSF | MSG | SIGN VERIFY FAIL | Signing verification failed +* |!| TSF | MSG | REL MSG,NORP | Node received a message for relaying, but node is not a repeater, message skipped +* |!| TSF | MSG | SIGN FAIL | Signing message failed +* |!| TSF | MSG | GWL FAIL | GW uplink failed +* | | TSF | SAN | OK | Sanity check passed +* |!| TSF | SAN | FAIL | Sanity check failed, attempt to re-initialize radio +* | | TSF | CRT | OK | Clearing routing table successful +* | | TSF | LRT | OK | Loading routing table successful +* | | TSF | SRT | OK | Saving routing table successful +* |!| TSF | RTE | FPAR ACTIVE | Finding parent active, message not sent +* |!| TSF | RTE | DST %%d UNKNOWN | Routing for destination (DST) unknown, send message to parent +* |!| TSF | SND | TNR | Transport not ready, message cannot be sent +* +* Incoming / outgoing messages: +* +* See here for more detail on the format and definitons. +* +* Receiving a message +* - TSF:MSG:READ,sender-last-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d:%%s +* +* Sending a message +* - [!]TSF:MSG:SEND,sender-last-next-destination,s=%%d,c=%%d,t=%%d,pt=%%d,l=%%d,sg=%%d,ft=%%d,st=%%s:%%s +* +* Message fields: +* - s=sensor ID +* - c=command +* - t=msg type +* - pt=payload type +* - l=length +* - sg=signing flag +* - ft=failed uplink transmission counter +* - st=send status, OK=success, NACK=no radio ACK received +* +* @brief API declaration for MyTransport +* +*/ + #ifndef MyTransport_h #define MyTransport_h #include "MySensorsCore.h" +// debug +#if defined(MY_DEBUG) +#define TRANSPORT_DEBUG(x,...) hwDebugPrint(x, ##__VA_ARGS__) //!< debug +extern char _convBuf[MAX_PAYLOAD * 2 + 1]; +#else +#define TRANSPORT_DEBUG(x,...) //!< debug NULL +#endif + + #if defined(MY_REPEATER_FEATURE) - #define TRANSMISSION_FAILURES 10 //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes +#define MY_TRANSPORT_MAX_TX_FAILURES (10u) //!< search for a new parent node after this many transmission failures, higher threshold for repeating nodes #else - #define TRANSMISSION_FAILURES 5 //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes +#define MY_TRANSPORT_MAX_TX_FAILURES (5u) //!< search for a new parent node after this many transmission failures, lower threshold for non-repeating nodes +#endif + +#define MY_TRANSPORT_MAX_TSM_FAILURES (7u) //!< Max. number of consecutive TSM failure state entries (3bits) + +#ifndef MY_TRANSPORT_TIMEOUT_FAILURE_STATE +#define MY_TRANSPORT_TIMEOUT_FAILURE_STATE (10*1000ul) //!< Duration failure state (in ms) +#endif +#ifndef MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE +#define MY_TRANSPORT_TIMEOUT_EXT_FAILURE_STATE (60*1000ul) //!< Duration extended failure state (in ms) +#endif +#ifndef MY_TRANSPORT_STATE_TIMEOUT_MS +#define MY_TRANSPORT_STATE_TIMEOUT_MS (2*1000ul) //!< general state timeout (in ms) +#endif +#ifndef MY_TRANSPORT_CHKUPL_INTERVAL_MS +#define MY_TRANSPORT_CHKUPL_INTERVAL_MS (10*1000ul) //!< Interval to re-check uplink (in ms) +#endif +#ifndef MY_TRANSPORT_STATE_RETRIES +#define MY_TRANSPORT_STATE_RETRIES (3u) //!< retries before switching to FAILURE +#endif + +#define AUTO (255u) //!< ID 255 is reserved +#define BROADCAST_ADDRESS (255u) //!< broadcasts are addressed to ID 255 +#define DISTANCE_INVALID (255u) //!< invalid distance when searching for parent +#define MAX_HOPS (254u) //!< maximal mumber of hops for ping/pong +#define INVALID_HOPS (255u) //!< invalid hops +#define MAX_SUBSEQ_MSGS (5u) //!< Maximum number of subsequentially processed messages in FIFO (to prevent transport deadlock if HW issue) + +// parent node check +#if defined(MY_PARENT_NODE_IS_STATIC) && !defined(MY_PARENT_NODE_ID) +#error MY_PARENT_NODE_IS_STATIC but no MY_PARENT_NODE_ID defined! #endif -#define TIMEOUT_FAILURE_STATE 10000 //!< duration failure state -#define STATE_TIMEOUT 2000 //!< general state timeout -#define STATE_RETRIES 3 //!< retries before switching to FAILURE -#define AUTO 255 //!< ID 255 is reserved for auto initialization of nodeId. -#define BROADCAST_ADDRESS ((uint8_t)255) //!< broadcasts are addressed to ID 255 -#define DISTANCE_INVALID ((uint8_t)255) //!< invalid distance when searching for parent -#define MAX_HOPS ((uint8_t)254) //!< maximal mumber of hops for ping/pong -#define INVALID_HOPS ((uint8_t)255) //!< invalid hops -#define MAX_SUBSEQ_MSGS 5 //!< Maximum number of subsequentially processed messages in FIFO (to prevent transport deadlock if HW issue) -#define CHKUPL_INTERVAL ((uint32_t)10000) //!< Minimum time interval to re-check uplink #define _autoFindParent (bool)(MY_PARENT_NODE_ID == AUTO) //!< returns true if static parent id is undefined -#define isValidDistance(distance) (bool)(distance!=DISTANCE_INVALID) //!< returns true if distance is valid -#define isValidParent(parent) (bool)(parent != AUTO) //!< returns true if parent is valid +#define isValidDistance(_distance) (bool)(_distance!=DISTANCE_INVALID) //!< returns true if distance is valid +#define isValidParent(_parent) (bool)(_parent != AUTO) //!< returns true if parent is valid + +// RX queue +#if defined(MY_RX_MESSAGE_BUFFER_FEATURE) +#if defined(MY_RADIO_RFM69) +#error Receive message buffering not supported for RFM69! +#endif +#if defined(MY_RS485) +#error Receive message buffering not supported for RS485! +#endif +#elif !defined(MY_RX_MESSAGE_BUFFER_FEATURE) && defined(MY_RX_MESSAGE_BUFFER_SIZE) +#error Receive message buffering requires message buffering feature enabled! +#endif + +/** + * @brief Callback type + */ +typedef void(*transportCallback_t)(void); + +/** + * @brief Node configuration + * + * This structure stores node-related configurations + */ +typedef struct { + uint8_t nodeId; //!< Current node id + uint8_t parentNodeId; //!< Where this node sends its messages + uint8_t distanceGW; //!< This nodes distance to sensor net gateway (number of hops) +} transportConfig_t; - /** +/** * @brief SM state * * This structure stores SM state definitions */ -struct State { - void(*Transition)(); //!< state transition function - void(*Update)(); //!< state update function -}; +typedef struct { + void(*Transition)(void); //!< state transition function + void(*Update)(void); //!< state update function +} transportState_t; /** -* @brief Status variables and SM state -* -* This structure stores transport status and SM variables -*/ + * @brief Status variables and SM state + * + * This structure stores transport status and SM variables + */ typedef struct { - State* currentState; //!< pointer to current fsm state + // SM variables + transportState_t* currentState; //!< pointer to current fsm state uint32_t stateEnter; //!< state enter timepoint + // general transport variables + uint32_t lastUplinkCheck; //!< last uplink check, required to prevent GW flooding + // 8 bits bool findingParentNode : 1; //!< flag finding parent node is active bool preferredParentFound : 1; //!< flag preferred parent found bool uplinkOk : 1; //!< flag uplink ok bool pingActive : 1; //!< flag ping active bool transportActive : 1; //!< flag transport active - uint8_t reserved : 3; //!< reserved - uint8_t retries : 4; //!< retries / state re-enter - uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions - uint8_t pingResponse; //!< stores hops received in I_PONG -} __attribute__((packed)) transportSM; + uint8_t stateRetries : 3; //!< retries / state re-enter (max 7) + // 8 bits + uint8_t failedUplinkTransmissions : 4; //!< counter failed uplink transmissions (max 15) + uint8_t failureCounter : 3; //!< counter for TSM failures (max 7) + bool msgReceived : 1; //!< flag message received + // 8 bits + uint8_t pingResponse; //!< stores I_PONG hops +} transportSM_t; +/** +* @brief RAM routing table +*/ +typedef struct { + uint8_t route[SIZE_ROUTES]; //!< route for node +} routingTable_t; // PRIVATE functions /** * @brief Initialize SM variables and transport HW */ -void stInitTransition(); +void stInitTransition(void); +/** +* @brief Initialize transport +*/ +void stInitUpdate(void); /** * @brief Find parent */ -void stParentTransition(); +void stParentTransition(void); /** * @brief Verify find parent responses */ -void stParentUpdate(); +void stParentUpdate(void); /** * @brief Send ID request */ -void stIDTransition(); +void stIDTransition(void); /** * @brief Verify ID response and GW link */ -void stIDUpdate(); +void stIDUpdate(void); /** * @brief Send uplink ping request */ -void stUplinkTransition(); +void stUplinkTransition(void); +/** +* @brief Verify uplink response +*/ +void stUplinkUpdate(void); /** * @brief Set transport OK */ -void stOKTransition(); +void stReadyTransition(void); /** * @brief Monitor transport link */ -void stOKUpdate(); +void stReadyUpdate(void); /** -* @brief Transport failure and power down radio +* @brief Transport failure and power down radio */ -void stFailureTransition(); +void stFailureTransition(void); /** * @brief Re-initialize transport after timeout */ -void stFailureUpdate(); +void stFailureUpdate(void); /** * @brief Switch SM state * @param newState New state to switch SM to */ -void transportSwitchSM(State& newState); +void transportSwitchSM(transportState_t& newState); /** * @brief Update SM state */ -void transportUpdateSM(); +void transportUpdateSM(void); /** * @brief Request time in current SM state * @return ms in current state */ -uint32_t transportTimeInState(); - +uint32_t transportTimeInState(void); +/** +* @brief Call transport driver sanity check +*/ +void transportInvokeSanityCheck(void); /** * @brief Process all pending messages in RX FIFO */ -void transportProcessFIFO(); +void transportProcessFIFO(void); /** * @brief Receive message from RX FIFO and process */ -void transportProcessMessage(); +void transportProcessMessage(void); /** * @brief Assign node ID * @param newNodeId New node ID +* @return true if node ID is valid and successfully assigned */ -void transportAssignNodeID(uint8_t newNodeId); +bool transportAssignNodeID(const uint8_t newNodeId); /** * @brief Wait and process messages for a defined amount of time until specified message received -* @param ms Time to wait and process incoming messages in ms +* @param waitingMS Time to wait and process incoming messages in ms * @param cmd Specific command -* @param msgtype Specific message type +* @param msgType Specific message type * @return true if specified command received within waiting time */ -bool transportWait(uint32_t ms, uint8_t cmd, uint8_t msgtype); +bool transportWait(const uint32_t waitingMS, const uint8_t cmd, const uint8_t msgType); /** * @brief Ping node * @param targetId Node to be pinged * @return hops from pinged node or 255 if no answer received within 2000ms */ -uint8_t transportPingNode(uint8_t targetId); +uint8_t transportPingNode(const uint8_t targetId); /** * @brief Send and route message according to destination -* -* This function is used in MyTransport and omits the transport state check, i.e. message can be sent even if transport is !OK +* +* This function is used in MyTransport and omits the transport state check, i.e. message can be sent even if transport is not ready * * @param message * @return true if message sent successfully @@ -180,55 +381,118 @@ bool transportSendRoute(MyMessage &message); * @brief Send message to recipient * @param to Recipient of message * @param message -* @return true if message sent successfully +* @return true if message sent successfully */ -bool transportSendWrite(uint8_t to, MyMessage &message); +bool transportSendWrite(const uint8_t to, MyMessage &message); /** * @brief Check uplink to GW, includes flooding control * @param force to override flood control timer * @return true if uplink ok */ -bool transportCheckUplink(bool force); +bool transportCheckUplink(const bool force = false); // PUBLIC functions +/** +* @brief Wait until transport is ready +* @param waitingMS timeout in MS, set 0 (default) for no timeout, i.e. wait indefinitely. For a node in standalone mode (optional network connection) set >0 to allow a node entering the main loop() function. +* @return true if transport is ready +*/ +bool transportWaitUntilReady(const uint32_t waitingMS = 0); /** * @brief Initialize transport and SM */ -void transportInitialize(); +void transportInitialize(void); /** * @brief Process FIFO msg and update SM */ -void transportProcess(); +void transportProcess(void); +/** +* @brief Flag transport ready +* @return true if transport is initialized and ready +*/ +bool isTransportReady(void); /** -* @brief Verify transport status -* @return true if transport is initialize and ready +* @brief Flag searching parent ongoing +* @return true if transport is searching for parent */ -bool isTransportOK(); +bool isTransportSearchingParent(void); +/** +* @brief Flag TSM extended failure +* @return true if TSM had too many consecutive failure state entries +*/ +bool isTransportExtendedFailure(void); +/** +* @brief Flag valid message received +* @return true if valid message received, needs to be reset if used +*/ +bool isMessageReceived(void); +/** +* @brief Reset message received flag +*/ +void resetMessageReceived(void); /** * @brief Clear routing table */ -void transportClearRoutingTable(); +void transportClearRoutingTable(void); /** -* @brief Return heart beat, i.e. ms in current state +* @brief Return heart beat +* @return MS in current state */ -uint32_t transportGetHeartbeat(); +uint32_t transportGetHeartbeat(void); +/** +* @brief Load routing table from EEPROM to RAM. +* Only for GW devices with enough RAM, i.e. ESP8266, RPI Sensebender GW, etc. +* Atmega328 has only limited amount of RAM +*/ +void transportLoadRoutingTable(void); +/** +* @brief Save routing table to EEPROM. +*/ +void transportSaveRoutingTable(void); +/** +* @brief Update routing table +* @param node +* @param route +*/ +void transportSetRoute(const uint8_t node, const uint8_t route); +/** +* @brief Load route to node +* @param node +* @return route to node +*/ +uint8_t transportGetRoute(const uint8_t node); +/** +* @brief Get node ID +* @return node ID +*/ +uint8_t transportGetNodeId(void); +/** +* @brief Get parent node ID +* @return parent node ID +*/ +uint8_t transportGetParentNodeId(void); +/** +* @brief Get distance to GW +* @return distance (=hops) to GW +*/ +uint8_t transportGetDistanceGW(void); // interface functions for radio driver /** * @brief Initialize transport HW -* @return true if initalization successful +* @return true if initialization successful */ -bool transportInit(); +bool transportInit(void); /** * @brief Set node address */ -void transportSetAddress(uint8_t address); +void transportSetAddress(const uint8_t address); /** * @brief Retrieve node address */ -uint8_t transportGetAddress(); +uint8_t transportGetAddress(void); /** * @brief Send message * @param to recipient @@ -236,27 +500,26 @@ uint8_t transportGetAddress(); * @param len length of message (header + payload) * @return true if message sent successfully */ -bool transportSend(uint8_t to, const void* data, uint8_t len); +bool transportSend(const uint8_t to, const void* data, const uint8_t len); /** * @brief Verify if RX FIFO has pending messages * @return true if message available in RX FIFO */ -bool transportAvailable(); +bool transportAvailable(void); /** * @brief Sanity check for transport: is transport still responsive? -* @return true transport ok +* @return true if transport HW is ok */ -bool transportSanityCheck(); +bool transportSanityCheck(void); /** * @brief Receive message from FIFO * @return length of recevied message (header + payload) */ -uint8_t transportReceive(void* data); +uint8_t transportReceive(void* data); /** * @brief Power down transport HW */ -void transportPowerDown(); - +void transportPowerDown(void); #endif // MyTransport_h /** @}*/ diff --git a/core/MyTransportNRF24.cpp b/core/MyTransportNRF24.cpp index e38ecf4ca..14e8d257b 100644 --- a/core/MyTransportNRF24.cpp +++ b/core/MyTransportNRF24.cpp @@ -19,77 +19,137 @@ #include "MyConfig.h" #include "MyTransport.h" + #include "drivers/RF24/RF24.h" +#include "drivers/CircularBuffer/CircularBuffer.h" + +#if defined(MY_RF24_ENABLE_ENCRYPTION) +#include "drivers/AES/AES.h" +#endif + +#if defined(MY_RX_MESSAGE_BUFFER_FEATURE) +typedef struct _transportQueuedMessage { + uint8_t m_len; // Length of the data + uint8_t m_data[MAX_MESSAGE_LENGTH]; // The raw data +} transportQueuedMessage; + +/** Buffer to store queued messages in. */ +static transportQueuedMessage transportRxQueueStorage[MY_RX_MESSAGE_BUFFER_SIZE]; +/** Circular buffer, which uses the transportRxQueueStorage and administers stored messages. */ +static CircularBuffer transportRxQueue(transportRxQueueStorage, + MY_RX_MESSAGE_BUFFER_SIZE); + +static volatile uint8_t transportLostMessageCount = 0; + +static void transportRxCallback(void) +{ + // Called for each message received by radio, from interrupt context. + // This function _must_ call RF24_readMessage() to de-assert interrupt line! + if (!transportRxQueue.full()) { + transportQueuedMessage* msg = transportRxQueue.getFront(); + msg->m_len = RF24_readMessage(msg->m_data); // Read payload & clear RX_DR + (void)transportRxQueue.pushFront(msg); + } else { + // Queue is full. Discard message. + (void)RF24_readMessage(NULL); // Read payload & clear RX_DR + // Keep track of messages lost. Max 255, prevent wrapping. + if (transportLostMessageCount < 255) { + ++transportLostMessageCount; + } + } +} +#endif + #if defined(MY_RF24_ENABLE_ENCRYPTION) - #include "drivers/AES/AES.h" +AES _aes; +uint8_t _dataenc[32] = {0}; +uint8_t _psk[16]; #endif +bool transportInit(void) +{ #if defined(MY_RF24_ENABLE_ENCRYPTION) - AES _aes; - uint8_t _dataenc[32] = {0}; - uint8_t _psk[16]; + hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); + //set up AES-key + _aes.set_key(_psk, 16); + // Make sure it is purged from memory when set + memset(_psk, 0, 16); #endif -bool transportInit() { - - #if defined(MY_RF24_ENABLE_ENCRYPTION) - hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); - //set up AES-key - _aes.set_key(_psk, 16); - // Make sure it is purged from memory when set - memset(_psk, 0, 16); - #endif - +#if defined(MY_RX_MESSAGE_BUFFER_FEATURE) + RF24_registerReceiveCallback( transportRxCallback ); +#endif return RF24_initialize(); } -void transportSetAddress(uint8_t address) { +void transportSetAddress(const uint8_t address) +{ RF24_setNodeAddress(address); RF24_startListening(); } -uint8_t transportGetAddress() { +uint8_t transportGetAddress(void) +{ return RF24_getNodeID(); } -bool transportSend(uint8_t recipient, const void* data, uint8_t len) { - #if defined(MY_RF24_ENABLE_ENCRYPTION) - // copy input data because it is read-only - memcpy(_dataenc,data,len); - // has to be adjusted, WIP! - _aes.set_IV(0); - len = len > 16 ? 32 : 16; - //encrypt data - _aes.cbc_encrypt(_dataenc, _dataenc, len/16); - bool status = RF24_sendMessage( recipient, _dataenc, len ); - #else - bool status = RF24_sendMessage( recipient, data, len ); - #endif - - return status; +bool transportSend(const uint8_t to, const void* data, const uint8_t len) +{ +#if defined(MY_RF24_ENABLE_ENCRYPTION) + // copy input data because it is read-only + (void)memcpy(_dataenc,data,len); + // has to be adjusted, WIP! + _aes.set_IV(0); + const uint8_t finalLength = len > 16 ? 32 : 16; + //encrypt data + _aes.cbc_encrypt(_dataenc, _dataenc, finalLength /16); + return RF24_sendMessage(to, _dataenc, finalLength); +#else + return RF24_sendMessage(to, data, len); +#endif } -bool transportAvailable() { - bool avail = RF24_isDataAvailable(); - return avail; +bool transportAvailable(void) +{ +#if defined(MY_RX_MESSAGE_BUFFER_FEATURE) + (void)RF24_isDataAvailable; // Prevent 'defined but not used' warning + return !transportRxQueue.empty(); +#else + return RF24_isDataAvailable(); +#endif } -bool transportSanityCheck() { +bool transportSanityCheck(void) +{ return RF24_sanityCheck(); } -uint8_t transportReceive(void* data) { - uint8_t len = RF24_readMessage(data); - #if defined(MY_RF24_ENABLE_ENCRYPTION) - // has to be adjusted, WIP! - _aes.set_IV(0); - // decrypt data - _aes.cbc_decrypt((byte*)(data), (byte*)(data), len>16?2:1); - #endif +uint8_t transportReceive(void* data) +{ + uint8_t len = 0; +#if defined(MY_RX_MESSAGE_BUFFER_FEATURE) + transportQueuedMessage* msg = transportRxQueue.getBack(); + if (msg) { + len = msg->m_len; + (void)memcpy(data, msg->m_data, len); + (void)transportRxQueue.popBack(); + } +#else + len = RF24_readMessage(data); +#endif +#if defined(MY_RF24_ENABLE_ENCRYPTION) + // has to be adjusted, WIP! + _aes.set_IV(0); + // decrypt data + if (_aes.cbc_decrypt((uint8_t*)(data), (uint8_t*)(data), len > 16 ? 2 : 1) != AES_SUCCESS) { + len = 0; + } +#endif return len; } -void transportPowerDown() { +void transportPowerDown(void) +{ RF24_powerDown(); } diff --git a/core/MyTransportRFM69.cpp b/core/MyTransportRFM69.cpp index b5aec3325..905ee8b1a 100644 --- a/core/MyTransportRFM69.cpp +++ b/core/MyTransportRFM69.cpp @@ -26,51 +26,61 @@ RFM69 _radio(MY_RF69_SPI_CS, MY_RF69_IRQ_PIN, MY_RFM69HW, MY_RF69_IRQ_NUM); uint8_t _address; -bool transportInit() { +bool transportInit(void) +{ // Start up the radio library (_address will be set later by the MySensors library) if (_radio.initialize(MY_RFM69_FREQUENCY, _address, MY_RFM69_NETWORKID)) { - #ifdef MY_RFM69_ENABLE_ENCRYPTION - uint8_t _psk[16]; - hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); - _radio.encrypt((const char*)_psk); - memset(_psk, 0, 16); // Make sure it is purged from memory when set - #endif +#ifdef MY_RFM69_ENABLE_ENCRYPTION + uint8_t _psk[16]; + hwReadConfigBlock((void*)_psk, (void*)EEPROM_RF_ENCRYPTION_AES_KEY_ADDRESS, 16); + _radio.encrypt((const char*)_psk); + memset(_psk, 0, 16); // Make sure it is purged from memory when set +#endif return true; } return false; } -void transportSetAddress(uint8_t address) { +void transportSetAddress(const uint8_t address) +{ _address = address; _radio.setAddress(address); } -uint8_t transportGetAddress() { +uint8_t transportGetAddress(void) +{ return _address; } -bool transportSend(uint8_t to, const void* data, uint8_t len) { +bool transportSend(const uint8_t to, const void* data, const uint8_t len) +{ return _radio.sendWithRetry(to,data,len); } -bool transportAvailable() { +bool transportAvailable(void) +{ return _radio.receiveDone(); } -bool transportSanityCheck() { +bool transportSanityCheck(void) +{ // not implemented yet return true; } -uint8_t transportReceive(void* data) { +uint8_t transportReceive(void* data) +{ memcpy(data,(const void *)_radio.DATA, _radio.DATALEN); + // save payload length + const uint8_t dataLen = _radio.DATALEN; // Send ack back if this message wasn't a broadcast - if (_radio.TARGETID != RF69_BROADCAST_ADDR) - _radio.ACKRequested(); - _radio.sendACK(); - return _radio.DATALEN; -} + if(_radio.ACKRequested()) { + _radio.sendACK(); + } + return dataLen; +} -void transportPowerDown() { +void transportPowerDown(void) +{ _radio.sleep(); } diff --git a/core/MyTransportRFM95.cpp b/core/MyTransportRFM95.cpp new file mode 100644 index 000000000..3b88994e9 --- /dev/null +++ b/core/MyTransportRFM95.cpp @@ -0,0 +1,92 @@ +/* + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2016 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + */ + +#include "MyConfig.h" +#include "MyTransport.h" +#include "drivers/RFM95/RFM95.h" + +bool transportInit(void) +{ + const bool result = RFM95_initialise(MY_RFM95_FREQUENCY); +#if !defined(MY_GATEWAY_FEATURE) && !defined(MY_RFM95_ATC_MODE_DISABLED) + // only enable ATC mode nodes + RFM95_ATCmode(true, MY_RFM95_ATC_TARGET_RSSI); +#endif + return result; +} + +void transportSetAddress(const uint8_t address) +{ + RFM95_setAddress(address); +} + +uint8_t transportGetAddress(void) +{ + return RFM95_getAddress(); +} + +bool transportSend(const uint8_t to, const void* data, const uint8_t len) +{ + return RFM95_sendWithRetry(to, data, len); +} + +bool transportAvailable(void) +{ + return RFM95_available(); +} + +bool transportSanityCheck(void) +{ + return RFM95_sanityCheck(); +} + +uint8_t transportReceive(void* data) +{ + return RFM95_recv((uint8_t*)data); +} + +void transportPowerDown(void) +{ + (void)RFM95_sleep(); +} + +// experimental +// ********************************************** +int16_t transportGetReceivingSignalStrength(void) +{ + return RFM95_getReceivingRSSI(); +} +int16_t transportGetSendingSignalStrength(void) +{ + return RFM95_getSendingRSSI(); +} +int8_t transportGetReceivingSNR(void) +{ + return RFM95_getReceivingSNR(); +} +int8_t transportGetSendingSNR(void) +{ + return RFM95_getSendingSNR(); +} +uint8_t transportGetTxPower(void) +{ + return RFM95_getTxPowerPercent(); +} +// ********************************************** diff --git a/core/MyTransportRS485.cpp b/core/MyTransportRS485.cpp index 814f721fd..976bc867c 100644 --- a/core/MyTransportRS485.cpp +++ b/core/MyTransportRS485.cpp @@ -57,23 +57,21 @@ #include "MyTransport.h" #include - -#if defined(ARDUINO) && ARDUINO >= 100 #include -#else -#include -#endif #include "MyTransport.h" +#ifdef __linux__ +#include "SerialPort.h" +#endif #if defined(MY_RS485_DE_PIN) - #define assertDE() digitalWrite(MY_RS485_DE_PIN, HIGH); delayMicroseconds(5) - #define deassertDE() digitalWrite(MY_RS485_DE_PIN, LOW) +#define assertDE() hwDigitalWrite(MY_RS485_DE_PIN, HIGH); delayMicroseconds(5) +#define deassertDE() hwDigitalWrite(MY_RS485_DE_PIN, LOW) #else - #define assertDE() - #define deassertDE() +#define assertDE() +#define deassertDE() #endif // We only use SYS_PACK in this application @@ -92,8 +90,14 @@ unsigned char _recSender; unsigned char _recCS; unsigned char _recCalcCS; -AltSoftSerial _dev; +#if defined(__linux__) +SerialPort _dev = SerialPort(MY_RS485_HWSERIAL); +#elif defined(MY_RS485_HWSERIAL) +HardwareSerial& _dev = MY_RS485_HWSERIAL; +#else +AltSoftSerial _dev; +#endif unsigned char _nodeId; char _data[MY_RS485_MAX_MESSAGE_LENGTH]; @@ -110,13 +114,14 @@ bool _packet_received; //Reset the state machine and release the data pointer -void _serialReset(){ - _recPhase = 0; - _recPos = 0; - _recLen = 0; - _recCommand = 0; - _recCS = 0; - _recCalcCS = 0; +void _serialReset() +{ + _recPhase = 0; + _recPos = 0; + _recLen = 0; + _recCommand = 0; + _recCS = 0; + _recCalcCS = 0; } // This is the main reception state machine. Progress through the states @@ -128,120 +133,118 @@ void _serialReset(){ // function. bool _serialProcess() { - char inch; - unsigned char i; - if (!_dev.available()) return false; - - while(_dev.available()) { - inch = _dev.read(); - - switch(_recPhase) { - - // Case 0 looks for the header. Bytes arrive in the serial interface and get - // shifted through a header buffer. When the start and end characters in - // the buffer match the SOH/STX pair, and the destination station ID matches - // our ID, save the header information and progress to the next state. - case 0: - memcpy(&_header[0],&_header[1],5); - _header[5] = inch; - if ((_header[0] == SOH) && (_header[5] == STX) && (_header[1] != _header[2])) { - _recCalcCS = 0; - _recStation = _header[1]; - _recSender = _header[2]; - _recCommand = _header[3]; - _recLen = _header[4]; - - for (i=1; i<=4; i++) { - _recCalcCS += _header[i]; - } - _recPhase = 1; - _recPos = 0; - - //Check if we should process this message - //We reject the message if we are the sender - //We reject if we are not the receiver and message is not a broadcast - if ((_recSender == _nodeId) || - (_recStation != _nodeId && - _recStation != BROADCAST_ADDRESS)) { - _dev.print(" wrongid: "); - _dev.print(_recStation); - _dev.print(" - "); - _dev.println(_nodeId); - _serialReset(); - break; - } - - if (_recLen == 0) { - _recPhase = 2; - } - - } - break; - - // Case 1 receives the data portion of the packet. Read in "_recLen" number - // of bytes and store them in the _data array. - case 1: - _data[_recPos++] = inch; - _recCalcCS += inch; - if (_recPos == _recLen) { - _recPhase = 2; - } - break; - - // After the data comes a single ETX character. Do we have it? If not, - // reset the state machine to default and start looking for a new header. - case 2: - // Packet properly terminated? - if (inch == ETX) { - _recPhase = 3; - } else { - _serialReset(); - } - break; - - // Next comes the checksum. We have already calculated it from the incoming - // data, so just store the incoming checksum byte for later. - case 3: - _recCS = inch; - _recPhase = 4; - break; - - // The final state - check the last character is EOT and that the checksum matches. - // If that test passes, then look for a valid command callback to execute. - // Execute it if found. - case 4: - if (inch == EOT) { - if (_recCS == _recCalcCS) { - // First, check for system level commands. It is possible - // to register your own callback as well for system level - // commands which will be called after the system default - // hook. - - switch (_recCommand) { - case ICSC_SYS_PACK: - _packet_from = _recSender; - _packet_len = _recLen; - _packet_received = true; - break; - } - } - } - //Clear the data - _serialReset(); - //Return true, we have processed one command - return true; - break; - } - } - return true; + char inch; + unsigned char i; + if (!_dev.available()) { + return false; + } + + while(_dev.available()) { + inch = _dev.read(); + + switch(_recPhase) { + + // Case 0 looks for the header. Bytes arrive in the serial interface and get + // shifted through a header buffer. When the start and end characters in + // the buffer match the SOH/STX pair, and the destination station ID matches + // our ID, save the header information and progress to the next state. + case 0: + memcpy(&_header[0],&_header[1],5); + _header[5] = inch; + if ((_header[0] == SOH) && (_header[5] == STX) && (_header[1] != _header[2])) { + _recCalcCS = 0; + _recStation = _header[1]; + _recSender = _header[2]; + _recCommand = _header[3]; + _recLen = _header[4]; + + for (i=1; i<=4; i++) { + _recCalcCS += _header[i]; + } + _recPhase = 1; + _recPos = 0; + + //Check if we should process this message + //We reject the message if we are the sender + //We reject if we are not the receiver and message is not a broadcast + if ((_recSender == _nodeId) || + (_recStation != _nodeId && + _recStation != BROADCAST_ADDRESS)) { + _serialReset(); + break; + } + + if (_recLen == 0) { + _recPhase = 2; + } + + } + break; + + // Case 1 receives the data portion of the packet. Read in "_recLen" number + // of bytes and store them in the _data array. + case 1: + _data[_recPos++] = inch; + _recCalcCS += inch; + if (_recPos == _recLen) { + _recPhase = 2; + } + break; + + // After the data comes a single ETX character. Do we have it? If not, + // reset the state machine to default and start looking for a new header. + case 2: + // Packet properly terminated? + if (inch == ETX) { + _recPhase = 3; + } else { + _serialReset(); + } + break; + + // Next comes the checksum. We have already calculated it from the incoming + // data, so just store the incoming checksum byte for later. + case 3: + _recCS = inch; + _recPhase = 4; + break; + + // The final state - check the last character is EOT and that the checksum matches. + // If that test passes, then look for a valid command callback to execute. + // Execute it if found. + case 4: + if (inch == EOT) { + if (_recCS == _recCalcCS) { + // First, check for system level commands. It is possible + // to register your own callback as well for system level + // commands which will be called after the system default + // hook. + + switch (_recCommand) { + case ICSC_SYS_PACK: + _packet_from = _recSender; + _packet_len = _recLen; + _packet_received = true; + break; + } + } + } + //Clear the data + _serialReset(); + //Return true, we have processed one command + return true; + break; + } + } + return true; } -bool transportSend(uint8_t to, const void* data, uint8_t len) +bool transportSend(const uint8_t to, const void* data, const uint8_t len) { const char *datap = static_cast(data); - unsigned char i; - unsigned char cs = 0; - unsigned char del; + unsigned char i; + unsigned char cs = 0; + unsigned char del; // This is how many times to try and transmit before failing. unsigned char timeout = 10; @@ -262,99 +265,107 @@ bool transportSend(uint8_t to, const void* data, uint8_t len) } } - #if defined(MY_RS485_DE_PIN) - digitalWrite(MY_RS485_DE_PIN, HIGH); - delayMicroseconds(5); - #endif - - // Start of header by writing multiple SOH - for(byte w=0;w<1;w++) _dev.write(SOH); - _dev.write(to); // Destination address - cs += to; - _dev.write(_nodeId); // Source address - cs += _nodeId; - _dev.write(ICSC_SYS_PACK); // Command code - cs += ICSC_SYS_PACK; - _dev.write(len); // Length of text - cs += len; - _dev.write(STX); // Start of text - for(i=0; i= 100 - #if ARDUINO >= 104 - // Arduino 1.0.4 and upwards does it right - _dev.flush(); - #else - // Between 1.0.0 and 1.0.3 it almost does it - need to compensate - // for the hardware buffer. Delay for 2 bytes worth of transmission. - _dev.flush(); - delayMicroseconds((20000000UL/9600)+1); - #endif - #endif - #endif - digitalWrite(MY_RS485_DE_PIN, LOW); - #endif - return true; +#if defined(MY_RS485_DE_PIN) + hwDigitalWrite(MY_RS485_DE_PIN, HIGH); + delayMicroseconds(5); +#endif + + // Start of header by writing multiple SOH + for(byte w=0; w<1; w++) { + _dev.write(SOH); + } + _dev.write(to); // Destination address + cs += to; + _dev.write(_nodeId); // Source address + cs += _nodeId; + _dev.write(ICSC_SYS_PACK); // Command code + cs += ICSC_SYS_PACK; + _dev.write(len); // Length of text + cs += len; + _dev.write(STX); // Start of text + for(i=0; i= 100 +#if ARDUINO >= 104 + // Arduino 1.0.4 and upwards does it right + _dev.flush(); +#else + // Between 1.0.0 and 1.0.3 it almost does it - need to compensate + // for the hardware buffer. Delay for 2 bytes worth of transmission. + _dev.flush(); + delayMicroseconds((20000000UL/9600)+1); +#endif +#elif defined(__linux__) + _dev.flush(); +#endif +#endif + hwDigitalWrite(MY_RS485_DE_PIN, LOW); +#endif + return true; } -bool transportInit() { - // Reset the state machine +bool transportInit(void) +{ + // Reset the state machine _dev.begin(MY_RS485_BAUD_RATE); - _serialReset(); - #if defined(MY_RS485_DE_PIN) - pinMode(MY_RS485_DE_PIN, OUTPUT); - digitalWrite(MY_RS485_DE_PIN, LOW); - #endif - return true; + _serialReset(); +#if defined(MY_RS485_DE_PIN) + hwPinMode(MY_RS485_DE_PIN, OUTPUT); + hwDigitalWrite(MY_RS485_DE_PIN, LOW); +#endif + return true; } -void transportSetAddress(uint8_t address) { +void transportSetAddress(const uint8_t address) +{ _nodeId = address; } -uint8_t transportGetAddress() { +uint8_t transportGetAddress(void) +{ return _nodeId; } -bool transportAvailable() { +bool transportAvailable(void) +{ _serialProcess(); return _packet_received; } -bool transportSanityCheck() { +bool transportSanityCheck(void) +{ // not implemented yet return true; } -uint8_t transportReceive(void* data) { +uint8_t transportReceive(void* data) +{ if (_packet_received) { memcpy(data,_data,_packet_len); _packet_received = false; return _packet_len; - } - else { + } else { return (0); } } -void transportPowerDown() { +void transportPowerDown(void) +{ // Nothing to shut down here } - - diff --git a/core/Version.h b/core/Version.h index e80c508ab..5f365ff64 100644 --- a/core/Version.h +++ b/core/Version.h @@ -1,3 +1,21 @@ +/** + * The MySensors Arduino library handles the wireless radio link and protocol + * between your home built sensors/actuators and HA controller of choice. + * The sensors forms a self healing radio network with optional repeaters. Each + * repeater and gateway builds a routing tables in EEPROM which keeps track of the + * network topology allowing messages to be routed to nodes. + * + * Created by Henrik Ekblad + * Copyright (C) 2013-2015 Sensnology AB + * Full contributor list: https://github.com/mysensors/Arduino/graphs/contributors + * + * Documentation: http://www.mysensors.org + * Support Forum: http://forum.mysensors.org + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + */ /*** * This file defines the Sensor library version number * Normally, contributors should not modify this directly @@ -6,6 +24,6 @@ #ifndef Version_h #define Version_h -#define MYSENSORS_LIBRARY_VERSION "2.0.0" +#define MYSENSORS_LIBRARY_VERSION "2.1.0" #endif diff --git a/drivers/AES/AES.cpp b/drivers/AES/AES.cpp index cd5d3067f..d62589f69 100644 --- a/drivers/AES/AES.cpp +++ b/drivers/AES/AES.cpp @@ -36,13 +36,13 @@ /* This version derived by Mark Tillotson 2012-01-23, tidied up, slimmed down and tailored to 8-bit microcontroller abilities and Arduino datatypes. - The s-box and inverse s-box were retained as tables (0.5kB PROGMEM) but all - the other transformations are coded to save table space. Many efficiency + The s-box and inverse s-box were retained as tables (0.5kB PROGMEM) but all + the other transformations are coded to save table space. Many efficiency improvments to the routines mix_sub_columns() and inv_mix_sub_columns() (mainly common sub-expression elimination). Only the routines with precalculated subkey schedule are retained (together - with set_key() - this does however mean each AES object takes 240 bytes of + with set_key() - this does however mean each AES object takes 240 bytes of RAM, alas) The CBC routines side-effect the iv argument (so that successive calls work @@ -65,84 +65,80 @@ #define WPOLY 0x011B #define DPOLY 0x008D -const static byte s_fwd [0x100] PROGMEM = -{ - 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, - 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, - 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, - 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, - 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, - 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, - 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, - 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, - 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, - 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, - 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, - 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, - 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, - 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, - 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, - 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, +const static byte s_fwd [0x100] PROGMEM = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, } ; -const static byte s_inv [0x100] PROGMEM = -{ - 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, - 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, - 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, - 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, - 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, - 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, - 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, - 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, - 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, - 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, - 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, - 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, - 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, - 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, - 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, - 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, +const static byte s_inv [0x100] PROGMEM = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d, } ; // times 2 in the GF(2^8) -#define f2(x) ((x) & 0x80 ? (x << 1) ^ WPOLY : x << 1) +#define f2(x) (((x) & 0x80) ? (x << 1) ^ WPOLY : x << 1) #define d2(x) (((x) >> 1) ^ ((x) & 1 ? DPOLY : 0)) static byte s_box (byte x) { - // return fwd_affine (pgm_read_byte (&inv [x])) ; - return pgm_read_byte (& s_fwd [x]) ; + // return fwd_affine (pgm_read_byte (&inv [x])) ; + return pgm_read_byte (& s_fwd [x]) ; } // Inverse Sbox static byte is_box (byte x) { - // return pgm_read_byte (&inv [inv_affine (x)]) ; - return pgm_read_byte (& s_inv [x]) ; + // return pgm_read_byte (&inv [inv_affine (x)]) ; + return pgm_read_byte (& s_inv [x]) ; } static void xor_block (byte * d, byte * s) { - for (byte i = 0 ; i < N_BLOCK ; i += 4) - { - *d++ ^= *s++ ; // some unrolling - *d++ ^= *s++ ; - *d++ ^= *s++ ; - *d++ ^= *s++ ; - } + for (byte i = 0 ; i < N_BLOCK ; i += 4) { + *d++ ^= *s++ ; // some unrolling + *d++ ^= *s++ ; + *d++ ^= *s++ ; + *d++ ^= *s++ ; + } } static void copy_and_key (byte * d, byte * s, byte * k) { - for (byte i = 0 ; i < N_BLOCK ; i += 4) - { - *d++ = *s++ ^ *k++ ; // some unrolling - *d++ = *s++ ^ *k++ ; - *d++ = *s++ ^ *k++ ; - *d++ = *s++ ^ *k++ ; - } + for (byte i = 0 ; i < N_BLOCK ; i += 4) { + *d++ = *s++ ^ *k++ ; // some unrolling + *d++ = *s++ ^ *k++ ; + *d++ = *s++ ^ *k++ ; + *d++ = *s++ ^ *k++ ; + } } // #define add_round_key(d, k) xor_block (d, k) @@ -151,88 +147,115 @@ static void copy_and_key (byte * d, byte * s, byte * k) static void shift_sub_rows (byte st [N_BLOCK]) { - st [0] = s_box (st [0]) ; st [4] = s_box (st [4]) ; - st [8] = s_box (st [8]) ; st [12] = s_box (st [12]) ; - - byte tt = st [1] ; - st [1] = s_box (st [5]) ; st [5] = s_box (st [9]) ; - st [9] = s_box (st [13]) ; st [13] = s_box (tt) ; - - tt = st[2] ; st [2] = s_box (st [10]) ; st [10] = s_box (tt) ; - tt = st[6] ; st [6] = s_box (st [14]) ; st [14] = s_box (tt) ; - - tt = st[15] ; - st [15] = s_box (st [11]) ; st [11] = s_box (st [7]) ; - st [7] = s_box (st [3]) ; st [3] = s_box (tt) ; + st [0] = s_box (st [0]) ; + st [4] = s_box (st [4]) ; + st [8] = s_box (st [8]) ; + st [12] = s_box (st [12]) ; + + byte tt = st [1] ; + st [1] = s_box (st [5]) ; + st [5] = s_box (st [9]) ; + st [9] = s_box (st [13]) ; + st [13] = s_box (tt) ; + + tt = st[2] ; + st [2] = s_box (st [10]) ; + st [10] = s_box (tt) ; + tt = st[6] ; + st [6] = s_box (st [14]) ; + st [14] = s_box (tt) ; + + tt = st[15] ; + st [15] = s_box (st [11]) ; + st [11] = s_box (st [7]) ; + st [7] = s_box (st [3]) ; + st [3] = s_box (tt) ; } static void inv_shift_sub_rows (byte st[N_BLOCK]) { - st [0] = is_box (st[0]) ; st [4] = is_box (st [4]); - st [8] = is_box (st[8]) ; st [12] = is_box (st [12]); - - byte tt = st[13] ; - st [13] = is_box (st [9]) ; st [9] = is_box (st [5]) ; - st [5] = is_box (st [1]) ; st [1] = is_box (tt) ; - - tt = st [2] ; st [2] = is_box (st [10]) ; st [10] = is_box (tt) ; - tt = st [6] ; st [6] = is_box (st [14]) ; st [14] = is_box (tt) ; - - tt = st [3] ; - st [3] = is_box (st [7]) ; st [7] = is_box (st [11]) ; - st [11] = is_box (st [15]) ; st [15] = is_box (tt) ; + st [0] = is_box (st[0]) ; + st [4] = is_box (st [4]); + st [8] = is_box (st[8]) ; + st [12] = is_box (st [12]); + + byte tt = st[13] ; + st [13] = is_box (st [9]) ; + st [9] = is_box (st [5]) ; + st [5] = is_box (st [1]) ; + st [1] = is_box (tt) ; + + tt = st [2] ; + st [2] = is_box (st [10]) ; + st [10] = is_box (tt) ; + tt = st [6] ; + st [6] = is_box (st [14]) ; + st [14] = is_box (tt) ; + + tt = st [3] ; + st [3] = is_box (st [7]) ; + st [7] = is_box (st [11]) ; + st [11] = is_box (st [15]) ; + st [15] = is_box (tt) ; } /* SUB COLUMNS PHASE */ static void mix_sub_columns (byte dt[N_BLOCK], byte st[N_BLOCK]) { - byte j = 5 ; - byte k = 10 ; - byte l = 15 ; - for (byte i = 0 ; i < N_BLOCK ; i += N_COL) - { - byte a = st [i] ; - byte b = st [j] ; j = (j+N_COL) & 15 ; - byte c = st [k] ; k = (k+N_COL) & 15 ; - byte d = st [l] ; l = (l+N_COL) & 15 ; - byte a1 = s_box (a), b1 = s_box (b), c1 = s_box (c), d1 = s_box (d) ; - byte a2 = f2(a1), b2 = f2(b1), c2 = f2(c1), d2 = f2(d1) ; - dt[i] = a2 ^ b2^b1 ^ c1 ^ d1 ; - dt[i+1] = a1 ^ b2 ^ c2^c1 ^ d1 ; - dt[i+2] = a1 ^ b1 ^ c2 ^ d2^d1 ; - dt[i+3] = a2^a1 ^ b1 ^ c1 ^ d2 ; - } + byte j = 5 ; + byte k = 10 ; + byte l = 15 ; + for (byte i = 0 ; i < N_BLOCK ; i += N_COL) { + byte a = st [i] ; + byte b = st [j] ; + j = (j+N_COL) & 15 ; + byte c = st [k] ; + k = (k+N_COL) & 15 ; + byte d = st [l] ; + l = (l+N_COL) & 15 ; + byte a1 = s_box (a), b1 = s_box (b), c1 = s_box (c), d1 = s_box (d) ; + byte a2 = f2(a1), b2 = f2(b1), c2 = f2(c1), d2 = f2(d1) ; + dt[i] = a2 ^ b2^b1 ^ c1 ^ d1 ; + dt[i+1] = a1 ^ b2 ^ c2^c1 ^ d1 ; + dt[i+2] = a1 ^ b1 ^ c2 ^ d2^d1 ; + dt[i+3] = a2^a1 ^ b1 ^ c1 ^ d2 ; + } } static void inv_mix_sub_columns (byte dt[N_BLOCK], byte st[N_BLOCK]) { - for (byte i = 0 ; i < N_BLOCK ; i += N_COL) - { - byte a1 = st [i] ; - byte b1 = st [i+1] ; - byte c1 = st [i+2] ; - byte d1 = st [i+3] ; - byte a2 = f2(a1), b2 = f2(b1), c2 = f2(c1), d2 = f2(d1) ; - byte a4 = f2(a2), b4 = f2(b2), c4 = f2(c2), d4 = f2(d2) ; - byte a8 = f2(a4), b8 = f2(b4), c8 = f2(c4), d8 = f2(d4) ; - byte a9 = a8 ^ a1,b9 = b8 ^ b1,c9 = c8 ^ c1,d9 = d8 ^ d1 ; - byte ac = a8 ^ a4,bc = b8 ^ b4,cc = c8 ^ c4,dc = d8 ^ d4 ; - - dt[i] = is_box (ac^a2 ^ b9^b2 ^ cc^c1 ^ d9) ; - dt[(i+5)&15] = is_box (a9 ^ bc^b2 ^ c9^c2 ^ dc^d1) ; - dt[(i+10)&15] = is_box (ac^a1 ^ b9 ^ cc^c2 ^ d9^d2) ; - dt[(i+15)&15] = is_box (a9^a2 ^ bc^b1 ^ c9 ^ dc^d2) ; - } + for (byte i = 0 ; i < N_BLOCK ; i += N_COL) { + byte a1 = st [i] ; + byte b1 = st [i+1] ; + byte c1 = st [i+2] ; + byte d1 = st [i+3] ; + byte a2 = f2(a1), b2 = f2(b1), c2 = f2(c1), d2 = f2(d1) ; + byte a4 = f2(a2), b4 = f2(b2), c4 = f2(c2), d4 = f2(d2) ; + byte a8 = f2(a4), b8 = f2(b4), c8 = f2(c4), d8 = f2(d4) ; + byte a9 = a8 ^ a1,b9 = b8 ^ b1,c9 = c8 ^ c1,d9 = d8 ^ d1 ; + byte ac = a8 ^ a4,bc = b8 ^ b4,cc = c8 ^ c4,dc = d8 ^ d4 ; + + dt[i] = is_box (ac^a2 ^ b9^b2 ^ cc^c1 ^ d9) ; + dt[(i+5)&15] = is_box (a9 ^ bc^b2 ^ c9^c2 ^ dc^d1) ; + dt[(i+10)&15] = is_box (ac^a1 ^ b9 ^ cc^c2 ^ d9^d2) ; + dt[(i+15)&15] = is_box (a9^a2 ^ bc^b1 ^ c9 ^ dc^d2) ; + } } /******************************************************************************/ -AES::AES(){ +AES::AES() +{ byte ar_iv[8] = { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01 }; + IVC = 0x01; + round = 0; + pad = 0; + size = 0; + memset(key_sched, 0, KEY_SCHEDULE_BYTES); memcpy(iv,ar_iv,8); memcpy(iv+8,ar_iv,8); - arr_pad[0] = 0x01; + arr_pad[0] = 0x01; arr_pad[1] = 0x02; arr_pad[2] = 0x03; arr_pad[3] = 0x04; @@ -253,200 +276,196 @@ AES::AES(){ byte AES::set_key (byte key [], int keylen) { - byte hi ; - switch (keylen) - { - case 16: - case 128: - keylen = 16; // 10 rounds - round = 10 ; - break; - case 24: - case 192: - keylen = 24; // 12 rounds - round = 12 ; - break; - case 32: - case 256: - keylen = 32; // 14 rounds - round = 14 ; - break; - default: - round = 0; - return AES_FAILURE; - } - hi = (round + 1) << 4 ; - copy_n_bytes (key_sched, key, keylen) ; - byte t[4] ; - byte next = keylen ; - for (byte cc = keylen, rc = 1 ; cc < hi ; cc += N_COL) - { - for (byte i = 0 ; i < N_COL ; i++) - t[i] = key_sched [cc-4+i] ; - if (cc == next) - { - next += keylen ; - byte ttt = t[0] ; - t[0] = s_box (t[1]) ^ rc ; - t[1] = s_box (t[2]) ; - t[2] = s_box (t[3]) ; - t[3] = s_box (ttt) ; - rc = f2 (rc) ; - } - else if (keylen == 32 && (cc & 31) == 16) - { - for (byte i = 0 ; i < 4 ; i++) - t[i] = s_box (t[i]) ; - } - byte tt = cc - keylen ; - for (byte i = 0 ; i < N_COL ; i++) - key_sched [cc + i] = key_sched [tt + i] ^ t[i] ; - } - return AES_SUCCESS ; + byte hi ; + switch (keylen) { + case 16: + case 128: + keylen = 16; // 10 rounds + round = 10 ; + break; + case 24: + case 192: + keylen = 24; // 12 rounds + round = 12 ; + break; + case 32: + case 256: + keylen = 32; // 14 rounds + round = 14 ; + break; + default: + round = 0; + return AES_FAILURE; + } + hi = (round + 1) << 4 ; + copy_n_bytes (key_sched, key, keylen) ; + byte t[4] ; + byte next = keylen ; + for (byte cc = keylen, rc = 1 ; cc < hi ; cc += N_COL) { + for (byte i = 0 ; i < N_COL ; i++) { + t[i] = key_sched [cc-4+i] ; + } + if (cc == next) { + next += keylen ; + byte ttt = t[0] ; + t[0] = s_box (t[1]) ^ rc ; + t[1] = s_box (t[2]) ; + t[2] = s_box (t[3]) ; + t[3] = s_box (ttt) ; + rc = f2 (rc) ; + } else if (keylen == 32 && (cc & 31) == 16) { + for (byte i = 0 ; i < 4 ; i++) { + t[i] = s_box (t[i]) ; + } + } + byte tt = cc - keylen ; + for (byte i = 0 ; i < N_COL ; i++) { + key_sched [cc + i] = key_sched [tt + i] ^ t[i] ; + } + } + return AES_SUCCESS ; } /******************************************************************************/ void AES::clean () { - for (byte i = 0 ; i < KEY_SCHEDULE_BYTES ; i++) - key_sched [i] = 0 ; - round = 0 ; + for (byte i = 0 ; i < KEY_SCHEDULE_BYTES ; i++) { + key_sched [i] = 0 ; + } + round = 0 ; } /******************************************************************************/ void AES::copy_n_bytes (byte * d, byte * s, byte nn) { - while (nn >= 4) - { - *d++ = *s++ ; // some unrolling - *d++ = *s++ ; - *d++ = *s++ ; - *d++ = *s++ ; - nn -= 4 ; - } - while (nn--) - *d++ = *s++ ; + while (nn >= 4) { + *d++ = *s++ ; // some unrolling + *d++ = *s++ ; + *d++ = *s++ ; + *d++ = *s++ ; + nn -= 4 ; + } + while (nn--) { + *d++ = *s++ ; + } } /******************************************************************************/ byte AES::encrypt (byte plain [N_BLOCK], byte cipher [N_BLOCK]) { - if (round) - { - byte s1 [N_BLOCK], r ; - copy_and_key (s1, plain, (byte*) (key_sched)) ; - - for (r = 1 ; r < round ; r++) - { - byte s2 [N_BLOCK] ; - mix_sub_columns (s2, s1) ; - copy_and_key (s1, s2, (byte*) (key_sched + r * N_BLOCK)) ; - } - shift_sub_rows (s1) ; - copy_and_key (cipher, s1, (byte*) (key_sched + r * N_BLOCK)) ; - } - else - return AES_FAILURE ; - return AES_SUCCESS ; + if (round) { + byte s1 [N_BLOCK], r ; + copy_and_key (s1, plain, (byte*) (key_sched)) ; + + for (r = 1 ; r < round ; r++) { + byte s2 [N_BLOCK] ; + mix_sub_columns (s2, s1) ; + copy_and_key (s1, s2, (byte*) (key_sched + r * N_BLOCK)) ; + } + shift_sub_rows (s1) ; + copy_and_key (cipher, s1, (byte*) (key_sched + r * N_BLOCK)) ; + } else { + return AES_FAILURE ; + } + return AES_SUCCESS ; } /******************************************************************************/ byte AES::cbc_encrypt (byte * plain, byte * cipher, int n_block, byte iv [N_BLOCK]) { - while (n_block--) - { - xor_block (iv, plain) ; - if (encrypt (iv, iv) != AES_SUCCESS) - return AES_FAILURE ; - copy_n_bytes (cipher, iv, N_BLOCK) ; - plain += N_BLOCK ; - cipher += N_BLOCK ; - } - return AES_SUCCESS ; + while (n_block--) { + xor_block (iv, plain) ; + if (encrypt (iv, iv) != AES_SUCCESS) { + return AES_FAILURE ; + } + copy_n_bytes (cipher, iv, N_BLOCK) ; + plain += N_BLOCK ; + cipher += N_BLOCK ; + } + return AES_SUCCESS ; } /******************************************************************************/ byte AES::cbc_encrypt (byte * plain, byte * cipher, int n_block) { - while (n_block--) - { - xor_block (iv, plain) ; - if (encrypt (iv, iv) != AES_SUCCESS) - return AES_FAILURE ; - copy_n_bytes (cipher, iv, N_BLOCK) ; - plain += N_BLOCK ; - cipher += N_BLOCK ; - } - return AES_SUCCESS ; + while (n_block--) { + xor_block (iv, plain) ; + if (encrypt (iv, iv) != AES_SUCCESS) { + return AES_FAILURE ; + } + copy_n_bytes (cipher, iv, N_BLOCK) ; + plain += N_BLOCK ; + cipher += N_BLOCK ; + } + return AES_SUCCESS ; } /******************************************************************************/ byte AES::decrypt (byte plain [N_BLOCK], byte cipher [N_BLOCK]) { - if (round) - { - byte s1 [N_BLOCK] ; - copy_and_key (s1, plain, (byte*) (key_sched + round * N_BLOCK)) ; - inv_shift_sub_rows (s1) ; - - for (byte r = round ; --r ; ) - { - byte s2 [N_BLOCK] ; - copy_and_key (s2, s1, (byte*) (key_sched + r * N_BLOCK)) ; - inv_mix_sub_columns (s1, s2) ; - } - copy_and_key (cipher, s1, (byte*) (key_sched)) ; - } - else - return AES_FAILURE ; - return AES_SUCCESS ; + if (round) { + byte s1 [N_BLOCK] ; + copy_and_key (s1, plain, (byte*) (key_sched + round * N_BLOCK)) ; + inv_shift_sub_rows (s1) ; + + for (byte r = round ; --r ; ) { + byte s2 [N_BLOCK] ; + copy_and_key (s2, s1, (byte*) (key_sched + r * N_BLOCK)) ; + inv_mix_sub_columns (s1, s2) ; + } + copy_and_key (cipher, s1, (byte*) (key_sched)) ; + } else { + return AES_FAILURE ; + } + return AES_SUCCESS ; } /******************************************************************************/ byte AES::cbc_decrypt (byte * cipher, byte * plain, int n_block, byte iv [N_BLOCK]) -{ - while (n_block--) - { - byte tmp [N_BLOCK] ; - copy_n_bytes (tmp, cipher, N_BLOCK) ; - if (decrypt (cipher, plain) != AES_SUCCESS) - return AES_FAILURE ; - xor_block (plain, iv) ; - copy_n_bytes (iv, tmp, N_BLOCK) ; - plain += N_BLOCK ; - cipher += N_BLOCK; - } - return AES_SUCCESS ; +{ + while (n_block--) { + byte tmp [N_BLOCK] ; + copy_n_bytes (tmp, cipher, N_BLOCK) ; + if (decrypt (cipher, plain) != AES_SUCCESS) { + return AES_FAILURE ; + } + xor_block (plain, iv) ; + copy_n_bytes (iv, tmp, N_BLOCK) ; + plain += N_BLOCK ; + cipher += N_BLOCK; + } + return AES_SUCCESS ; } /******************************************************************************/ byte AES::cbc_decrypt (byte * cipher, byte * plain, int n_block) -{ - while (n_block--) - { - byte tmp [N_BLOCK] ; - copy_n_bytes (tmp, cipher, N_BLOCK) ; - if (decrypt (cipher, plain) != AES_SUCCESS) - return AES_FAILURE ; - xor_block (plain, iv) ; - copy_n_bytes (iv, tmp, N_BLOCK) ; - plain += N_BLOCK ; - cipher += N_BLOCK; - } - return AES_SUCCESS ; +{ + while (n_block--) { + byte tmp [N_BLOCK] ; + copy_n_bytes (tmp, cipher, N_BLOCK) ; + if (decrypt (cipher, plain) != AES_SUCCESS) { + return AES_FAILURE ; + } + xor_block (plain, iv) ; + copy_n_bytes (iv, tmp, N_BLOCK) ; + plain += N_BLOCK ; + cipher += N_BLOCK; + } + return AES_SUCCESS ; } /*****************************************************************************/ -void AES::set_IV(unsigned long long int IVCl){ +void AES::set_IV(unsigned long long int IVCl) +{ memcpy(iv,&IVCl,8); memcpy(iv+8,&IVCl,8); IVC = IVCl; @@ -454,7 +473,8 @@ void AES::set_IV(unsigned long long int IVCl){ /******************************************************************************/ -void AES::iv_inc(){ +void AES::iv_inc() +{ IVC += 1; memcpy(iv,&IVC,8); memcpy(iv+8,&IVC,8); @@ -462,31 +482,35 @@ void AES::iv_inc(){ /******************************************************************************/ -int AES::get_size(){ +int AES::get_size() +{ return size; } /******************************************************************************/ -void AES::set_size(int sizel){ +void AES::set_size(int sizel) +{ size = sizel; } /******************************************************************************/ -void AES::get_IV(byte *out){ +void AES::get_IV(byte *out) +{ memcpy(out,&IVC,8); memcpy(out+8,&IVC,8); } /******************************************************************************/ -void AES::calc_size_n_pad(int p_size){ +void AES::calc_size_n_pad(int p_size) +{ int s_of_p = p_size - 1; - if ( s_of_p % N_BLOCK == 0){ - size = s_of_p; - }else{ + if ( s_of_p % N_BLOCK == 0) { + size = s_of_p; + } else { size = s_of_p + (N_BLOCK-(s_of_p % N_BLOCK)); } pad = size - s_of_p; @@ -497,59 +521,63 @@ void AES::calc_size_n_pad(int p_size){ void AES::padPlaintext(void* in,byte* out) { memcpy(out,in,size); - for (int i = size-pad; i < size; i++){; + for (int i = size-pad; i < size; i++) { + ; out[i] = arr_pad[pad - 1]; } } /******************************************************************************/ -bool AES::CheckPad(byte* in,int lsize){ - if (in[lsize-1] <= 0x0f){ +bool AES::CheckPad(byte* in,int lsize) +{ + if (in[lsize-1] <= 0x0f) { int lpad = (int)in[lsize-1]; - for (int i = lsize - 1; i >= lsize-lpad; i--){ - if (arr_pad[lpad - 1] != in[i]){ + for (int i = lsize - 1; i >= lsize-lpad; i--) { + if (arr_pad[lpad - 1] != in[i]) { return false; } } - }else{ + } else { return true; } -return true; + return true; } /******************************************************************************/ void AES::printArray(byte output[],bool p_pad) { -uint8_t i,j; -uint8_t loops = size/N_BLOCK; -uint8_t outp = N_BLOCK; -for (j = 0; j < loops; j += 1){ - if (p_pad && (j == (loops - 1)) ) { outp = N_BLOCK - pad; } - for (i = 0; i < outp; i++) - { - printf_P(PSTR("%c"),output[j*N_BLOCK + i]); - } -} - printf_P(PSTR("\n")); + uint8_t i,j; + uint8_t loops = size/N_BLOCK; + uint8_t outp = N_BLOCK; + for (j = 0; j < loops; j += 1) { + if (p_pad && (j == (loops - 1)) ) { + outp = N_BLOCK - pad; + } + for (i = 0; i < outp; i++) { + printf_P(PSTR("%c"),output[j*N_BLOCK + i]); + } + } + printf_P(PSTR("\n")); } /******************************************************************************/ void AES::printArray(byte output[],int sizel) { - for (int i = 0; i < sizel; i++) - { - printf_P(PSTR("%x"),output[i]); - } - printf_P(PSTR("\n")); + for (int i = 0; i < sizel; i++) { + printf_P(PSTR("%x"),output[i]); + } + printf_P(PSTR("\n")); } /******************************************************************************/ -void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits, byte ivl [N_BLOCK]){ +void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits, + byte ivl [N_BLOCK]) +{ calc_size_n_pad(size_p); byte plain_p[get_size()]; padPlaintext(plain,plain_p); @@ -560,7 +588,8 @@ void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits /******************************************************************************/ -void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits){ +void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits) +{ calc_size_n_pad(size_p); byte plain_p[get_size()]; padPlaintext(plain,plain_p); @@ -571,7 +600,9 @@ void AES::do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits /******************************************************************************/ -void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits, byte ivl [N_BLOCK]){ +void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits, + byte ivl [N_BLOCK]) +{ set_size(size_c); int blocks = size_c / N_BLOCK; set_key (key, bits); @@ -580,7 +611,8 @@ void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits /******************************************************************************/ -void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits){ +void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits) +{ set_size(size_c); int blocks = size_c / N_BLOCK; set_key (key, bits); @@ -591,7 +623,8 @@ void AES::do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits /******************************************************************************/ #if defined(AES_LINUX) -double AES::millis(){ +double AES::millis() +{ gettimeofday(&tv, NULL); return (tv.tv_sec + 0.000001 * tv.tv_usec); } diff --git a/drivers/AES/AES.h b/drivers/AES/AES.h index 1d92c8c68..3c49f4e0e 100644 --- a/drivers/AES/AES.h +++ b/drivers/AES/AES.h @@ -31,44 +31,44 @@ This is an AES implementation that uses only 8-bit byte operations on the cipher state. */ - - /* code was modified by george spanos - * 16/12/14 - */ + +/* code was modified by george spanos +* 16/12/14 +*/ /** AES class */ class AES { - public: +public: -/* The following calls are for a precomputed key schedule + /* The following calls are for a precomputed key schedule - NOTE: If the length_type used for the key length is an - unsigned 8-bit character, a key length of 256 bits must - be entered as a length in bytes (valid inputs are hence - 128, 192, 16, 24 and 32). -*/ + NOTE: If the length_type used for the key length is an + unsigned 8-bit character, a key length of 256 bits must + be entered as a length in bytes (valid inputs are hence + 128, 192, 16, 24 and 32). + */ /** \fn AES() * \brief AES constructor - * + * * This function initialized an instance of AES. */ AES(); - - /** Set the cipher key for the pre-keyed version. + + /** Set the cipher key for the pre-keyed version. * @param key[] pointer to the key string. * @param keylen Integer that indicates the length of the key. - * @note NOTE: If the length_type used for the key length is an unsigned 8-bit character, - * a key length of 256 bits must be entered as a length in bytes + * @note NOTE: If the length_type used for the key length is an unsigned 8-bit character, + * a key length of 256 bits must be entered as a length in bytes * (valid inputs are hence 128, 192, 16, 24 and 32). * */ byte set_key (byte key[], int keylen) ; - + /** clean up subkeys after use. - * - */ + * + */ void clean () ; // delete key schedule after use - + /** copying and xoring utilities. * @param *AESt byte pointer of the AEStination array. * @param *src byte pointer of the source array. @@ -92,9 +92,9 @@ class AES * */ byte encrypt (byte plain [N_BLOCK], byte cipher [N_BLOCK]) ; - + /** CBC encrypt a number of blocks (input and return an IV). - * + * * @param *plain Pointer, points to the plaintex. * @param *cipher Pointer, points to the ciphertext that will be created. * @param n_block integer, indicated the number of blocks to be ciphered. @@ -103,9 +103,9 @@ class AES * */ byte cbc_encrypt (byte * plain, byte * cipher, int n_block, byte iv [N_BLOCK]) ; - + /** CBC encrypt a number of blocks (input and return an IV). - * + * * @param *plain Pointer, points to the plaintex. * @param *cipher Pointer, points to the ciphertext that will be created. * @param n_block integer, indicated the number of blocks to be ciphered. @@ -115,7 +115,7 @@ class AES byte cbc_encrypt (byte * plain, byte * cipher, int n_block) ; - /** Decrypt a single block of 16 bytes + /** Decrypt a single block of 16 bytes * @param cipher Array of the ciphertext. * @param plain Array of the plaintext. * @note The N_BLOCK is defined in AES_config.h as, @@ -128,9 +128,9 @@ class AES * */ byte decrypt (byte cipher [N_BLOCK], byte plain [N_BLOCK]) ; - - /** CBC decrypt a number of blocks (input and return an IV) - * + + /** CBC decrypt a number of blocks (input and return an IV) + * * @param *cipher Pointer, points to the ciphertext that will be created. * @param *plain Pointer, points to the plaintex. * @param n_block integer, indicated the number of blocks to be ciphered. @@ -139,9 +139,9 @@ class AES * */ byte cbc_decrypt (byte * cipher, byte * plain, int n_block, byte iv [N_BLOCK]) ; - - /** CBC decrypt a number of blocks (input and return an IV) - * + + /** CBC decrypt a number of blocks (input and return an IV) + * * @param *cipher Pointer, points to the ciphertext that will be created. * @param *plain Pointer, points to the plaintex. * @param n_block integer, indicated the number of blocks to be ciphered. @@ -149,7 +149,7 @@ class AES * */ byte cbc_decrypt (byte * cipher, byte * plain, int n_block) ; - + /** Sets IV (initialization vector) and IVC (IV counter). * This function changes the ivc and iv variables needed for AES. * @@ -158,62 +158,62 @@ class AES * @code unsigned long long int my_iv = 01234567; @endcode */ void set_IV(unsigned long long int IVCl); - + /** increase the iv (initialization vector) and IVC (IV counter) by 1 - * + * * This function increased the VI by one step in order to have a different IV each time - * + * */ void iv_inc(); - + /** Getter method for size - * + * * This function return the size * @return an integer, that is the size of the of the padded plaintext, * thus, the size of the ciphertext. */ int get_size(); - + /** Setter method for size * * This function sets the size of the plaintext+pad - * + * */ void set_size(int sizel); - + /** Getter method for IV - * + * * This function return the IV * @param out byte pointer that gets the IV. * @return none, the IV is writed to the out pointer. */ void get_IV(byte *out); - + /** Calculates the size of the plaintext and the padding. - * + * * Calculates the size of theplaintext with the padding * and the size of the padding needed. Moreover it stores them in their class variables. - * + * * @param p_size the size of the byte array ex sizeof(plaintext) */ void calc_size_n_pad(int p_size); - + /** Pads the plaintext - * - * This function pads the plaintext and returns an char array with the - * plaintext and the padding in order for the plaintext to be compatible with + * + * This function pads the plaintext and returns an char array with the + * plaintext and the padding in order for the plaintext to be compatible with * 16bit size blocks required by AES - * + * * @param in the string of the plaintext in a byte array * @param out The string of the out array. * @return no return, The padded plaintext is stored in the out pointer. */ void padPlaintext(void* in,byte* out); - + /** Check the if the padding is correct. - * + * * This functions checks the padding of the plaintext. - * + * * @param in the string of the plaintext in a byte array * @param size the size of the string * @return true if correct / false if not @@ -221,26 +221,26 @@ class AES bool CheckPad(byte* in,int size); /** Prints the array given. - * - * This function prints the given array and pad, + * + * This function prints the given array and pad, * It is mainlly used for debugging purpuses or to output the string. - * + * * @param output[] the string of the text in a byte array * @param p_pad optional, used to print with out the padding characters */ void printArray(byte output[],bool p_pad = true); - + /** Prints the array given. - * + * * This function prints the given array in Hexadecimal. - * + * * @param output[] the string of the text in a byte array * @param sizel the size of the array. */ void printArray(byte output[],int sizel); - + /** User friendly implementation of AES-CBC encryption. - * + * * @param *plain pointer to the plaintext * @param size_p size of the plaintext * @param *cipher pointer to the ciphertext @@ -250,9 +250,9 @@ class AES * @note The key will be stored in class variable. */ void do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits, byte ivl [N_BLOCK]); - + /** User friendly implementation of AES-CBC encryption. - * + * * @param *plain pointer to the plaintext * @param size_p size of the plaintext * @param *cipher pointer to the ciphertext @@ -261,9 +261,9 @@ class AES * @note The key will be stored in class variable. */ void do_aes_encrypt(byte *plain,int size_p,byte *cipher,byte *key, int bits); - + /** User friendly implementation of AES-CBC decryption. - * + * * @param *cipher pointer to the ciphertext * @param size_c size of the ciphertext * @param *plain pointer to the plaintext @@ -273,9 +273,9 @@ class AES * @note The key will be stored in class variable. */ void do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits, byte ivl [N_BLOCK]); - + /** User friendly implementation of AES-CBC decryption. - * + * * @param *cipher pointer to the ciphertext * @param size_c size of the ciphertext * @param *plain pointer to the plaintext @@ -285,68 +285,33 @@ class AES */ void do_aes_decrypt(byte *cipher,int size_c,byte *plain,byte *key, int bits); - #if defined(AES_LINUX) - /** - * used in linux in order to retrieve the time in milliseconds. - * - * @return returns the milliseconds in a double format. - */ - double millis(); - #endif - private: - int round ;/**< holds the number of rounds to be used. */ - byte key_sched [KEY_SCHEDULE_BYTES] ;/**< holds the pre-computed key for the encryption/decrpytion. */ - unsigned long long int IVC;/**< holds the initialization vector counter in numerical format. */ - byte iv[16];/**< holds the initialization vector that will be used in the cipher. */ - int pad;/**< holds the size of the padding. */ - int size;/**< hold the size of the plaintext to be ciphered */ - #if defined(AES_LINUX) +#if defined(AES_LINUX) + /** + * used in linux in order to retrieve the time in milliseconds. + * + * @return returns the milliseconds in a double format. + */ + double millis(); +#endif +private: + int round ;/**< holds the number of rounds to be used. */ + byte key_sched [KEY_SCHEDULE_BYTES] + ;/**< holds the pre-computed key for the encryption/decrpytion. */ + unsigned long long int IVC;/**< holds the initialization vector counter in numerical format. */ + byte iv[16];/**< holds the initialization vector that will be used in the cipher. */ + int pad;/**< holds the size of the padding. */ + int size;/**< hold the size of the plaintext to be ciphered */ +#if defined(AES_LINUX) timeval tv;/**< holds the time value on linux */ byte arr_pad[15];/**< holds the hexadecimal padding values on linux */ - #else +#else byte arr_pad[15];// = { 0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f };/**< holds the hexadecimal padding values */ - #endif +#endif } ; #endif -/** - * @example aes.pde - * For Arduino
- * Updated: spaniakos 2015
- * - * This is an example of how to use AES in CBC mode easily. - * The text and keys can be either in HEX or String format.
- */ - - /** - * @example aes.cpp - * For Rasberry pi
- * Updated: spaniakos 2015
- * - * This is an example of how to use AES in CBC mode easily. - * The text and keys can be either in HEX or String format.
- */ - - /** - * @example test_vectors.pde - * For Arduino
- * Updated: spaniakos 2015
- * - * This is an example of monte carlo test vectors, in order to justify the effectiveness of the algorithm.
- * plus is a classical approach to the AES encryption library with out the easy to use add-on modifications. - */ - - /** - * @example test_vectors.cpp - * For Rasberry pi
- * Updated: spaniakos 2015
- * - * This is an example of monte carlo test vectors, in order to justify the effectiveness of the algorithm.
- * plus is a classical approach to the AES encryption library with out the easy to use add-on modifications. - */ - /** * @defgroup aeslib AES library for Arduino and Raspberry pi * @ingroup internals @@ -362,21 +327,21 @@ class AES * * @section Acknowledgements Acknowledgements * This is an AES library for the Arduino, based on tzikis's AES library, which you can find here:.
- * Tzikis library was based on scottmac`s library, which you can find here:
- * + * Tzikis library was based on scottmac`s library, which you can find here:
+ * * @section Installation Installation *

Arduino

- * Create a folder named _AES_ in the _libraries_ folder inside your Arduino sketch folder. If the + * Create a folder named _AES_ in the _libraries_ folder inside your Arduino sketch folder. If the * libraries folder doesn't exist, create it. Then copy everything inside. (re)launch the Arduino IDE.
* You're done. Time for a mojito - * + * *

Raspberry pi

* install

- * + * * sudo make install
* cd examples_Rpi
* make

- * + * * What to do after changes to the library

* sudo make clean
* sudo make install
@@ -392,7 +357,7 @@ class AES * How to start a sketch

* cd examples_Rpi
* sudo ./\

- * + * * @section AesNews News * * If issues are discovered with the documentation, please report them here @@ -411,6 +376,6 @@ class AES * - Arduino * - Intel Galileo support * - Raspberry Pi Support - * + * * - The library has not been tested to other boards, but it should suppport ATMega 328 based boards,Mega Boards,Arduino Due,ATTiny board */ diff --git a/drivers/AES/AES_config.h b/drivers/AES/AES_config.h index d0ae8282f..ab3848960 100644 --- a/drivers/AES/AES_config.h +++ b/drivers/AES/AES_config.h @@ -7,34 +7,34 @@ #if (defined(__linux) || defined(linux)) && !defined(__ARDUINO_X86__) - #define AES_LINUX - - #include - #include - #include - #include - #include - #include +#define AES_LINUX + +#include +#include +#include +#include +#include +#include #else - #include +#include #endif #include #include #if defined(__ARDUINO_X86__) || (defined (__linux) || defined (linux)) - #undef PROGMEM - #define PROGMEM __attribute__(( section(".progmem.data") )) - #define pgm_read_byte(p) (*(p)) - typedef unsigned char byte; - #define printf_P printf - #define PSTR(x) (x) +#undef PROGMEM +#define PROGMEM __attribute__(( section(".progmem.data") )) +#define pgm_read_byte(p) (*(p)) +typedef unsigned char byte; +#define printf_P printf +#define PSTR(x) (x) #elif defined(ARDUINO_ARCH_ESP8266) - #include +#include #elif defined(ARDUINO_ARCH_SAMD) - #define printf_P printf +#define printf_P printf #else - #include +#include #endif #define N_ROW 4 diff --git a/drivers/AES/Doxyfile b/drivers/AES/Doxyfile deleted file mode 100644 index 79cf3b7dc..000000000 --- a/drivers/AES/Doxyfile +++ /dev/null @@ -1,2372 +0,0 @@ -# Doxyfile 1.8.6 - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. - -DOXYFILE_ENCODING = UTF-8 - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. - -PROJECT_NAME = "AES Encryption Library for Arduino and Raspberry Pi" - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = "Spaniakos - AES Encryption Library for Arduino and Raspberry Pi" - -# With the PROJECT_LOGO tag one can specify an logo or icon that is included in -# the documentation. The maximum height of the logo should not exceed 55 pixels -# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo -# to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = "../Documentations/AES" - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. - -CREATE_SUBDIRS = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - -OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = YES - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = "The $name class" \ - "The $name widget" \ - "The $name file" \ - is \ - provides \ - specifies \ - contains \ - represents \ - a \ - an \ - the - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - -FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - -STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - -INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a -# new page for each member. If set to NO, the documentation of a member will be -# part of the file/class/namespace that contains it. -# The default value is: NO. - -SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_FOR_C = NO - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL. For instance to make -# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C -# (default is Fortran), use: inc=Fortran f=C. -# -# Note For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - -EXTENSION_MAPPING = ino=c \ - pde=c - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - -MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by by putting a % sign in front of the word -# or globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - -AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - -BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - -SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - -DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - -SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - -INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - -INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = NO - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = YES - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will -# be included in the documentation. -# The default value is: NO. - -EXTRACT_PRIVATE = YES - -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = YES - -# If the EXTRACT_STATIC tag is set to YES all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = YES - -# This flag is only useful for Objective-C code. When set to YES local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = YES - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - -EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO these classes will be included in the various overviews. This option has -# no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO these declarations will be -# included in the documentation. -# The default value is: NO. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES the -# scope will be hidden. -# The default value is: NO. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - -SHOW_INCLUDE_FILES = YES - - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = NO - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. -# The default value is: YES. - -SORT_MEMBER_DOCS = NO - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = NO - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - -SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = YES - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the -# todo list. This list is created by putting \todo commands in the -# documentation. -# The default value is: YES. - -GENERATE_TODOLIST = NO - -# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the -# test list. This list is created by putting \test commands in the -# documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES the list -# will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = NO - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = NO - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - -FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - -LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. Do not use file names with spaces, bibtex cannot handle them. See -# also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - -WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = YES - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - -WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO doxygen will only warn about wrong or incomplete parameter -# documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = NO - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. -# Note: If this tag is empty the current directory is searched. - -INPUT = ./ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - -INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. - -FILE_PATTERNS = *.c \ - *.cc \ - *.cxx \ - *.cpp \ - *.c++ \ - *.java \ - *.ii \ - *.ixx \ - *.ipp \ - *.i++ \ - *.inl \ - *.idl \ - *.ddl \ - *.odl \ - *.h \ - *.hh \ - *.hxx \ - *.hpp \ - *.h++ \ - *.cs \ - *.d \ - *.php \ - *.php4 \ - *.php5 \ - *.phtml \ - *.inc \ - *.m \ - *.markdown \ - *.md \ - *.mm \ - *.dox \ - *.py \ - *.f90 \ - *.f \ - *.for \ - *.tcl \ - *.vhd \ - *.vhdl \ - *.ucf \ - *.qsf \ - *.as \ - *.js - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = examples \ - examples_RPi \ - examples_Rpi - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = YES - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. - -INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. - -FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER ) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - -FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - -FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - -STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = NO - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = NO - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES, then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - -REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - -USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - -VERBATIM_HEADERS = YES - -# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the -# clang parser (see: http://clang.llvm.org/) for more acurate parsing at the -# cost of reduced performance. This can be particularly helpful with template -# rich C++ code for which doxygen's built-in parser lacks the necessary type -# information. -# Note: The availability of this option depends on whether or not doxygen was -# compiled with the --with-libclang option. -# The default value is: NO. - -CLANG_ASSISTED_PARSING = NO - -# If clang assisted parsing is enabled you can provide the compiler with command -# line options that you would normally use when invoking the compiler. Note that -# the include paths will already be set by doxygen for the files and directories -# specified with INPUT and INCLUDE_PATH. -# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. - -CLANG_OPTIONS = - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - -ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 10 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output -# The default value is: YES. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional user- -# defined cascading style sheet that is included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. -# Doxygen will copy the style sheet file to the output directory. For an example -# see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = doxygen-custom.css - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the stylesheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = YES - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - -DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler ( hhc.exe). If non-empty -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated ( -# YES) or that it should be included in the master .chm file ( NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated ( -# YES) or a normal table of contents ( NO) in the .chm file. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - -QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - -ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - -ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using prerendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /